/** @preserve Eloquence WEBDLG v2
 (C) Copyright Marxmeier Software AG, 2020-2025
 Version: 2.1.2
 $Id: 00-base.js,v 29.22 2025/06/18 11:26:11 rhg Exp $
*//*======================================================================
   WEBDLG v2 namespace / globals
========================================================================*/

var eq = eq || {};
eq.c = eq.c || {};   // Classes
eq.p = eq.p || {};   // Properties:
eq.p.version = 20102; // Version 2.01.02 (major*10,000 + minor*100 + build)

/*========================================================================
   class Obj
   Generic base class.
========================================================================*/

eq.c.Obj = function() {};

/*------------------------------------------------------------------------
   static Obj.subClass()
   Subclassing convenience helper.
------------------------------------------------------------------------*/

(eq.c.Obj.setClassName = function(name, cls) {
   var pd = Object.getOwnPropertyDescriptor(cls.prototype.constructor, 'name');
   if (pd !== undefined)
      pd.value = name;
   else
      pd = {
         value : name,
         configurable : true,
         enumerable : false,
         writable : false
      };
   Object.defineProperty(cls.prototype.constructor, 'name', pd);
})("Obj", eq.c.Obj);

eq.c.Obj.subClass = function(name, cls) {
   var base = this;
   cls.prototype = Object.create(base.prototype);
   Object.defineProperty(cls.prototype, 'constructor', {
      value : cls,
      configurable : false,
      enumerable : false,
      writable : true
   });
   eq.c.Obj.setClassName(name, cls);
   cls.subClass = function(n, c) { return base.subClass.call(this, n, c) };
   return eq.c[name] = cls;
};

/** @preserve $Id: 05-api-comm.js,v 29.5 2025/07/03 14:16:08 rhg Exp $
*//*======================================================================
   class Rq extends Obj
   DLG request.
========================================================================*/

eq.c.Obj.subClass('Rq', function() {
   eq.c.Obj.call(this);
});

/*------------------------------------------------------------------------
   Rq.dispose()
   Dispose request data buffer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.dispose = function() {
   this.bf = undefined;
   this.bfOffset = undefined;
   this.bfLength = undefined;
};

/*------------------------------------------------------------------------
   Rq.assign()
   Assign request data buffer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.assign = function(buffer) {
   this.bf = new Uint8Array(buffer);
   this.bfOffset = 0;
   this.bfLength = this.bf.length;
};

/*------------------------------------------------------------------------
   Rq.acquire()
   Acquire ownership of request data buffer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.acquire = function() {
   if (this.bf == undefined)
      throw new Error("DLG buffer undefined");
   if (this.bfOffset > this.bfLength)
      throw new Error("DLG buffer invalid offset " + this.bfOffset +
         "max: " + this.bfLength);
   this.bf = new Uint8Array(this.bf.buffer.slice(this.bfOffset));
   this.bfLength -= this.bfOffset;
   this.bfOffset = 0;
   if (this.bfLength !== this.bf.length)
      throw new Error("DLG buffer invalid length " + this.bfLength +
         "expected: " + this.bf.length);
};

/*------------------------------------------------------------------------
   Rq.decodeUi32()
   Decode varint-encoded 32-bit unsigned integer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.decodeUi32 = function() {
   var iv = 0, shift = 0, b;
   for (var cnt = 0;;) {
      if (this.bfOffset >= this.bfLength)
         throw new Error("DLG buffer offset exceeded: " +
                         (this.bfOffset + 1) + ", max: " + this.bfLength);
      if (++cnt > 5)
         throw new Error("DLG buffer varint overflow, offset: " +
                         this.bfOffset);

      b = this.bf[this.bfOffset++];
      if (!(b & 0x80))
         break;

      iv |= (b & 0x7f) << shift;
      shift += 7;
   }
   return iv | b << shift;
};

/*------------------------------------------------------------------------
   Rq.decodeUint()
   Decode varint-encoded unsigned integer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.decodeUint = function() {
   var off = this.bfOffset, iv = this.decodeUi32();
   if (iv < 0)
      throw new Error("DLG buffer UINT out of range: " +
                      iv + ", offset: " + off);
   return iv;
};

/*------------------------------------------------------------------------
   Rq.decodeInt()
   Decode varint-encoded signed integer.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.decodeInt = function() {
   var iv = this.decodeUi32();
   return iv & 1 ? iv >>> 1 ^ -1 : iv >>> 1;
};

/*------------------------------------------------------------------------
   Rq.decodeString()
   Decode varint-encoded string.
------------------------------------------------------------------------*/

eq.c.Rq.decodeStringFailed = function(off, len, minLen) {
   throw new Error("DLG buffer invalid STRING UTF-8 encoding, offset: " +
                   off + ", len: " + len + ", min: " + minLen);
};

eq.c.Rq.prototype.decodeString = function() {
   var len = this.decodeUint(), off = this.bfOffset;

   this.bfOffset += len;
   if (this.bfOffset > this.bfLength)
      throw new Error("DLG buffer offset exceeded: " +
                      this.bfOffset + ", max: " + this.bfLength);

   var s = "";
   while (len) {
      var b = this.bf[off];

      if (b < 0x80) {
         s += String.fromCharCode(b);
         off += 1;
         len -= 1;
      }
      else if (b > 0xbf && b < 0xe0) {
         if (len < 2)
            eq.c.Rq.decodeStringFailed(off, len, 2);

         s += String.fromCharCode(  (b & 0x1f) << 6
                                  | this.bf[off+1] & 0x3f);
         off += 2;
         len -= 2;
      }
      else if (b > 0xdf && b < 0xf0) {
         if (len < 3)
            eq.c.Rq.decodeStringFailed(off, len, 3);

         s += String.fromCharCode(  (b & 0xf) << 12
                                  | (this.bf[off+1] & 0x3f) << 6
                                  | this.bf[off+2] & 0x3f);
         off += 3;
         len -= 3;
      }
      else {
         // Surrogate pair.
         if (len < 4)
            eq.c.Rq.decodeStringFailed(off, len, 4);

         var c = (  (b & 0x7) << 18
                  | (this.bf[off+1] & 0x3f) << 12
                  | (this.bf[off+2] & 0x3f) << 6
                  | this.bf[off+3] & 0x3f) - 0x10000;
         s += String.fromCharCode(c >> 10 | 0xd800, c & 0x3ff | 0xdc00);
         off += 4;
         len -= 4;
      }
   }

   return s;
};

/*------------------------------------------------------------------------
   Rq.decodeData()
   Decode varint-encoded binary data.
------------------------------------------------------------------------*/

eq.c.Rq.prototype.decodeData = function() {
   var len = this.decodeUint(), off = this.bfOffset;

   this.bfOffset += len;
   if (this.bfOffset > this.bfLength)
      throw new Error("DLG buffer offset exceeded: " +
                      this.bfOffset + ", max: " + this.bfLength);

   return this.bf.subarray(off, off + len);
};

/*========================================================================
   class DlgRq extends Rq
   DLG request.
========================================================================*/

eq.c.Rq.subClass('DlgRq', function() {
   eq.c.Rq.call(this);
   this.next = null;
});

/*------------------------------------------------------------------------
   static DlgRq.mode
   DLG request modes.
------------------------------------------------------------------------*/

eq.c.DlgRq.mode = {
   ssnNew     : 1,  // New session
   ssnResume  : 2,  // Resume session
   message    : 3,  // Issue message to user
   login      : 4,  // Obtain login credentials
   global     : 5,  // Global properties
   rootFont   : 6,  // Root font face/size/style
   add        : 7,  // Add control(s)
   modify     : 8,  // Modify control
   del        : 9,  // Delete control
   cancel     : 10, // Cancel current call
   invalidate : 11, // Invalidate dialog(s)
   DLG_DRAW   : 12, // DLG DRAW
   DLG_DO     : 13, // DLG DO
   POPUP_BOX  : 14, // POPUP BOX
   update     : 15, // Update request
   updated    : 16  // Update response
};

/*------------------------------------------------------------------------
   DlgRq.dispose()
   Dispose request message.
------------------------------------------------------------------------*/

eq.c.DlgRq.prototype.dispose = function() {
   eq.c.Rq.prototype.dispose.call(this);
   this.msg = undefined;
   if (this.next) {
      var next = this.next;
      this.next = null;
      next.dispose();
   }
};

/*------------------------------------------------------------------------
   Rq.assign()
   Assign request message.
------------------------------------------------------------------------*/

eq.c.DlgRq.prototype.assign = function(buffer) {
   eq.c.Rq.prototype.assign.call(this, buffer);
   this.msg = [];
};

/*========================================================================
   class Rs extends Obj
   DLG response.
========================================================================*/

eq.c.Obj.subClass('Rs', function() {
   eq.c.Obj.call(this);
   this.bf = [];
});

/*------------------------------------------------------------------------
   Rs.dispose()
   Dispose response data buffer.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.dispose = function() {
   this.bf.length = 0;
};

/*------------------------------------------------------------------------
   Rs.buffer()
   Return and dispose response data buffer as Uint8Array.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.buffer = function() {
   var a = new Uint8Array(this.bf);
   this.bf.length = 0;
   return a.buffer;
};

/*------------------------------------------------------------------------
   Rs.takeBuffer() .putBuffer()
   Take/put response data buffer as array.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.takeBuffer = function() {
   var bf = this.bf;
   this.bf = [];
   return bf;
};

eq.c.Rs.prototype.putBuffer = function(bf) {
   this.bf = bf;
};

/*------------------------------------------------------------------------
   Rs.encodeUint()
   Varint-encode unsigned integer.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.encodeUint = function(iv) {
   var bf = this.bf, cnt = 0;
   while (iv & ~0x7f) {
      if (++cnt === 5)
         throw new Error("Varint value overflow");
      bf.push((iv & 0x7f) | 0x80);
      iv >>>= 7;
   }
   bf.push(iv);
};

/*------------------------------------------------------------------------
   Rs.encodeInt()
   Varint-encode signed integer.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.encodeInt = function(iv) {
   this.encodeUint(iv < 0 ? iv << 1 ^ -1 : iv << 1);
};

/*------------------------------------------------------------------------
   Rs.encodeString()
   Varint-encode string.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.encodeString = function(s) {
   var bf = this.bf, i, bi, c, l = 0, sl = s.length;
   for (i = 0; i < sl; i++) {
      c = s.charCodeAt(i);
      if (c < 0x80)
         l += 1;
      else if (c < 0x800)
         l += 2;
      else if (c < 0xd800 || c >= 0xe000)
         l += 3;
      else {
         // Surrogate pair.
         if (++i === sl)
            throw new Error("Incomplete STRING surrogate pair, offset: " + i);
         l += 4;
      }
   }

   this.encodeUint(l);
   bi = bf.length;
   bf.length += l;

   for (i = 0; i < sl; i++) {
      c = s.charCodeAt(i);
      if (c < 0x80)
         bf[bi++] = c;
      else if (c < 0x800) {
         bf[bi++] = 0xc0 | (c >> 6);
         bf[bi++] = 0x80 | (c & 0x3f);
      }
      else if (c < 0xd800 || c >= 0xe000) {
         bf[bi++] = 0xe0 | (c >> 12);
         bf[bi++] = 0x80 | ((c >> 6) & 0x3f);
         bf[bi++] = 0x80 | (c & 0x3f);
      }
      else {
         // Surrogate pair.
         c = 0x10000
           + (((c & 0x3ff) << 10) | (s.charCodeAt(++i) & 0x3ff));
         bf[bi++] = 0xf0 | (c >> 18);
         bf[bi++] = 0x80 | ((c >> 12) & 0x3f);
         bf[bi++] = 0x80 | ((c >> 6) & 0x3f);
         bf[bi++] = 0x80 | (c & 0x3f);
      }
   }
};

/*------------------------------------------------------------------------
   Rs.encodeData()
   Varint-encode binary data.
------------------------------------------------------------------------*/

eq.c.Rs.prototype.encodeData = function(d) {
   var bf = this.bf, i, bi, l = d.length;

   this.encodeUint(l);
   bi = bf.length;
   bf.length += l;

   for (i = 0; i < l; i++)
      bf[bi++] = d[i];
};

/*========================================================================
   class DlgRs extends Rs
   DLG response.
========================================================================*/

eq.c.Rs.subClass('DlgRs', function() {
   eq.c.Rs.call(this);
});

/*------------------------------------------------------------------------
   static DlgRs.op
   DLG response opcodes.
------------------------------------------------------------------------*/

eq.c.DlgRs.op = {
   start      : 1, // Initialize DLG session, start application
   resume     : 2, // Resume application
   synced     : 3, // SYNCED response, also Dialog.do implicit DLG DRAW
   event      : 4, // DLG DO response
   popup      : 5, // POPUP BOX response
   invalidate : 6, // Invalidate dialog
   update     : 7, // Request update
   updated    : 8, // Update response
   canceled   : 9, // Current call canceled
   logError   : 10 // Forward error log message to application
};

/** @preserve $Id: 05-api-dlg.js,v 29.7 2025/07/03 14:14:54 rhg Exp $
*//*======================================================================
   WEBDLG v2 namespace / globals
   List of DLG control classes.
========================================================================*/

eq.d = eq.d || [];

/*========================================================================
   class Dlg extends Obj
   DLG dialog instance.
========================================================================*/

eq.c.Obj.subClass('Dlg', function(id) {
   eq.c.Obj.call(this);

   this.id = id;        // Dialog id.
   this.root = null;    // Root control.

   // Layout grid.
   this.grid = {
      // wr : Width reference 1ch
      // wp : Width pixels
      // wf : Width ch factor
      // hr : Height reference 1em
      // hp : Height pixels
      // hf : Height em factor
   };

   this.tabOrder = []; // Tab order.
   this.tabOrderValid = false;
});

eq.c.Dlg.prototype.temp = false; // Temporary, closed after use.

/*------------------------------------------------------------------------
   Dlg.close()
   Close dialog instance.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.close = function() {
   if (this.root) {
      this.root.del();
      this.root = null;
   }

   this.tabOrder.length = 0;
   this.tabOrderValid = false;
};

/*------------------------------------------------------------------------
   Dlg.addChild()
   Add control from DLG request.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.addChild = function(rq, ctParent, etParent) {
   // Control class, create control, set id.
   var ct = new eq.d[rq.decodeUint() - 1]();
   ct.id = rq.decodeString();

   // Add to parent.
   var updated;
   if (ctParent) {
      ctParent.addChild(ct);
      if (ct.focusable && !ct.containerFocusable())
         ct.focusable = false;
   }
   else if (!(updated = rq.mode === eq.c.DlgRq.mode.updated)) {
      if (this.root)
         this.root.del();
      this.root = ct;
   }

   // Setup control, create children if any.

   var et = {
      // DOM element template.
      // See: eq.c.App.prototype.addControls()

      id  : ct.id,              // Element id
      dcn : ct.constructor.name // DLG class name

      // pa  : "" Parent element id
      // tg  : "" Tag
      // cla : [] Add class names
      // clr : [] Remove class names
      // at  : {} Attributes
      // st  : {} Styles
      // ds  : {} Data attributes
      // hc  : "" Text or HTML content
      // hca : "" Text or HTML content w/ optional accelerator
      // ec  : "" EditText content
      // rt  : [] Value by request tag
      // rtd : [] Value by request tag, delay until next event cycle
      // cr  : [] CSS rules { ty, rl }
      // ev  : {  Event(s):
      //        a : [] Action event type(s)
      //        c : [] Change event type(s)
      //        m : Expected number of mouse clicks
      //        f : Callback-specific event flags
      //       }
      // dlg : {  Dialog properties:
      //        l  : {  Layout
      //              x, y, w, h, mode, state
      //             }
      //        t  : "" Title
      //        ic : "" Logo/icon url
      //        f  : [] Function key rule values
      //        tm : {} Timer
      //        vc : Rule value
      //        d  : {} Dialog.do
      //       }
      // et  : {} Nested template
      // ch  : [] Children
   };

   if (etParent) {
      etParent.ch = etParent.ch || [];
      etParent.ch.push(et);
   }
   else if (!updated) {
      if (ctParent)
         et.pa = ctParent.id;
      else if (this.temp)
         et.pa = 'eq-temp';
      if (!this.temp || ct.tempUi)
         rq.msg.push({
            o : eq.MainOp.addControl,
            i : this.id,
            d : et
         });
   }

   ct.setup(this, rq, et);

   if (ct.focusable && ct.visible) {
      var cl = et.cla = et.cla || [];
      cl.push('eq-focus');
   }

   if (updated)
      ct.del();
   return et;
}

/*------------------------------------------------------------------------
   Dlg.findById()
   Find control by id.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.findById = function(id, required) {
   if (this.root) {
      var found = this.root.findById(id);
      if (found)
         return found;
   }
   if (required)
      throw new Error("Dlg.findById(" + id + ") failed: not found");
   return null;
}

/*------------------------------------------------------------------------
   Dlg.gridWidth() .gridHeight()
   Return width/height in grid units.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.gridWidth = function(w) {
   if (w && this.grid.wp)
      return Math.round(w / this.grid.wp);
   return 0;
};

eq.c.Dlg.prototype.gridHeight = function(h) {
   if (h && this.grid.hp)
      return Math.round(h / this.grid.hp);
   return 0;
};

/*------------------------------------------------------------------------
   Dlg.pxWidth() .pxHeight()
   Return width/height in pixel units.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.pxWidth = function(w) {
   if (w)
      return Math.round(w * this.grid.wp * 1000) / 1000;
   return 0;
};

eq.c.Dlg.prototype.pxHeight = function(h) {
   if (h)
      return Math.round(h * this.grid.hp * 1000) / 1000;
   return 0;
};

/*------------------------------------------------------------------------
   Dlg.cssWidth() .cssHeight()
   Return width/height in CSS units.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.cssWidth = function(w) {
   if (w)
      return (Math.round(w * this.grid.wf * 1000) / 1000) + 'ch';
   return 0;
};

eq.c.Dlg.prototype.cssHeight = function(h) {
   if (h)
      return (Math.round(h * this.grid.hf * 1000) / 1000) + 'em';
   return 0;
};

/*------------------------------------------------------------------------
   Dlg.updateTabOrder()
   Fill current tab order array.
------------------------------------------------------------------------*/

eq.c.Dlg.prototype.updateTabOrder = function() {
   this.tabOrder.length = 0;
   if (this.tabOrderValid = this.root !== null)
      this.root.updateTabOrder(this.tabOrder);
}

/*========================================================================
   class DlgControl extends Obj
   DLG control base class.
========================================================================*/

eq.c.Obj.subClass('DlgControl', function() {
   eq.c.Obj.call(this);
});

eq.c.DlgControl.prototype.posRaster = true;
eq.c.DlgControl.prototype.sizeRaster = true;
eq.c.DlgControl.prototype.defaultHeight = true;
eq.c.DlgControl.prototype.focusable = true;
eq.c.DlgControl.prototype.visible = true;
eq.c.DlgControl.prototype.sensitive = true;
eq.c.DlgControl.prototype.border = true;

/*------------------------------------------------------------------------
   static DlgControl.addClass()
   Subclassing convenience helper: Add DLG control class.
------------------------------------------------------------------------*/

eq.c.DlgControl.addClass = function(name, cls) {
   this.subClass(name, cls);
   eq.d.push(cls);
   return cls;
};

/*------------------------------------------------------------------------
   DlgControl.del()
   Delete control.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.del = function(level) {
   this.id = undefined;
   if (this.parent) {
      if (!level)
         this.parent.childDeleted(this);
      this.parent = undefined;
      return false;
   }
   return true; // Root control deleted.
};

/*------------------------------------------------------------------------
   DlgControl.setup()
   Setup control from DLG request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.setup = function(dlg, rq, et) {
   var tag;
   while (tag = rq.decodeUint())
      this.setAttr(dlg, rq, tag, et);
};

/*------------------------------------------------------------------------
   static DlgControl.isSetup()
   Check if specified template level(s) exist.
   In 'modify' mode set them up if necessary.
------------------------------------------------------------------------*/

eq.c.DlgControl.isSetup = function(rq, et, levels) {
   var modify, setup = false;
   for (var i = 2; i <= levels; i++, et = et.et)
      if (et.et === undefined) {
         if (modify === undefined)
            modify = rq.mode === eq.c.DlgRq.mode.modify;
         if (!modify)
            throw new Error("DlgControl.isSetup: missing level " + i);
         et.et = {};
         setup = true;
      }
   return setup;
};

/*------------------------------------------------------------------------
   DlgControl.modify()
   Modify control from DLG request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.modify = function(dlg, rq, et) {
   var tag, f0 = this.focusable, f1, v0 = this.visible, v1;
   while (tag = rq.decodeUint())
      this.setAttr(dlg, rq, tag, et);

   f1 = this.focusable;
   v1 = this.visible;
   if (!!f0 !== !!f1 || !!v0 !== !!v1) {
      var cl;
      if (f1 && v1)
         cl = et.cla = et.cla || [];
      else
         cl = et.clr = et.clr || [];
      cl.push('eq-focus');
   }
};

/*------------------------------------------------------------------------
   DlgControl.setAttr()
   Set control attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.cssid:
         this.id = et.id = rq.decodeString();
         break;

      case rqTag.cssclsDel: {
         var sv, cl = et.clr = et.clr || [];
         while ((sv = rq.decodeString()).length)
            cl.push(sv);
         break;
      }

      case rqTag.cssclsAdd: {
         var sv, cl = et.cla = et.cla || [];
         while ((sv = rq.decodeString()).length)
            cl.push(sv);
         break;
      }

      case rqTag.font: {
         var
            api = eq.api,
            fn = api.fixupFont({
               fc : rq.decodeString(),                  // Font face
               sz : api.fixupFontSize(rq.decodeUint()), //      size
               st : rq.decodeUint()                     //      style
            }),
            ffc = fn.fc,
            fsz = fn.sz,
            fst = fn.st,
            fs = eq.FontStyle,
            rl, cl;
         if (ffc || fsz || (fst & (fs.bold | fs.italic))) {
            rl = "{";
            if (ffc)
               rl += "font-family:" + ffc + ";";
            if (fsz)
               rl += "font-size:" + fsz + ";";
            if (fst & fs.bold)
               rl += "font-weight:bold;";
            if (fst & fs.italic)
               rl += "font-style:italic;";
            rl += "}";
         }
         if (this.setFontAttr(dlg, rq, et, fn, rl) && rl)
            cl = et.cla = et.cla || [];
         else
            cl = et.clr = et.clr || [];
         cl.push('eq-font');
         break;
      }

      case rqTag.raster: {
         var iv = rq.decodeUint(), dr = eq.DialogRaster;
         this.posRaster = !!(iv & dr.pos);
         this.sizeRaster = !!(iv & dr.size);
         break;
      }

      case rqTag.posSize: {
         var
            st = et.st = et.st || {},
            iv;
         switch (rq.decodeInt()) { // xauto
            case -1:
               iv = rq.decodeInt(); // xright
               if (this.posRaster)
                  st['right'] = dlg.cssWidth(iv);
               else
                  st['right'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // width
                  if (this.sizeRaster)
                     st['width'] = dlg.cssWidth(iv);
                  else
                     st['width'] = iv + 'px';
               }
               else if (this.defaultWidth)
                  st['width'] = dlg.cssWidth(1);
               else
                  st['width'] = null;
               st['left'] = null;
               break;
            case 0:
               iv = rq.decodeInt(); // xleft
               if (this.posRaster)
                  st['left'] = dlg.cssWidth(iv);
               else
                  st['left'] = iv ? iv + 'px' : 0;
               iv = rq.decodeInt(); // xright
               if (this.posRaster)
                  st['right'] = dlg.cssWidth(iv);
               else
                  st['right'] = iv ? iv + 'px' : 0;
               st['width'] = null;
               break;
            case 1:
               iv = rq.decodeInt(); // xleft
               if (this.posRaster)
                  st['left'] = dlg.cssWidth(iv);
               else
                  st['left'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // width
                  if (this.sizeRaster)
                     st['width'] = dlg.cssWidth(iv);
                  else
                     st['width'] = iv + 'px';
               }
               else if (this.defaultWidth)
                  st['width'] = dlg.cssWidth(1);
               else
                  st['width'] = null;
               st['right'] = null;
               break;
         }

         switch (rq.decodeInt()) { // yauto
            case -1:
               iv = rq.decodeInt(); // ybottom
               if (this.posRaster)
                  st['bottom'] = dlg.cssHeight(iv);
               else
                  st['bottom'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // height
                  if (this.sizeRaster)
                     st['height'] = dlg.cssHeight(iv);
                  else
                     st['height'] = iv + 'px';
                  st['min-height'] = null;
               }
               else if (this.defaultHeight) {
                  st['height'] = dlg.cssHeight(1);
                  st['min-height'] = null;
               }
               else {
                  st['height'] = null;
                  st['min-height'] = dlg.cssHeight(1);
               }
               st['top'] = null;
               break;
            case 0:
               iv = rq.decodeInt(); // ytop
               if (this.posRaster)
                  st['top'] = dlg.cssHeight(iv);
               else
                  st['top'] = iv ? iv + 'px' : 0;
               iv = rq.decodeInt(); // ybottom
               if (this.posRaster)
                  st['bottom'] = dlg.cssHeight(iv);
               else
                  st['bottom'] = iv ? iv + 'px' : 0;
               st['height'] = null;
               st['min-height'] = null;
               break;
            case 1:
               iv = rq.decodeInt(); // ytop
               if (this.posRaster)
                  st['top'] = dlg.cssHeight(iv);
               else
                  st['top'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // height
                  if (this.sizeRaster)
                     st['height'] = dlg.cssHeight(iv);
                  else
                     st['height'] = iv + 'px';
                  st['min-height'] = null;
               }
               else if (this.defaultHeight) {
                  st['height'] = dlg.cssHeight(1);
                  st['min-height'] = null;
               }
               else {
                  st['height'] = null;
                  st['min-height'] = dlg.cssHeight(1);
               }
               st['bottom'] = null;
               break;
         }

         break;
      }

      case rqTag.size: {
         var
            st = et.st = et.st || {},
            iv;
         if (iv = rq.decodeInt()) { // width
            if (this.sizeRaster)
               st['width'] = dlg.cssWidth(iv);
            else
               st['width'] = iv ? iv + 'px' : 0;
         }
         else if (this.defaultWidth)
            st['width'] = dlg.cssWidth(1);
         else
            st['width'] = null;
         if (iv = rq.decodeInt()) { // height
            if (this.sizeRaster)
               st['height'] = dlg.cssHeight(iv);
            else
               st['height'] = iv ? iv + 'px' : 0;
            st['min-height'] = null;
         }
         else if (this.defaultHeight) {
            st['height'] = dlg.cssHeight(1);
            st['min-height'] = null;
         }
         else {
            st['height'] = null;
            st['min-height'] = dlg.cssHeight(1);
         }
         break;
      }

      case rqTag.border: {
         var iv = rq.decodeUint();
         if (this.border) {
            var cl;
            if (iv)
               cl = et.cla = et.cla || [];
            else
               cl = et.clr = et.clr || [];
            cl.push('eq-border');
         }
         break;
      }

      case rqTag.rule: {
         var
            rule = rq.decodeInt(),
            cl;
         if (rule)
            this.rule = rule;
         else
            delete this.rule;
         this.setRuleAttr(dlg, rq, et, rule);
         if (rule === -1) {
            cl = et.cla = et.cla || [];
            this.focusable = false;
            // Invalidate tab order.
            dlg.tabOrderValid = false;
         }
         else {
            cl = et.clr = et.clr || [];
            if (Object.prototype.hasOwnProperty.call(this, 'focusable')) {
               if (!this.containerFocusable())
                  this.focusable = false;
               else
                  delete this.focusable;
               // Invalidate tab order.
               dlg.tabOrderValid = false;
            }
         }
         if (cl !== undefined)
            cl.push('eq-help');
         break;
      }

      case rqTag.tabOnEnter: {
         var cl;
         if (rq.decodeUint())
            cl = et.clr = et.clr || [];
         else
            cl = et.cla = et.cla || [];
         cl.push('eq-noenter');
         break;
      }

      case rqTag.visible: {
         var cl, st = et.st = et.st || {};
         if (rq.decodeUint()) {
            cl = et.clr = et.clr || [];
            st['display'] = null;
            delete this.visible;
         }
         else {
            cl = et.cla = et.cla || [];
            st['display'] = 'none';
            this.visible = false;
         }
         cl.push('eq-hidden');
         // Invalidate tab order.
         dlg.tabOrderValid = false;
         break;
      }

      case rqTag.sensitive: {
         var
            sensitive = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            cl;
         rt.push({ t : eq.RqTag.sensitive, v : sensitive });
         if (sensitive) {
            cl = et.clr = et.clr || [];
            delete this.sensitive;
         }
         else {
            cl = et.cla = et.cla || [];
            this.sensitive = false;
         }
         cl.push('eq-disabled');
         // Invalidate tab order.
         dlg.tabOrderValid = false;
         break;
      }

      case rqTag.inactive: {
         if (rq.decodeUint())
            this.inactive = true;
         else
            delete this.inactive;
         // Invalidate tab order.
         dlg.tabOrderValid = false;
         break;
      }

      case rqTag.tabStop: {
         var cl;
         if (rq.decodeUint())
            cl = et.clr = et.clr || [];
         else
            cl = et.cla = et.cla || [];
         cl.push('eq-notab');
         break;
      }

      case rqTag.bgColor: {
         var
            sv = rq.decodeString(),
            st = et.st = et.st || {};
         st['background-color'] = sv.length ? sv : null;
         break;
      }

      case rqTag.fgColor: {
         var
            sv = rq.decodeString(),
            st = et.st = et.st || {};
         st['color'] = sv.length ? sv : null;
         break;
      }

      case rqTag.focusColor:
         // Ignore.
         rq.decodeString();
         break;

      case rqTag.btnMask: {
         var
            ds = et.ds = et.ds || {},
            iv = rq.decodeUint(),
            mask = 0;
         if (iv & 1)    // Main button, usually left button.
            mask |= 1;  // -> MouseEvent.buttons bit 0
         if (iv & 2)    // Auxiliary button, usually wheel/middle button.
            mask |= 4;  // -> MouseEvent.buttons bit 2
         if (iv & 4)    // Secondary button, usually right button.
            mask |= 2;  // -> MouseEvent.buttons bit 1
         ds['mask'] = mask || null;
         break;
      }

      case rqTag.contextMenu:
         this.contextMenu = rq.decodeUint() ? true : undefined;
         break;
      case rqTag.clipboardMenu: {
         var menu = rq.decodeString();
         this.clipboardMenu = menu.length ? menu : undefined;
         break;
      }

      case rqTag.help:
         this.help = rq.decodeString();
         break;

      case rqTag.toolTip: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      default:
         throw new Error(this.constructor.name + ".setAttr(" + this.id +
            ") failed: tag " + tag);
   }
};

/*------------------------------------------------------------------------
   DlgControl.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   return false;
};

/*------------------------------------------------------------------------
   DlgControl.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.setRuleAttr = function(dlg, rq, et, rule) {};

/*------------------------------------------------------------------------
   DlgControl.findById()
   Find control by id.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.findById = function(id) {
   return id === this.id ? this : null;
};

/*------------------------------------------------------------------------
   DlgControl.containerFocusable()
   Check 'focusable' based on container.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.containerFocusable = function() {
   if (this.parent)
      return this.parent.containerFocusable();
   return true;
};

/*------------------------------------------------------------------------
   DlgControl.updateTabOrder()
   Fill current tab order array.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.updateTabOrder = function(tabOrder) {
   if (this.visible && this.sensitive && !this.inactive && this.focusable)
      tabOrder.push(this.id);
};

/*------------------------------------------------------------------------
   DlgControl.onHelp()
   Act upon main thread help request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.onHelp = function() {
   var
      ct = this,
      url = "",
      hbu = eq.api.helpBaseUrl,
      isa = eq.c.Api.reIsAbsoluteUrl,
      h;
   while (ct) {
      if (h = ct.help) {
         url = h + url;
         if (isa.test(url))
            return url;
      }
      ct = ct.parent;
   }
   return url.length && hbu !== null && !isa.test(url)
        ? hbu + url : url;
};

/*------------------------------------------------------------------------
   DlgControl.onMouseButton2()
   Act upon main thread mouseButton2 request.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.onMouseButton2 = function(dlg, x, y, cmc) {
   var ct = this;
   while (ct) {
      if (cmc > 0 || ct.contextMenu || ct.ruleButton2) {
         postMessage([{
            o : eq.MainOp.onMouseButton2,
            d : dlg.id,
            i : this.id,
            x : x,
            y : y,
            c : cmc
         }]);
         return;
      }
      ct = ct.parent;
   }
};

/*------------------------------------------------------------------------
   DlgControl.onUpdate()
   Act upon main thread requesting update.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.onUpdate = function(dlg, tag, arg) {
   var ut = eq.UpdateTag;
   switch (tag) {
      case ut.changed: {
         var
            api = eq.api,
            rs = api.dlgRs,
            changed = arg.c;
         try {
            rs.encodeUint(eq.c.DlgRs.op.update);

            // Update request: Changed elements.
            rs.encodeUint(dlg.id);
            rs.encodeString(''); // No specific element.
            rs.encodeUint(tag);

            // Changed elements.
            eq.c.Api.packElements(dlg, rs, arg.c);

            rs.encodeUint(0); // End of message.
            api.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (api.ws !== undefined)
               throw e;
         }
         break;
      }

      case ut.mouseButton2: {
         var ct = this, cmc = arg.c;
         while (ct) {
            if (cmc > 0 || ct.contextMenu) {
               var
                  api = eq.api,
                  rs = api.dlgRs;
               try {
                  rs.encodeUint(eq.c.DlgRs.op.update);

                  // Update request: Open context menu.
                  rs.encodeUint(dlg.id);
                  rs.encodeString(ct.id);
                  rs.encodeUint(eq.UpdateTag.ctxMenuOpen);

                  // Requesting control, pointer coordinates, child index.
                  rs.encodeString(this.id);
                  rs.encodeInt(arg.x);
                  rs.encodeInt(arg.y);
                  rs.encodeUint(cmc > 0 ? cmc : 0);

                  rs.encodeUint(0); // End of message.
                  api.ws.send(rs.buffer());
               }
               catch (e) {
                  rs.dispose();
                  if (api.ws !== undefined)
                     throw e;
               }
               return true;
            }
            if (ct.ruleButton2) {
               arg.rule = ct.ruleButton2;
               return this.notifyUpdated(dlg, tag, arg);
            }
            ct = ct.parent;
         }
         break;
      }

      case ut.ctxMenuAct: {
         var
            id = arg.i,
            api, rs;
         if (id !== undefined) {
            api = eq.api;
            rs = api.dlgRs;
            try {
               rs.encodeUint(eq.c.DlgRs.op.update);

               // Update request: Update context menu item active state.
               rs.encodeUint(0);    // No specific dialog.
               rs.encodeString(''); // No specific element.
               rs.encodeUint(tag);

               // Original menu item id.
               rs.encodeString(id);

               rs.encodeUint(0); // End of message.
               api.ws.send(rs.buffer());
            }
            catch (e) {
               rs.dispose();
               if (api.ws !== undefined)
                  throw e;
            }
         }
         break;
      }

      default:
         throw new Error(this.constructor.name + ".onUpdate(" + this.id +
            ") failed: tag " + tag);
   }

   return true;
};

/*------------------------------------------------------------------------
   DlgControl.updated()
   Process update response.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.updated = function(dlg, tag, rq, rs, data) {
   switch (tag) {
      case eq.UpdateTag.ctxMenuOpen:
         this.notifyUpdated(dlg, tag, {
            id : rq.decodeString(), // Requesting control.
            x  : rq.decodeInt(),    // X pointer coordinate.
            y  : rq.decodeInt(),    // Y pointer coordinate.
            c  : rq.decodeUint(),   // Child index.
            et : dlg.addChild(rq)   // Context menu element template.
         });
         break;

      default:
         throw new Error(this.constructor.name + ".updated(" + this.id +
            ") failed: tag " + tag);
   }
};

/*------------------------------------------------------------------------
   DlgControl.notifyUpdated()
   Notify main thread when updated.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.notifyUpdated = function(dlg, tag, arg) {
   postMessage([{
      o : eq.MainOp.updated,
      d : dlg.id,
      i : this.id,
      t : tag,
      a : arg
   }]);
   return true;
};

/*------------------------------------------------------------------------
   DlgControl.getClipboardMenu()
   Obtain clipboardMenu assigned to control or parent.
------------------------------------------------------------------------*/

eq.c.DlgControl.prototype.getClipboardMenu = function() {
   var ct = this, menu;
   while (ct) {
      if ((menu = ct.clipboardMenu) !== undefined)
         return menu;
      ct = ct.parent;
   }
   return eq.api.clipboardMenu;
};

/*========================================================================
   class DlgContainer extends DlgControl
   DLG container base class.
========================================================================*/

eq.c.DlgControl.subClass('DlgContainer', function() {
   eq.c.DlgControl.call(this);

   this.ch = []; // Children
});

eq.c.DlgContainer.prototype.focusable = false;

/*------------------------------------------------------------------------
   static DlgContainer.addClass()
   Subclassing convenience helper: Add DLG container class.
------------------------------------------------------------------------*/

eq.c.DlgContainer.addClass = function(name, cls) {
   return eq.c.DlgControl.addClass.call(this, name, cls);
};

/*------------------------------------------------------------------------
   DlgContainer.addChild()
   Add child control.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.addChild = function(child) {
   this.ch.push(child);
   child.parent = this;
};

/*------------------------------------------------------------------------
   DlgContainer.childDeleted()
   Child control deleted.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.childDeleted = function(child) {
   var i = this.ch.indexOf(child);
   if (i === -1)
      throw new Error(this.constructor.name + ".childDeleted(" +
         this.id + ", " + child.id + ") failed: not found");

   this.ch.splice(i, 1);
};

/*------------------------------------------------------------------------
   DlgContainer.del()
   Delete container.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.del = function(level) {
   if (level === undefined)
      level = 0;

   for (var i = this.ch.length; --i >= 0;)
      this.ch[i].del(level + 1);
   this.ch.length = 0;

   return eq.c.DlgControl.prototype.del.call(this, level);
};

/*------------------------------------------------------------------------
   DlgContainer.setup()
   Setup container from DLG request, create children if any.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.setup = function(dlg, rq, et) {
   var tag;
   while (tag = rq.decodeUint()) {
      if (tag === eq.RqTag.child) {
         var ch = et;
         while (ch.et !== undefined)
            ch = ch.et;
         dlg.addChild(rq, this, ch);
      }
      else
         this.setAttr(dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   DlgContainer.findById()
   Find control by id.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.findById = function(id) {
   if (id === this.id)
      return this;

   for (var i = 0, l = this.ch.length; i < l; i++) {
      var found = this.ch[i].findById(id);
      if (found)
         return found;
   }

   return null;
};

/*------------------------------------------------------------------------
   DlgContainer.updateTabOrder()
   Fill current tab order array.
------------------------------------------------------------------------*/

eq.c.DlgContainer.prototype.updateTabOrder = function(tabOrder) {
   if (this.visible && this.sensitive && !this.inactive) {
      if (this.focusable)
         tabOrder.push(this.id);
      for (var i = 0, l = this.ch.length; i < l; i++)
         this.ch[i].updateTabOrder(tabOrder);
   }
};

/** @preserve $Id: 05-const.js,v 29.10 2025/07/03 14:15:50 rhg Exp $
*//*======================================================================
   static MainOp
   API->Main message opcodes.
========================================================================*/

eq.MainOp = {
   apiThreadStarted   : 1,
   ssnNewOrResume     : 2,
   inject             : 3,
   message            : 4,
   login              : 5,
   applicationStopped : 6,
   applicationFailed  : 7,
   popupPos           : 8,
   warnLeavingPage    : 9,
   windowFeature      : 10,
   windowFeatures     : 11,
   grid               : 12,
   layout             : 13,
   notifyBusy         : 14,
   typeAhead          : 15,
   cancelCurrentCall  : 16,
   update             : 17,
   updated            : 18,
   synced             : 19,
   openUrl            : 20,
   beep               : 21,
   playSound          : 22,
   startApp           : 23,
   activate           : 24,
   clipboard          : 25,
   resume             : 26,
   addControl         : 27,
   modifyControl      : 28,
   deleteControl      : 29,
   invalidate         : 30,
   onMouseButton2     : 31,
   onClipboardMenu    : 32,
   dlg_DRAW           : 33,
   dlg_DO             : 34,
   dlg_POPUP_BOX      : 35
};

/*========================================================================
   static ApiOp
   Main->API message opcodes.
========================================================================*/

eq.ApiOp = {
   documentComplete : 1,
   installPlugins   : 2,
   injectDone       : 3,
   openWebSocket    : 4,
   closeWebSocket   : 5,
   onLoginEvent     : 6,
   onEvent          : 7,
   onPopupEvent     : 8,
   onSynced         : 9,
   onDo             : 10,
   onHelp           : 11,
   onMouseButton2   : 12,
   invalidate       : 13,
   update           : 14,
   updated          : 15,
   canceled         : 16,
   grid             : 17,
   logError         : 18
};

/*========================================================================
   static UpdateTag
   Main->API update request/response tags.
========================================================================*/

eq.UpdateTag = {
   changed      : 1,  // Changed elements
   dialogDo     : 2,  // Dialog.do triggered by user
   mouseButton2 : 3,  // Secondary mouse button pressed
   ctxMenuOpen  : 4,  // Open context menu
   ctxMenuAct   : 5,  // Update context menu item active state
   tabOrder     : 6,  // Update tab order (TabBox tab switched)
   colResized   : 7,  // ListBox column resized
   colMoved     : 8,  // ListBox column moved
   sortSequence : 9,  // Listbox sort sequence modified
   lineOrder    : 10, // Update ListBox line order (server request)
   plgUpdate    : 11, // Update Plugin (server request)
   plgGetVal    : 12, // Get Plugin attribute value (server request)
   screenSize   : 13, // Get screen size (server request)
   clipboard    : 14  // Get clipboard text (server request)
};

/*========================================================================
   static DlgRq.tag
   DLG request tags.
========================================================================*/

eq.RqTag  = {
   child : 1, // Child control

   // Common attributes.

   cssid         : 2,  // CSS id
   cssclsDel     : 3,  // Delete CSS class(es)
   cssclsAdd     : 4,  // Add CSS class(es)
   font          : 5,  // Font face/size/style
   raster        : 6,  // Raster
   posSize       : 7,  // Position/size
   size          : 8,  // Size, ToolBar children
   border        : 9,  // Border
   rule          : 10, // Rule
   ruleMode      : 11, // When to submit rule (Tree/ListBox.singleclick)
   tabOnEnter    : 12, // Enter key navigates focus
   visible       : 13, // Visibility
   sensitive     : 14, // Sensitivity
   inactive      : 15, // Inactive, eg. TabBox tab
   tabStop       : 16, // Tab stop
   bgColor       : 17, // Background color
   fgColor       : 18, // Foreground color
   focusColor    : 19, // Focus color
   btnMask       : 20, // Mouse button mask
   contextMenu   : 21, // Context menu
   clipboardMenu : 22, // Clipboard menu
   url           : 23, // Url
   help          : 24, // Help url
   toolTip       : 25, // Tool tip

   // Drag&drop attributes.

   dndMode        : 27, // Drag&drop mode flags
   dropRule       : 28, // Local drop rule
   globalDropRule : 29, // Global drop rule

   // Dialog attributes.

   dialogState   : 30, // Resizepolicy, maximized
   dialogPosSize : 31, // Dialog position/size
   logoIcon      : 32, // Logo/icon
   fKey          : 33, // Function keys
   dialogTimer   : 34, // Dialog timer rule, delay
   ruleButton2   : 35, // Secondary mouse button rule
   dialogDo      : 36, // Dialog.do

   // Dialog, GroupBox attributes.

   margin  : 37, // Dialog/GroupBox margin
   bgImage : 38, // Dialog/GroupBox background image and fill mode
   vx      : 39, // X scroll position
   vy      : 40, // Y scroll position
   vWidth  : 41, // Visible width
   vHeight : 42, // Visible height

   // GroupBox, MenuItem attributes

   accel : 43, // Accelerator

   // Button, Menu, StaticText attributes.

   text       : 45, // Text, also Dialog/GroupBox title
   textPos    : 46, // Text position
   align      : 47, // Align
   active     : 48, // Active
   radioGroup : 49, // RadioButton group
   icon       : 50, // PushButton icon
   iconActive : 51, // PushButton icon when active
   type       : 52, // MenuItem type
   itemId     : 53, // Context menu, original MenuItem id
   objId      : 54, // Context menu, requesting object id
   scrollable : 55, // Context menu, scrollable

   // EditText, ComboBox attributes.

   multiline    : 56, // Multiline
   readonly     : 57, // Read-only
   wrapmode     : 58, // Wrap mode
   maxchars     : 59, // Maxchars
   content      : 60, // Content
   ispassword   : 61, // IsPassword
   fitfont      : 62, // Fitfont width/height
   autocomplete : 63, // tcrule, tcdelay, tcminchars
   autotab      : 64, // Autotab

   // Dialog, ListBox, PopText, Tree attributes.

   vcRule      : 65, // Rule: view has changed
   columns     : 66, // Columns
   colOrder    : 67, // Column order
   colState    : 68, // Column states
   colTitle    : 69, // Column titles
   lineOrder   : 70, // Line order
   activeLine  : 71, // Active line, model order
   vActiveLine : 72, // Active line, view order
   topItem     : 73, // First visible line
   rowHeight   : 74, // Row height
   status      : 75, // Tree status
   bgColor2    : 76, // Alternate background color
   fgColor2    : 77, // Alternate foreground color
   gridColor   : 78, // Grid color
   colToolTip  : 79, // Column tool tips
   lineToolTip : 80, // Line tool tips
   emptyNode   : 81, // Tree empty node rule/auto
   separator   : 82, // ListBox separator (client-only)

   // TabBox attributes.

   tabs        : 83, // Tabs
   selectedTab : 84, // Selected tab

   // Splitter, ProgressBar attributes.

   panes       : 85, // Setup splitter panes
   vertical    : 86, // Split vertical
   position    : 87, // Divider position
   quickExpand : 88, // Quick-expand controls
   value       : 89, // ProgressBar value, empty=indeterminate

   // ComboBox attributes.

   items   : 94, // List items
   boxSize : 95, // Box width/height
   open    : 96, // Auto-open when focused

   // Image attributes.

   scaling : 97, // Image scaling

   // Plugin attributes.

   plgInt    : 98,  // INT attribute value
   plgString : 99,  // STRING attribute value
   plgBinary : 100, // BINARY attribute value

   // Global properties

   popupPos      : 101, // Popup window position
   baseUrl       : 102, // Base url
   helpBaseUrl   : 103, // Help base url
   windowFeature : 104, // Window feature
   layout        : 105, // Dialog layout mode
   notifyBusy    : 106, // Beep-notification when busy
   typeAhead     : 107, // Type-ahead enabled/disabled
   openUrl       : 108, // Open url
   openHelpUrl   : 109, // Open help url
   startApp      : 110, // Start application
   activate      : 111, // Activate window
   beep          : 112, // System.beep
   soundUrl      : 113, // System.sound url
   clipboard     : 114  // Clipboard
};

/*========================================================================
   static RsValType
   Response changed element value type.
========================================================================*/

eq.RsValType = {
   idFocus       : 1,  // Focus object id, STRING
   ruleOnce      : 2,  // Submit this rule once, INT
   textValue     : 3,  // Text value, STRING
   buttonChecked : 4,  // Button checked, UINT
   caretPos      : 5,  // Caret position, UINT
   activeLine    : 6,  // Active line, UINT
   activeLineCol : 7,  // Active line and column, UINT line/col
   topItem       : 8,  // First visible line, UINT
   selectedTab   : 9,  // Selected tab, UINT
   splitterPos   : 10, // Splitter position, UINT
   scrollPos     : 11, // GroupBox scroll position, UINT x/y
   status        : 12, // ListBox/Tree status, ARRAY
   colOrder      : 13, // ListBox column order, ARRAY
   colWidth      : 14, // ListBox column widths, ARRAY
   lineOrder     : 15, // ListBox line order, ARRAY
   emptyNode     : 16, // Tree emptynode index, UINT
   ruleKey       : 17, // Rule submission key code, UINT
   mouseBtns     : 18, //                 mouse buttons, UINT
   mouseCnt      : 19, //                 mouse click count, UINT
   mouseMods     : 20, //                 mouse modifier keys, UINT
   dialogMax     : 21, // Dialog maximized, UINT
   dialogPos     : 22, // Dialog position, INT x/y
   dialogSize    : 23, // Dialog size, INT w/h
   screenSize    : 24, // Screen size, INT w/h
   dragFrom      : 25, // Drag origin object id, STRING
   dragContent   : 26, // Drag content, STRING
   dropContent   : 27, // Drop content, STRING
   dropAction    : 28, // Drop action, UINT
   dropPos       : 29, // Drop position, UINT
   dropColumn    : 30, // Drop column, INT
   dropLine      : 31, // Drop line, INT
   dropElement   : 32, // HtmlView drop element, STRING
   plgVoid       : 33, // Plugin VOID attribute value
   plgInt        : 34, // Plugin INT attribute value
   plgString     : 35, // Plugin STRING attribute value
   plgBinary     : 36  // Plugin BINARY attribute value
};

/*========================================================================
   static PopupPos
   Popup window position constants.
========================================================================*/

eq.PopupPos = {
   top     : 1,
   left    : 2,
   login   : 4,
   message : 8,
   box     : 16
};

/*========================================================================
   static DlgCall
   DLG call constants.
========================================================================*/

eq.DlgCall = {
   set : 6 // DLG SET
};

/*========================================================================
   static DlgValType
   DLG value type constants.
========================================================================*/

eq.DlgValType = {
   tInt    : 1,
   tString : 2
};

/*========================================================================
   static LayoutMode
   Layout mode constants.
========================================================================*/

eq.LayoutMode = {
   inline    : 1,
   maximized : 2,
   dialog    : 4
};

/*========================================================================
   static DialogMode, DialogState, DialogRaster
   Dialog constants.
========================================================================*/

eq.DialogMode = {
   none       : 0,
   layout     : 1,
   scale      : 2,
   inline     : 3,
   modeMask   : 3, // Mask out mode bits
   stateShift : 2  // Shift down state bits
};

eq.DialogState = {
   maximized : 1
};

eq.DialogRaster = {
   pos  : 1,
   size : 2
};

/*========================================================================
   static Margin
   Dialog/GroupBox margin constants.
========================================================================*/

eq.Margin = {
   top    : 1,
   right  : 2,
   bottom : 4,
   left   : 8
};

/*========================================================================
   static BgFill
   Dialog/GroupBox background image fill mode constants.
========================================================================*/

eq.BgFill = {
   tile    : 0,
   topLeft : 1,
   center  : 2,
   scale   : 3
};

/*========================================================================
   static FontStyle
   Font style constants.
========================================================================*/

eq.FontStyle = {
   bold   : 1,
   italic : 2
};

/*========================================================================
   static TextPos
   Text position constants.
========================================================================*/

eq.TextPos = {
   right  : 1,
   left   : 2,
   bottom : 3,
   top    : 4
};

/*========================================================================
   static ColState
   ListBox column state constants.
========================================================================*/

eq.ColState = {
   alignMask : 3,   // Mask out align bits
   visible   : 4,   // Column is visible
   raster    : 8,   // If set: column width *= sizeRaster
   movable   : 16,  // Column can be moved
   sizable   : 32,  // Column can be resized
   sortable  : 64,  // Column is sortable
   userHide  : 128  // User can show/hide the column (column context menu)
};

/*========================================================================
   static ColType
   ListBox column type constants.
========================================================================*/

eq.ColType = {
   colAlphaNofold : 0,
   colNumeric     : 1,
   colBoolean     : 2,
   colBarGraph    : 3,
   colAlphaFold   : 5,
   colDate        : 6
};

/*========================================================================
   static MenuType
   Menu type, active constants.
========================================================================*/

eq.MenuType = {
   item  : 0,
   sep   : 1,
   radio : 2,
   check : 3
};

/*========================================================================
   static TabState
   TabBox tab state constants.
========================================================================*/

eq.TabState = {
   visible   : 1,
   sensitive : 2
};

/*========================================================================
   static RuleType
   CSS rule type constants.
========================================================================*/

eq.RuleType = {
   bgColor    : 1,
   bgColor2   : 2,
   bgImage    : 3,
   childFont  : 4,
   fgColor    : 5,
   fgColor2   : 6,
   focusColor : 7,
   font       : 8,
   gridColor  : 9,
   height     : 10,
   width      : 11
};

/*========================================================================
   static Plugin attribute descriptor flags.
========================================================================*/

eq.atrGET = 1;   // DLG GET attribute
eq.atrSET = 2;   // DLG SET attribute
eq.atrIDX = 4;   // May be indexed
eq.atrINT = 8;   // Data type: INTEGER
eq.atrSTR = 16;  // Data type: STRING
eq.atrBIN = 32;  // Data type: BINARY
eq.atrPRI = 64;  // Has priority
eq.atrNOC = 128; // Not cached
eq.atrLST = 256; // Stored as list
eq.atrSYN = 512; // Synchronize immediately

/*========================================================================
   static PluginFlags
   Plugin class flags.
========================================================================*/

eq.PluginFlags = {
   focusable  : 4,
   tabOnEnter : 8
};

/*========================================================================
   static KeyMod
   Key modifier constants.
========================================================================*/

eq.KeyMod = {
   shift : 1,
   ctrl  : 2,
   alt   : 4,
   meta  : 8
};

/*========================================================================
   static DnD
   Drag&drop mode flags.
========================================================================*/

eq.Dnd = {
   local     : 1,
   global    : 2,
   move      : 4,
   copy      : 8,
   link      : 16,
   all       : 28, // move+copy+link
   dropExact : 32
};

/** @preserve $Id: 10-api-checkbox.js,v 29.2 2025/01/28 12:13:35 rhg Exp $
*//*======================================================================
   final class CheckBox extends DlgControl
   DLG CheckBox class.
========================================================================*/

eq.c.DlgControl.addClass('CheckBox', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.CheckBox.subClass; // final

/*------------------------------------------------------------------------
   CheckBox.setup()
   Setup CheckBox from DLG request.
------------------------------------------------------------------------*/

eq.c.CheckBox.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-checkbox', 'eq-control' ];
   et.et = {
      ch : [
         { id : '*' },
         { id : '*' }
      ]
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   CheckBox.setAttr()
   Set CheckBox attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.CheckBox.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.fgColor:
      case rqTag.focusColor:
         break;
      case rqTag.textPos:
      case rqTag.active:
      case rqTag.text:
         if (eq.c.DlgControl.isSetup(rq, et, 2))
            et.et.ch = et.et.ch || [{},{}];
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.textPos: {
         // Button index, 0 = label right, 1 = label left.
         this.iBtn = rq.decodeUint();
         // Button.
         var ch = et.et.ch[this.iBtn];
         ch.tg = 'INPUT';
         ch.at = { 'type' : 'checkbox' };
         ch.ev = {
            c : [ 'click' ]
         };
         // Label.
         ch = et.et.ch[this.iBtn ? 0 : 1];
         ch.tg = 'LABEL';
         ch.at = { 'for' : et.id + '-' + (this.iBtn + 1) };
         ch.cla = [ 'eq-fn' ];
         break;
      }

      case rqTag.active: {
         var ch = et.et.ch[this.iBtn];
         ch.at = ch.at || {};
         ch.at.checked = rq.decodeUint() !== 0;
         break;
      }

      case rqTag.text: {
         var ch = et.et.ch[this.iBtn ? 0 : 1];
         ch.hca = rq.decodeString();
         break;
      }

      case rqTag.fgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (sv.length)
            r.rl = ">*>label{color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (sv.length)
            r.rl = ".eq-focused>*>label{color:" + sv + "}";
         cr.push(r);
         break;
      }
   }
};

/*------------------------------------------------------------------------
   CheckBox.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.CheckBox.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-checkbox>*>label.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   CheckBox.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.CheckBox.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   if (eq.c.DlgControl.isSetup(rq, et, 2))
      et.et.ch = et.et.ch || [{},{}];
   var ch = et.et.ch[this.iBtn], ev = ch.ev = ch.ev || {};
   // Element is focusable and can submit,
   // use 'mousedown', not 'click'.
   ev.a = rule ? [ 'mousedown' ] : null;
};

/** @preserve $Id: 10-api-combobox.js,v 29.6 2025/07/03 14:14:54 rhg Exp $
*//*======================================================================
   final class ComboBox extends DlgControl
   DLG ComboBox class.
========================================================================*/

eq.c.DlgControl.addClass('ComboBox', function() {
   eq.c.DlgControl.call(this);

   // Default height.
   this.defaultHeight = 8;
});

delete eq.c.ComboBox.subClass; // final

/*------------------------------------------------------------------------
   ComboBox.setup()
   Setup ComboBox from DLG request.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.setup = function(dlg, rq, et) {
   var h = this.defaultHeight;
   this.defaultPxHeight = dlg.pxHeight(h);
   this.defaultCssHeight = dlg.cssHeight(h);

   et.cla = [ 'eq-combobox', 'eq-control', 'eq-border', 'eq-dn' ];
   et.et = {
      tg  : 'INPUT',
      at  : {
         'type' : 'text',
         'autocomplete' : 'off',
         'autocorrect' : 'off',
         'spellcheck' : false
      },
      cla : [ 'eq-fn' ],
      ev  : {
         c : [ 'input', 'mousedown', 'dnd' ]
      }
   };

   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   ComboBox.setAttr()
   Set ComboBox attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.items:
      case rqTag.boxSize:
      case rqTag.open:
      case rqTag.bgColor:
      case rqTag.focusColor:
      case rqTag.autocomplete:
      case rqTag.dndMode:
      case rqTag.dropRule:
      case rqTag.globalDropRule:
         break;

      case rqTag.readonly:
      case rqTag.maxchars:
      case rqTag.content:
      case rqTag.align:
         eq.c.DlgControl.isSetup(rq, et, 2);
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.items: {
         var
            l = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            v = [];
         v.length = l;
         for (var i = 0; i < l; i++)
            v[i] = rq.decodeString();
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.boxSize: {
         var
            w = rq.decodeUint(),
            h = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            cw, ch, mh;
         if (this.sizeRaster) {
            if (w)
               cw = dlg.pxWidth(w) + 'px';
            if (h && h > this.defaultHeight)
               ch = dlg.cssHeight(h);
            else
               ch = this.defaultCssHeight;
         }
         else {
            if (w)
               cw = w + 'px';
            if (h && h > this.defaultPxHeight)
               ch = h + 'px';
            else
               ch = this.defaultCssHeight;
         }
         rt.push({ t : tag, w : cw, h : ch });
         break;
      }

      case rqTag.readonly: {
         var
            cl,
            at = et.et.at = et.et.at || {};
         if (rq.decodeUint()) {
            cl = et.cla = et.cla || [];
            at.readonly = true;
            this.focusable = false;
         }
         else {
            cl = et.clr = et.clr || [];
            at.readonly = null;
            delete this.focusable;
         }
         cl.push('eq-readonly');
         // Invalidate tab order.
         dlg.tabOrderValid = false;
         break;
      }

      case rqTag.maxchars: {
         var
            m = rq.decodeUint(),
            at = et.et.at = et.et.at || {};
         at.maxlength = m ? m : null;
         break;
      }

      case rqTag.content: {
         var
            sv = rq.decodeString(),
            rt = et.rt = et.rt || [];
         et.et.ec = sv;
         rt.push({ t : tag, l : sv.length });
         break;
      }

      case rqTag.align: {
         var st = et.et.st = et.et.st || {};
         switch (rq.decodeUint()) {
            case 0:
               st['text-align'] = null;
               break;
            case 1:
               st['text-align'] = 'center';
               break;
            case 2:
               st['text-align'] = 'right';
         }
         break;
      }

      case rqTag.open: {
         var cl;
         if (rq.decodeUint())
            cl = et.cla = et.cla || [];
         else
            cl = et.clr = et.clr || [];
         cl.push('eq-auto');
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (sv.length)
            r.rl = "{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (sv.length)
            r.rl = ".eq-focused{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.autocomplete: {
         var
            rule = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            v;
         if (rule) {
            v = {
               r : rule,
               d : rq.decodeUint(),
               m : rq.decodeUint()
            };
         }
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.dndMode: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeUint() });
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }
   }
};

/*------------------------------------------------------------------------
   ComboBox.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-combobox>input.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   ComboBox.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   eq.c.DlgControl.isSetup(rq, et, 2);
   var cl, ev = et.et.ev = et.et.ev || {};
   if (rule) {
      cl = et.cla = et.cla || [];
      ev.a = [ 'blur' ];
   }
   else {
      cl = et.clr = et.clr || [];
      ev.a = null;
   }
   cl.push('eq-rule');
};

/*------------------------------------------------------------------------
   ComboBox.containerFocusable()
   Check 'focusable' based on container.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.containerFocusable = function() {
   // ComboBox is focusable, container does not matter.
   return true;
};

/*------------------------------------------------------------------------
   ComboBox.onMouseButton2()
   Act upon main thread mouseButton2 request.
------------------------------------------------------------------------*/

eq.c.ComboBox.prototype.onMouseButton2 = function(dlg, x, y, cmc) {
   if (cmc > 0 || this.contextMenu) {
      eq.c.DlgControl.prototype.onMouseButton2.call(this, dlg, x, y, cmc);
      return;
   }
   postMessage([{
      o : eq.MainOp.onClipboardMenu,
      d : dlg.id,
      i : this.id,
      x : x,
      y : y,
      c : cmc,
      m : this.getClipboardMenu()
   }]);
};

/** @preserve $Id: 10-api-dialog.js,v 29.2 2024/07/17 11:08:20 rhg Exp $
*//*======================================================================
   final class Dialog extends DlgContainer
   DLG Dialog class.
========================================================================*/

eq.c.DlgContainer.addClass('Dialog', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.Dialog.subClass; // final

/*------------------------------------------------------------------------
   Dialog.setup()
   Setup Dialog from DLG request.
------------------------------------------------------------------------*/

eq.c.Dialog.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-dialog', 'eq-control', 'eq-border' ];
   et.ev  = { c : [ 'dnd' ] };
   et.et = {
      cla : [ 'eq-pane' ],
      et  : {
         cla : [ 'eq-view' ],
         et  : {
            cla : [ 'eq-container' ]
         }
      }
   };
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Dialog.setAttr()
   Set Dialog attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Dialog.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag, dm = eq.DialogMode;
   switch (tag) {
      case rqTag.dialogState: {
         var
            d = et.dlg = et.dlg || {},
            l = d.l = d.l || {},
            iv = rq.decodeUint(),
            cl;
         this.mode = l.mode = iv & dm.modeMask;
         this.state = l.state = iv >> dm.stateShift;
         break;
      }

      case rqTag.dialogPosSize: {
         eq.c.DlgControl.isSetup(rq, et, 3);
         var
            d = et.dlg = et.dlg || {},
            l = d.l = d.l || {},
            st = et.st = et.st || {},
            cst = et.et.et.st = et.et.et.st || {},
            iv, cl;

         if ((iv = rq.decodeInt()) === -1)   // xleft
            l.x = -1;
         else if (this.posRaster)
            l.x = dlg.cssWidth(iv);
         else
            l.x = iv ? iv + 'px' : 0;
         if ((iv = rq.decodeInt()) === -1)   // ytop
            l.y = -1;
         else if (this.posRaster)
            l.y = dlg.cssHeight(iv);
         else
            l.y = iv ? iv + 'px' : 0;
         if ((iv = rq.decodeInt()) > 0) {    // width
            if (this.sizeRaster)
               l.w = dlg.cssWidth(iv);
            else
               l.w = iv + 'px';
         }
         if ((iv = rq.decodeInt()) > 0) {    // height
            if (this.sizeRaster)
               l.h = dlg.cssHeight(iv);
            else
               l.h = iv + 'px';
         }

         st['bottom'] = null;
         st['right'] = null;
         cst['width'] = l.w || null;
         cst['height'] = l.h || null;

         if (this.mode !== dm.inline && (l.x || l.y || l.w || l.h)) {
            cl = et.clr = et.clr || [];
            cl.push('eq-inline');
            cl.push('eq-max');
         }
         else {
            // Inline.
            l.mode = dm.inline;
            cl = et.cla = et.cla || [];
            cl.push('eq-inline');
            if (!(this.state & eq.DialogState.maximized) && (l.w || l.h))
               cl = et.clr = et.clr || [];
            cl.push('eq-max');
         }
         break;
      }

      case rqTag.posSize: {
         eq.c.DlgControl.isSetup(rq, et, 3);
         var
            d = et.dlg = et.dlg || {},
            st = et.st = et.st || {},
            cst = et.et.et.st = et.et.et.st || {},
            cl = et.clr = et.clr || [],
            iv;

         d.l = null;
         cl.push('eq-inline');
         cl.push('eq-max');

         switch (rq.decodeInt()) { // xauto
            case -1:
               iv = rq.decodeInt(); // xright
               if (this.posRaster)
                  st['right'] = dlg.cssWidth(iv);
               else
                  st['right'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // width
                  if (this.sizeRaster)
                     cst['width'] = dlg.cssWidth(iv);
                  else
                     cst['width'] = iv + 'px';
               }
               else if (this.defaultWidth)
                  cst['width'] = dlg.cssWidth(1);
               st['left'] = null;
               break;
            case 0:
               iv = rq.decodeInt(); // xleft
               if (this.posRaster)
                  st['left'] = dlg.cssWidth(iv);
               else
                  st['left'] = iv ? iv + 'px' : 0;
               iv = rq.decodeInt(); // xright
               if (this.posRaster)
                  st['right'] = dlg.cssWidth(iv);
               else
                  st['right'] = iv ? iv + 'px' : 0;
               cst['width'] = null;
               break;
         }

         switch (rq.decodeInt()) { // yauto
            case -1:
               iv = rq.decodeInt(); // ybottom
               if (this.posRaster)
                  st['bottom'] = dlg.cssHeight(iv);
               else
                  st['bottom'] = iv ? iv + 'px' : 0;
               if (iv = rq.decodeInt()) { // height
                  if (this.sizeRaster)
                     cst['height'] = dlg.cssHeight(iv);
                  else
                     cst['height'] = iv + 'px';
               }
               else
                  cst['height'] = dlg.cssHeight(1);
               st['top'] = null;
               break;
            case 0:
               iv = rq.decodeInt(); // ytop
               if (this.posRaster)
                  st['top'] = dlg.cssHeight(iv);
               else
                  st['top'] = iv ? iv + 'px' : 0;
               iv = rq.decodeInt(); // ybottom
               if (this.posRaster)
                  st['bottom'] = dlg.cssHeight(iv);
               else
                  st['bottom'] = iv ? iv + 'px' : 0;
               cst['height'] = null;
               break;
         }

         break;
      }

      case rqTag.margin: {
         eq.c.DlgControl.isSetup(rq, et, 4);
         var
            iv = rq.decodeUint(),
            st = et.et.et.et.st = et.et.et.et.st || {},
            mc, m;
         if (iv) {
            mc = eq.Margin;
            if (iv & (mc.top | mc.bottom)) {
               m = dlg.cssHeight(.5);
               st['top'] = iv & mc.top ? m : null;
               st['bottom'] = iv & mc.bottom ? m : null;
            }
            else {
               st['top'] = null;
               st['bottom'] = null;
            }
            if (iv & (mc.right | mc.left)) {
               m = dlg.cssWidth(.5);
               st['right'] = iv & mc.right ? m : null;
               st['left'] = iv & mc.left ? m : null;
            }
            else {
               st['right'] = null;
               st['left'] = null;
            }
         }
         else {
            st['top'] = null;
            st['right'] = null;
            st['bottom'] = null;
            st['left'] = null;
         }
         break;
      }

      case rqTag.logoIcon: {
         var
            d = et.dlg = et.dlg || {},
            s = eq.api.makeUrl(rq.decodeString());
         d.ic = s.length ? s : null;
         break;
      }

      case rqTag.fKey: {
         var
            d = et.dlg = et.dlg || {},
            fkey = [],
            l = rq.decodeUint();
         fkey.length = l;
         for (var i = 0; i < l; i++)
            fkey[i] = rq.decodeInt();
         d.f = fkey;
         break;
      }

      case rqTag.dialogTimer: {
         var
            d = et.dlg = et.dlg || {},
            delay = rq.decodeUint();
         if (delay)
            d.tm = {
               delay : delay,
               rule : rq.decodeInt()
            };
         else
            d.tm = null;
         break;
      }

      case rqTag.ruleButton2:
         this.ruleButton2 = rq.decodeInt();
         break;

      case rqTag.vcRule: {
         var d = et.dlg = et.dlg || {};
         d.vc = rq.decodeInt();
         break;
      }

      case rqTag.dialogDo: {
         var d = et.dlg = et.dlg || {};
         if (rq.decodeUint()) {
            d.d = {
               hide : rq.decodeString(),
               show : rq.decodeString(),
               f    : rq.decodeString()
            };
            if (!dlg.tabOrderValid) {
               dlg.updateTabOrder();
               d.d.t = dlg.tabOrder;
            }
         }
         else
            d.d = null;
         break;
      }

      case rqTag.text: {
         var d = et.dlg = et.dlg || {};
         d.t = rq.decodeString();
         break;
      }

      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      case rqTag.bgImage: {
         var
            cr = et.cr = et.cr || [],
            r = { ty : eq.RuleType.bgImage },
            s = eq.api.makeUrl(rq.decodeString()),
            fill = rq.decodeUint(),
            bf, rl;
         if (s.length) {
            bf = eq.BgFill;
            switch (fill) {
               case bf.topLeft:
                  rl = "";
                  break;
               case bf.center:
                  rl = "background-position:center;";
                  break;
               case bf.scale:
                  rl = "background-size:cover;";
                  break;
               default: // tile
                  rl = "background-repeat:repeat;";
                  break;

            }
            r.rl = [
               ">.eq-pane>.eq-view{background-image:url('" +
               s + "');" + rl + "}"
            ];
         }
         cr.push(r);
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }
};

/*------------------------------------------------------------------------
   Dialog.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.Dialog.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var cl;
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/** @preserve $Id: 10-api-edittext.js,v 29.3 2025/07/03 14:14:54 rhg Exp $
*//*======================================================================
   final class EditText extends DlgControl
   DLG EditText class.
========================================================================*/

eq.c.DlgControl.addClass('EditText', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.EditText.subClass; // final

/*------------------------------------------------------------------------
   EditText.setup()
   Setup EditText from DLG request.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-edittext', 'eq-control' ];
   et.et = {
      cla : [ 'eq-fn' ],
      ev  : {
         c : [ 'input', 'blur', 'dnd' ]
      }
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   EditText.setAttr()
   Set EditText attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.bgColor:
      case rqTag.focusColor:
      case rqTag.fitfont:
      case rqTag.autocomplete:
      case rqTag.autotab:
      case rqTag.dndMode:
      case rqTag.dropRule:
      case rqTag.globalDropRule:
         break;

      case rqTag.multiline:
      case rqTag.readonly:
      case rqTag.wrapmode:
      case rqTag.maxchars:
      case rqTag.content:
      case rqTag.ispassword:
      case rqTag.align:
      case rqTag.fgColor:
         eq.c.DlgControl.isSetup(rq, et, 2);
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.multiline: {
         var
            tt = et.et,
            at = tt.at = tt.at || {},
            rt = et.rt = et.rt || [],
            cl;
         if (this.multiline = rq.decodeUint()) {
            tt.tg = 'TEXTAREA';
            cl = et.cla = et.cla || [];
            // Disable wrap (IE), auto-capitalization (WebKit non-standard).
            at.wrap = 'off'; // IE
            at.autocapitalize = 'none';
         }
         else {
            tt.tg = 'INPUT';
            cl = et.clr = et.clr || [];
            at.type = this.ispassword ? 'password' : 'text';
         }
         // Disable auto-completion, auto-correction, spell-checking.
         at.autocomplete = 'off';
         at.autocorrect = 'off';
         at.spellcheck = false;
         cl.push('eq-enter');
         rt.push({ t : tag });
         break;
      }

      case rqTag.readonly: {
         var
            cl,
            at = et.et.at = et.et.at || {};
         if (rq.decodeUint()) {
            cl = et.cla = et.cla || [];
            at.readonly = true;
            this.focusable = false;
         }
         else {
            cl = et.clr = et.clr || [];
            at.readonly = null;
            delete this.focusable;
         }
         cl.push('eq-readonly');
         cl.push('eq-md');
         // Invalidate tab order.
         dlg.tabOrderValid = false;
         break;
      }

      case rqTag.wrapmode: {
         var
            iv = rq.decodeUint(),
            md = iv & 3,
            tt = et.et,
            at = tt.at = tt.at || {},
            rt = et.rtd = et.rtd || [],
            cl;
         if (!this.multiline)
            throw new Error("EditText.setAttr(" + this.id +
               ") wrapmode:" + iv + " multi-line:false");
         rt.push({ t : tag, b : md == 2 });
         if (md) {
            // linewrap 1:soft 2:hard
            at.wrap = md == 2 ? 'hard' : 'soft';
            cl = et.cla = et.cla || [];
            cl.push('eq-wrap');
            if (iv & 4)
               cl.push('eq-char');
            else {
               cl = et.clr = et.clr || [];
               cl.push('eq-char');
            }
         }
         else {
            // linewrap 0:none
            at.wrap = 'off'; // IE
            cl = et.clr = et.clr || [];
            cl.push('eq-wrap');
            cl.push('eq-char');
         }
         break;
      }

      case rqTag.maxchars: {
         var
            m = rq.decodeUint(),
            at = et.et.at = et.et.at || {};
         at.maxlength = m ? m : null;
         break;
      }

      case rqTag.content: {
         var
            sv = rq.decodeString(),
            rt = et.rt = et.rt || [];
         et.et.ec = sv;
         rt.push({ t : tag, l : sv.length });
         break;
      }

      case rqTag.ispassword:
         this.ispassword = rq.decodeUint();
         if (!this.multiline) {
            var at = et.et.at = et.et.at || {};
            at.type = this.ispassword ? 'password' : 'text';
         }
         break;

      case rqTag.align: {
         var st = et.et.st = et.et.st || {};
         switch (rq.decodeUint()) {
            case 0:
               st['text-align'] = null;
               break;
            case 1:
               st['text-align'] = 'center';
               break;
            case 2:
               st['text-align'] = 'right';
         }
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (sv.length)
            r.rl = "{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (sv.length)
            r.rl = ".eq-focused{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.fgColor: {
         var
            sv = rq.decodeString(),
            st = et.et.st = et.et.st || {};
         st['color'] = sv.length ? sv : null;
         break;
      }

      case rqTag.fitfont: {
         var
            iv = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            ff;
         if (iv) {
            ff = {};
            if (iv & 1)
               ff.w = rq.decodeUint();
            if (iv & 2)
               ff.h = rq.decodeUint();
         }
         rt.push({ t : tag, ff : ff });
         break;
      }

      case rqTag.autocomplete: {
         var
            iv = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            v;
         if (iv) {
            v = {
               r : iv,
               d : rq.decodeUint(),
               m : rq.decodeUint()
            };
         }
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.autotab: {
         var
            iv = rq.decodeUint(),
            rt = et.rt = et.rt || [];
         if (this.multiline)
            throw new Error("EditText.setAttr(" + this.id +
               ") autotab:" + iv + " multi-line:true");
         rt.push({ t : tag, i : iv });
         break;
      }

      case rqTag.dndMode: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeUint() });
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }
   }
};

/*------------------------------------------------------------------------
   EditText.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-edittext>.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   EditText.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   eq.c.DlgControl.isSetup(rq, et, 2);
   var ev = et.et.ev = et.et.ev || {};
   ev.a = rule ? [ 'blur' ] : null;
};

/*------------------------------------------------------------------------
   EditText.containerFocusable()
   Check 'focusable' based on container.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.containerFocusable = function() {
   // EditText is focusable, container does not matter.
   return true;
};

/*------------------------------------------------------------------------
   EditText.onMouseButton2()
   Act upon main thread mouseButton2 request.
------------------------------------------------------------------------*/

eq.c.EditText.prototype.onMouseButton2 = function(dlg, x, y, cmc) {
   if (cmc > 0 || this.contextMenu) {
      eq.c.DlgControl.prototype.onMouseButton2.call(this, dlg, x, y, cmc);
      return;
   }
   postMessage([{
      o : eq.MainOp.onClipboardMenu,
      d : dlg.id,
      i : this.id,
      x : x,
      y : y,
      c : cmc,
      m : this.getClipboardMenu()
   }]);
};

/** @preserve $Id: 10-api-groupbox.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class GroupBox extends DlgContainer
   DLG GroupBox class.
========================================================================*/

eq.c.DlgContainer.addClass('GroupBox', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.GroupBox.subClass; // final

/*------------------------------------------------------------------------
   GroupBox.setup()
   Setup GroupBox from DLG request.
------------------------------------------------------------------------*/

eq.c.GroupBox.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-groupbox', 'eq-control', 'eq-border' ];
   et.et = {
      cla : [ 'eq-pane' ],
      ev  : { c : [ 'scroll' ] },
      et  : {
         cla : [ 'eq-view' ],
         et  : {
            cla : [ 'eq-container' ]
         }
      }
   };
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   GroupBox.setAttr()
   Set GroupBox attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.GroupBox.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.vx:
      case rqTag.vy:
      case rqTag.text:
      case rqTag.accel:
      case rqTag.bgColor:
      case rqTag.fgColor:
      case rqTag.bgImage:
      case rqTag.dropRule:
      case rqTag.globalDropRule:
         break;

      case rqTag.vWidth:
      case rqTag.vHeight:
         eq.c.DlgControl.isSetup(rq, et, 3);
         break;

      case rqTag.margin:
         eq.c.DlgControl.isSetup(rq, et, 4);
         break;

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.vx: {
         var
            iv = rq.decodeUint(),
            rt = et.rtd = et.rtd || [];
         rt.push({
            t : tag,
            i : this.sizeRaster ? dlg.pxWidth(iv) : iv
         });
         break;
      }

      case rqTag.vy: {
         var
            iv = rq.decodeUint(),
            rt = et.rtd = et.rtd || [];
         rt.push({
            t : tag,
            i : this.sizeRaster ? dlg.pxHeight(iv) : iv
         });
         break;
      }

      case rqTag.vWidth: {
         var
            iv = rq.decodeUint(),
            st = et.et.et.st = et.et.et.st || {},
            rt = et.rtd = et.rtd || [];
         if (iv) {
            if (this.sizeRaster)
               st['width'] = dlg.cssWidth(iv);
            else
               st['width'] = iv + 'px';
            this.vWidth = iv;
         }
         else {
            st['width'] = '';
            this.vWidth = undefined;
         }
         rt.push({ t : tag, s : !!(this.vWidth || this.vHeight) });
         break;
      }

      case rqTag.vHeight: {
         var
            iv = rq.decodeUint(),
            st = et.et.et.st = et.et.et.st || {},
            rt = et.rtd = et.rtd || [];
         if (iv) {
            if (this.sizeRaster)
               st['height'] = dlg.cssHeight(iv);
            else
               st['height'] = iv + 'px';
            this.vHeight = iv;
         }
         else {
            st['height'] = '';
            this.vHeight = undefined;
         }
         rt.push({ t : tag, s : !!(this.vWidth || this.vHeight) });
         break;
      }

      case rqTag.margin: {
         var
            iv = rq.decodeUint(),
            st = et.et.et.et.st = et.et.et.et.st || {},
            mc, m;
         if (iv) {
            mc = eq.Margin;
            if (iv & (mc.top | mc.bottom)) {
               m = dlg.cssHeight(.5);
               st['top'] = iv & mc.top ? m : null;
               st['bottom'] = iv & mc.bottom ? m : null;
            }
            else {
               st['top'] = null;
               st['bottom'] = null;
            }
            if (iv & (mc.right | mc.left)) {
               m = dlg.cssWidth(.5);
               st['right'] = iv & mc.right ? m : null;
               st['left'] = iv & mc.left ? m : null;
            }
            else {
               st['right'] = null;
               st['left'] = null;
            }
         }
         else {
            st['top'] = null;
            st['right'] = null;
            st['bottom'] = null;
            st['left'] = null;
         }
         break;
      }

      case rqTag.text: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.accel: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            r.rl = ">.eq-pane{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.fgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (s.length)
            r.rl = ">.eq-title{color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.bgImage: {
         var
            cr = et.cr = et.cr || [],
            r = { ty : eq.RuleType.bgImage },
            s = eq.api.makeUrl(rq.decodeString()),
            fill = rq.decodeUint(),
            bf, rl;
         if (s.length) {
            bf = eq.BgFill;
            switch (fill) {
               case bf.topLeft:
                  rl = "";
                  break;
               case bf.center:
                  rl = "background-position:center;";
                  break;
               case bf.scale:
                  rl = "background-size:cover;";
                  break;
               default: // tile
                  rl = "background-repeat:repeat;";
                  break;

            }
            r.rl = [
               ">.eq-pane>.eq-view{background-image:url('" +
               s + "');" + rl + "}"
            ];
         }
         cr.push(r);
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }
   }
};

/*------------------------------------------------------------------------
   GroupBox.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.GroupBox.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-groupbox>.eq-title>span.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   GroupBox.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.GroupBox.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var cl;
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/** @preserve $Id: 10-api-htmlview.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class HtmlView extends DlgControl
   DLG HtmlView class.
========================================================================*/

eq.c.DlgControl.addClass('HtmlView', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.HtmlView.subClass; // final

eq.c.HtmlView.prototype.focusable = false;

/*------------------------------------------------------------------------
   HtmlView.setup()
   Setup HtmlView from DLG request.
------------------------------------------------------------------------*/

eq.c.HtmlView.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-htmlview', 'eq-control', 'eq-border', 'eq-md' ];
   et.ev = { c : [ 'change', 'dnd' ] };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   HtmlView.setAttr()
   Set HtmlView attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.HtmlView.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.url: {
         var rt = et.rt = et.rt || [];
         rt.push({
            t : tag,
            v : eq.api.makeUrl(rq.decodeString())
         });
         break;
      }

      case rqTag.content: {
         var rt = et.rt = et.rt || [];
         rt.push({
            t : tag,
            ty : rq.decodeUint(),
            v : rq.decodeString()
         });
         break;
      }

      case rqTag.bgColor: {
         var
            s = rq.decodeString(),
            cr = et.cr = et.cr || [],
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            r.rl = ">textarea{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.fgColor: {
         var
            s = rq.decodeString(),
            cr = et.cr = et.cr || [],
            r = { ty : eq.RuleType.fgColor };
         if (s.length)
            r.rl = ">textarea{color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.dndMode: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeUint() });
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }
};

/*------------------------------------------------------------------------
   HtmlView.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.HtmlView.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-htmlview>textarea.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   HtmlView.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.HtmlView.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var cl;
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/** @preserve $Id: 10-api-image.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class Image extends DlgControl
   DLG Image class.
========================================================================*/

eq.c.DlgControl.addClass('Image', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.Image.subClass; // final

eq.c.Image.prototype.focusable = false;

/*------------------------------------------------------------------------
   Image.setup()
   Setup Image from DLG request.
------------------------------------------------------------------------*/

eq.c.Image.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-image', 'eq-control', 'eq-border' ];
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Image.setAttr()
   Set Image attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Image.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.url:
         var
            s = eq.api.makeUrl(rq.decodeString()),
            st = et.st = et.st || {};
         st['background-image'] = s.length ? 'url("' + s + '")' : 'none';
         break;

      case rqTag.scaling: {
         var
            sw = rq.decodeInt(),
            sh = rq.decodeInt(),
            st = et.st = et.st || {};
         if (sw > 0 && sh > 0) {
            st['background-position'] = 'top left';
            st['background-size'] = sw + 'px ' + sh + 'px';
         }
         else if (sw === -2 && sh === -2) {
            st['background-position'] = 'center';
            st['background-size'] = 'contain';
         }
         else if (sw === -1 && sh === -1) {
            st['background-position'] = 'top left';
            st['background-size'] = '100% 100%';
         }
         else {
            st['background-position'] = 'center';
            st['background-size'] = 'auto';
         }
         break;
      }

      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   Image.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.Image.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var ev = et.ev = et.ev || {};
   // Element is focusable and can submit,
   // use 'mousedown', not 'click'.
   ev.a = rule ? [ 'mousedown' ] : null;
};

/** @preserve $Id: 10-api-listbox.js,v 29.5 2024/09/09 13:33:34 rhg Exp $
*//*======================================================================
   final class ListBox extends DlgControl
   DLG ListBox class.
========================================================================*/

eq.c.DlgControl.addClass('ListBox', function() {
   eq.c.DlgControl.call(this);

   this.nCols = 0;         // Number of columns.
   this.colContent = [];   // Column content [colIndex][lineIndex].
   this.colVisible = [];   // Visible columns.
   this.colOrder = [];     // Column order, view->model.
   this.colState = [];     // Column states (eq.ColState).
   this.colType = [];      // Column types (eq.ColType).
   this.colWidth = [];     // Column widths (eq.ColState.raster).
   this.colTitle = [];     // Column titles.
   this.colFormat = [];    // Column formats.
   this.colFont = [];      // Column fonts.
   this.colBgColor = [];   // Column header background color.
   this.colFgColor = [];   // Column header foreground color.
   this.colToolTip = [];   // Column tool tips.
   this.lineSeq = [];      // Line sort sequence.
   this.lineOrder = [];    // Line order, view->model.
});

delete eq.c.ListBox.subClass; // final

eq.c.ListBox.prototype.focusable = true;

/*------------------------------------------------------------------------
   ListBox.setup()
   Setup ListBox from DLG request.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-listbox', 'eq-control', 'eq-border' ];
   et.ev = {
      // Element is focusable and can submit,
      // use 'mousedown', not 'click'.
      c : [ 'mousedown', 'scroll', 'resize', 'move', 'sort', 'dnd' ]
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   ListBox.setAttr()
   Set ListBox attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.columns: {
         var l = rq.decodeUint();
         this.nCols = l;
         this.colContent.length =
         this.colVisible.length =
         this.colOrder.length =
         this.colState.length =
         this.colType.length =
         this.colWidth.length =
         this.colToolTip.length =
         this.colFormat.length =
         this.colFont.length =
         this.colBgColor.length =
         this.colFgColor.length =
         this.colTitle.length =
         this.lineSeq.length =
         this.lineOrder.length = 0;
         if (!dlg.temp) {
            var rt = et.rt = et.rt || [];
            rt.push({ t : tag, l : l });
         }
         break;
      }

      case rqTag.colOrder: {
         var
            colVisible = this.colVisible,
            colOrder = this.colOrder,
            i = 0, l = this.nCols,
            vi, colMove, to, from;
         colOrder.length = l;
         // Convert model->view to view->model.
         while (i < l) {
            vi = rq.decodeUint();
            if (vi >= l)
               throw new Error("ListBox.setAttr(" + this.id +
                  ") invalid colOrder vi:" + vi + " i:" + i + " l:" + l);
            colOrder[vi] = i++;
         }
         if (!dlg.temp && colVisible.length) {
            // Rearrange visible columns by new column order.
            colMove = [];
            colMove.length = colVisible.length;
            for (i = to = 0; i < l; i++)
               if ((from = colVisible.indexOf(colOrder[i])) !== -1) {
                  colVisible.splice(to, 0, colVisible.splice(from, 1)[0]);
                  colMove[to++] = from;
               }
            var rt = et.rt = et.rt || [];
            rt.push({ t : tag, m : colMove });
         }
         break;
      }

      case rqTag.colState: {
         var
            cls = this.constructor,
            colState = this.colState,
            colType = this.colType,
            colWidth = this.colWidth,
            colFormat = this.colFormat,
            colFont = this.colFont,
            colBgColor = this.colBgColor,
            colFgColor = this.colFgColor,
            i = 0, l = this.nCols,
            cl = colState.length,
            api = eq.api,
            ct, cf, updCols, updContent = rq.decodeUint();
         colState.length =
         colType.length =
         colWidth.length =
         colFormat.length =
         colFont.length =
         colBgColor.length =
         colFgColor.length = l;
         while (i < l) {
            colState[i] = rq.decodeUint();
            ct = rq.decodeUint();
            if (i < cl && ct !== colType[i]) {
               // Column type modified.
               updCols = true;
            }
            colType[i] = ct;
            colWidth[i] = rq.decodeUint();
            cf = cls.getFormatMethod(ct, rq.decodeString());
            if (i < cl && cf !== colFormat[i]) {
               // Column format modified.
               updCols = true;
            }
            colFormat[i] = cf;
            colFont[i] = api.fixupFont({
               fc : rq.decodeString(),                  // Font face
               sz : api.fixupFontSize(rq.decodeUint()), //      size
               st : rq.decodeUint()                     //      style
            });
            colBgColor[i] = rq.decodeString();
            colFgColor[i++] = rq.decodeString();
         }
         // Compile and update visible columns.
         if (dlg.temp) {
            var
               colOrder = this.colOrder,
               colVisible = this.colVisible,
               cs = eq.ColState,
               ci, st;
            colVisible.length = 0;
            for (i = 0; i < l;)
               if ((st = colState[ci = colOrder[i++]]) & cs.visible)
                  colVisible.push(ci);
         }
         else
            this.updateColState(dlg, et.rt = et.rt || [], updCols, updContent);
         break;
      }

      case rqTag.colTitle: {
         var
            colTitle = this.colTitle,
            i = 0, l = this.nCols;
         colTitle.length = l;
         while (i < l)
            colTitle[i++] = rq.decodeString();
         if (!dlg.temp)
            this.updateColTitle(et.rt = et.rt || []);
         break;
      }

      case rqTag.lineOrder: {
         var
            cls = this.constructor,
            lineSeq = this.lineSeq,
            colType = this.colType,
            colContent = this.colContent,
            i = 0, l = rq.decodeUint(),
            updContent = rq.decodeUint(),
            ci, cd, cmp;
         lineSeq.length = l;
         while (i < l) {
            ci = rq.decodeUint();
            cd = rq.decodeUint();
            lineSeq[i++] = {
               i : ci,
               d : cd,
               c : cls.getCompareMethod(colType[ci])
            };
         }
         if (updContent && colContent.length) {
            var
               lineOrder = this.lineOrder,
               ol;
            if (l) {
               lineOrder.length = ol = colContent[0].length;
               for (i = 0; i < ol; i++)
                  lineOrder[i] = i;
               if (ol)
                  this.sort();
            }
            else
               lineOrder.length = 0;
            if (!dlg.temp) {
               var rt = et.rt = et.rt || [];
               this.updateLineOrder(rt);
               this.updateContent(rt);
            }
         }
         break;
      }

      case rqTag.colToolTip: {
         var
            colToolTip = this.colToolTip,
            rt = et.rt = et.rt || [],
            i = 0, l = rq.decodeUint();
         colToolTip.length = l;
         while (i < l)
            colToolTip[i++] = rq.decodeString();
         this.updateColToolTip(et.rt = et.rt || []);
         break;
      }

      case rqTag.content: {
         var
            rt = et.rt = et.rt || [],
            l = rq.decodeUint(),
            colContent = this.colContent,
            nCols = this.nCols;
         if (nCols === 0) {
            // Single-column ListBox.
            var i = 0, ln;
            colContent.length = 1;
            colContent[0] = ln = [];
            ln.length = l;
            while (i < l)
               ln[i++] = rq.decodeString();
            if (!dlg.temp)
               rt.push({ t : tag, c : colContent });
         }
         else {
            // Multi-column ListBox.
            var
               sep = rq.decodeString(),
               lineSeq = this.lineSeq,
               lineOrder = this.lineOrder,
               ol, i, c, s, ln, lnl;
            colContent.length = nCols;
            for (c = 0; c < nCols; c++) {
               colContent[c] = ln = [];
               ln.length = l;
            }
            lineOrder.length = ol = lineSeq.length ? l : 0;
            for (i = 0; i < l; i++) {
               s = rq.decodeString();
               lnl = (ln = s.split(sep)).length;
               for (c = 0; c < nCols; c++)
                  if (c < lnl)
                     colContent[c][i] = ln[c];
               if (ol)
                  lineOrder[i] = i;
            }
            if (ol)
               this.sort();
            if (!dlg.temp) {
               this.updateLineOrder(rt);
               this.updateContent(rt);
            }
         }
         break;
      }

      case rqTag.lineToolTip: {
         var
            rt = et.rt = et.rt || [],
            l = rq.decodeUint();
         if (l === 0)
            rt.push({ t : tag, ltt : null });
         else {
            var i, tt, ltt = [];
            ltt.length = l;
            for (i = 0; i < l; i++) {
               tt = rq.decodeString();
               ltt[i] = tt.length ? tt : null;
            }
            rt.push({ t : tag, ltt : ltt });
         }
         break;
      }

      case rqTag.activeLine:
      case rqTag.vActiveLine:
      case rqTag.topItem: {
         var i = rq.decodeUint();
         if (!dlg.temp) {
            var rt = et.rtd = et.rtd || [];
            rt.push({ t : tag, i : i });
         }
         break;
      }

      case rqTag.rowHeight: {
         var i = rq.decodeUint();
         if (!dlg.temp) {
            var
               h = i > 0 ? i + 'px' : dlg.cssHeight(1),
               cr = et.cr = et.cr || [];
            cr.push({
               ty : eq.RuleType.height,
               rl : " .eq-column>*{height:" + h + "}"
            });
         }
         break;
      }

      case rqTag.ruleMode:
         this.ruleMode = rq.decodeUint();
         break;

      case rqTag.vcRule: {
         var rule = rq.decodeInt();
         if (!dlg.temp) {
            var ds = et.ds = et.ds || {};
            ds['rule'] = rule ? rule : null;
         }
         break;
      }

      case rqTag.bgColor: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.bgColor };
            if (s.length)
               r.rl = [
                  ">.eq-caption>.eq-space>*,",
                  ">.eq-pane{background-color:" + s + "}"
               ];
            cr.push(r);
         }
         break;
      }

      case rqTag.focusColor: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.focusColor };
            if (s.length)
               r.rl = [
                  ".eq-focused>.eq-caption>.eq-space>*,",
                  ".eq-focused>.eq-pane{background-color:" + s + "}"
               ];
            cr.push(r);
         }
         break;
      }

      case rqTag.fgColor: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.fgColor };
            if (s.length)
               r.rl = ">.eq-pane{color:" + s + "}";
            cr.push(r);
         }
         break;
      }

      case rqTag.bgColor2: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.bgColor2 };
            if (s.length)
               r.rl = " .eq-column>:nth-child(2n){" +
                      "background-color:" + s + "}";
            cr.push(r);
         }
         break;
      }

      case rqTag.fgColor2: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.fgColor2 };
            if (s.length)
               r.rl = " .eq-column>:nth-child(2n){" +
                      "color:" + s + "}";
            cr.push(r);
         }
         break;
      }

      case rqTag.gridColor: {
         var s = rq.decodeString();
         if (!dlg.temp) {
            var
               cr = et.cr = et.cr || [],
               r = { ty : eq.RuleType.gridColor }
            if (s.length) {
               r.rl = [
                  ".eq-multi>.eq-pane>.eq-column>*,.eq-overlay>",
                  ".eq-multi>.eq-pane{border-color:" + s + "}"
               ];
            }
            cr.push(r);
         }
         break;
      }

      case rqTag.dndMode: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeUint() });
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   ListBox.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-listbox .eq-column>.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   ListBox.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   if (!dlg.temp) {
      var ev = et.ev = et.ev || {};
      if (rule) {
         // Element is focusable and can submit,
         // use 'mousedown', not 'click'.
         ev.a = [ 'mousedown', 'keydown' ];
         switch (this.ruleMode) {
            case 1:
               ev.m = 1;
               ev.f = null;
               break;
            case 2:
               ev.m = 2;
               ev.f = 1;
               break;
            case 3:
               ev.m = 1;
               ev.f = 1;
               break;
            default:
               ev.m = 2;
               ev.f = null;
         }
      }
      else {
         ev.a = null;
         ev.m = null;
         ev.f = null;
      }
   }
};

/*------------------------------------------------------------------------
   ListBox.onUpdate()
   Act upon main thread requesting update.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.onUpdate = function(dlg, tag, arg) {
   var ut = eq.UpdateTag, reply = null;
   switch (tag) {
      case ut.colResized: {
         var
            col = this.colVisible[arg.col], // view->model
            w = arg.width;
         this.colWidth[col] = this.colState[col] & eq.ColState.raster
                            ? dlg.gridWidth(w) : w;
         break;
      }

      case ut.colMoved: {
         var
            colVisible = this.colVisible,
            colOrder = this.colOrder,
            from = arg.from,
            orderFrom = colOrder.indexOf(colVisible[from]), // view->view
            to = arg.to,
            orderTo = colOrder.indexOf(colVisible[to]); // view->view
         colVisible.splice(to, 0, colVisible.splice(from, 1)[0]);
         colOrder.splice(orderTo, 0, colOrder.splice(orderFrom, 1)[0]);
         break;
      }

      case ut.sortSequence: {
         var
            col = this.colVisible[arg.col], // view->model
            lineSeq = this.lineSeq,
            lineOrder = this.lineOrder,
            li, rt = [];
         for (var i = 0, l = lineSeq.length; i < l; i++)
            if ((li = lineSeq[i]).i === col)
               break;
         if (i < l) {
            if (!arg.add) {
               lineSeq = this.lineSeq = [li];
               i = 0;
            }
            if (li.d)
               lineSeq.splice(i, 1);
            else
               li.d = 1;
         }
         else {
            var
               cls = this.constructor,
               cmp = cls.getCompareMethod(this.colType[col]);
            if (!arg.add)
               lineSeq.length = 0;
            lineSeq.push({ i : col, d : 0, c : cmp });
         }
         lineOrder.length = l = this.colContent[0].length;
         for (i = 0; i < l; i++)
            lineOrder[i] = i;
         if (l)
            this.sort();
         this.updateLineOrder(rt);
         this.updateContent(rt);
         reply = { rt : rt };
         break;
      }

      case ut.lineOrder: {
         // Server request.
         var
            rs = arg.rs,
            lineOrder = this.lineOrder,
            i = 0, l = lineOrder.length;
         rs.encodeUint(l);
         while (i < l)
            rs.encodeUint(lineOrder[i++]);
         // Don't notify main thread.
         return true;
      }

      case ut.ctxMenuAct: {
         var cmc = arg.c;
         if (cmc !== undefined) {
            var
               api = eq.api,
               rs = api.dlgRs,
               colState = this.colState,
               cs = eq.ColState,
               st, rt;
            try {
               rs.encodeUint(eq.c.DlgRs.op.update);

               // Update request: Update context menu item active state.
               rs.encodeUint(dlg.id);
               rs.encodeString(this.id);
               rs.encodeUint(tag);

               // Context menu child index.
               rs.encodeUint(cmc);

               rs.encodeUint(0); // End of message.
               api.ws.send(rs.buffer());
            }
            catch (e) {
               rs.dispose();
               if (api.ws !== undefined)
                  throw e;
            }
            for (var i = 0, l = this.nCols; i < l; i++) {
               if (((st = colState[i]) & cs.userHide) && cmc-- === 0) {
                  colState[i] ^= cs.visible;
                  this.updateColState(dlg, rt = [], true, true);
                  reply = { rt : rt };
                  break;
               }
            }
         }
         break;
      }

      case ut.mouseButton2: {
         var cmc = arg.c;
         if (cmc > 0) {
            // ListBoxHeader contxt menu, view->model.
            arg.c = this.colVisible[cmc - 1] + 1;
         }
         // FALLTHROUGH
      }

      default:
         return eq.c.DlgControl.prototype.onUpdate.call(this, dlg, tag, arg);
   }

   return this.notifyUpdated(dlg, tag, reply);
};

/*------------------------------------------------------------------------
   ListBox.packValue()
   Pack response changed element value.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.packValue = function(rs, ty, v) {
   var vt = eq.RsValType;
   switch (ty) {
      case vt.colOrder: {
         var
            colOrder = this.colOrder,
            mv = [],
            i, l = this.nCols;
         // Convert view->model to model->view.
         mv.length = l;
         for (i = 0; i < l; i++)
            mv[colOrder[i]] = i;
         for (i = 0; i < l;)
            rs.encodeUint(mv[i++]);
         break;
      }

      case vt.colWidth: {
         var
            colWidth = this.colWidth,
            i = 0, l = this.nCols;
         while (i < l)
            rs.encodeInt(colWidth[i++]);
         break;
      }

      case vt.lineOrder: {
         var
            lineSeq = this.lineSeq,
            lineOrder = this.lineOrder,
            l, i, li;
         rs.encodeUint(l = lineSeq.length);
         for (i = 0; i < l;) {
            li = lineSeq[i++];
            rs.encodeUint(li.i);
            rs.encodeUint(li.d);
         }
         rs.encodeUint(l = lineOrder.length);
         for (i = 0; i < l;)
            rs.encodeUint(lineOrder[i++]);
         break;
      }

      case vt.activeLineCol: {
         var line = v.i1, col = v.i2;
         if (line === undefined || col === undefined)
            throw new Error("ListBox.packValue: " + ty + " i1/i2 expected");
         rs.encodeUint(line);
         if (col > 0)
            col = this.colVisible[col - 1] + 1; // view->model
         rs.encodeUint(col);
         break;
      }

      case vt.dropColumn: {
         var col = v.iv;
         if (col === undefined)
            throw new Error("ListBox.packValue: " + ty + " iv expected");
         if (col >= 0)
            col = this.colVisible[col]; // view->model
         rs.encodeInt(col);
         break;
      }

      default:
         throw new Error("ListBox.packValue(" +
            this.id + ") failed: type " + ty);
   }
};

/*------------------------------------------------------------------------
   ListBox.updateColState()
   Update ListBox column state.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.updateColState = function(dlg, rt, updCols, updContent) {
   var
      colVisible = this.colVisible,
      colOrder = this.colOrder,
      colState = this.colState,
      colType = this.colType,
      colWidth = this.colWidth,
      colFont = this.colFont,
      colBgColor = this.colBgColor,
      colFgColor = this.colFgColor,
      nowVisible = [],
      colShow = [],
      userHide = false,
      cs = eq.ColState,
      i = 0, l = this.nCols,
      ci, st, w, fc, bc;
   while (i < l) {
      if ((st = colState[ci = colOrder[i++]]) & cs.visible) {
         nowVisible.push(ci);
         if ((w = colWidth[ci]) == 0)
            w = null; // Auto width.
         else if (st & cs.raster)
            w = dlg.cssWidth(w);
         else
            w += 'px';
         bc = colBgColor[ci];
         fc = colFgColor[ci];
         colShow.push({
            type     : colType[ci],
            align    : st & cs.alignMask,
            movable  : !!(st & cs.movable),
            sizable  : !!(st & cs.sizable),
            sortable : !!(st & cs.sortable),
            w        : w,
            fn       : colFont[ci],
            bc       : bc.length ? bc : null,
            fc       : fc.length ? fc : null
         });
         if (st & cs.userHide)
            userHide = true;
         if (colVisible.length && colVisible.indexOf(ci) === -1) {
            // Visible column added.
            updCols = true;
         }
      }
      else if (colVisible.indexOf(ci) !== -1)
         colShow.push(null);
   }
   this.colVisible = nowVisible;
   rt.push({ t : eq.RqTag.colState, s : colShow, uh : userHide });
   if (updCols) {
      this.updateColTitle(rt);
      this.updateColToolTip(rt);
      if (updContent) {
         this.updateLineOrder(rt);
         this.updateContent(rt);
      }
   }
};

/*------------------------------------------------------------------------
   ListBox.updateColTitle()
   Update ListBox column titles.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.updateColTitle = function(rt) {
   var
      colVisible = this.colVisible,
      colTitle = this.colTitle,
      l = colVisible.length,
      colShow = [];
   colShow.length = l;
   for (var i = 0, vi = 0; i < l; i++)
      colShow[vi++] = colTitle[colVisible[i]]; // view->model
   rt.push({ t : eq.RqTag.colTitle, ct : colShow });
};

/*------------------------------------------------------------------------
   ListBox.updateLineOrder()
   Update ListBox line order.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.updateLineOrder = function(rt) {
   var
      colVisible = this.colVisible,
      lineSeq = this.lineSeq,
      seq = [], li, ci;
   for (var i = 0, l = lineSeq.length; i < l; i++) {
      if ((ci = colVisible.indexOf((li = lineSeq[i]).i)) !== -1) // model->view
         seq.push({
            i : ci,
            d : li.d
         });
   }
   rt.push({ t : eq.RqTag.lineOrder, s : seq, o : this.lineOrder });
};

/*------------------------------------------------------------------------
   ListBox.updateColToolTip()
   Update ListBox column tool tips.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.updateColToolTip = function(rt) {
   var
      colToolTip = this.colToolTip,
      l = colToolTip.length;
   if (l === 0)
      rt.push({ t : eq.RqTag.colToolTip, ctt : null });
   else {
      var
         colVisible = this.colVisible,
         vl = colVisible.length,
         ctt = [],
         i = 0, vi, tt;
      ctt.length = vl;
      while (i < vl)
         ctt[i++] = null;
      for (i = 0; i < l; i++)
         if ((tt = colToolTip[i]).length)
            if ((vi = colVisible.indexOf(i)) !== -1) // model->view
               ctt[vi] = tt;
      rt.push({ t : eq.RqTag.colToolTip, ctt : ctt });
   }
};


/*------------------------------------------------------------------------
   ListBox.updateContent()
   Update ListBox content.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.updateContent = function(rt) {
   var
      cls = this.constructor,
      colVisible = this.colVisible,
      colFormat = this.colFormat,
      colContent = this.colContent,
      l = colVisible.length,
      colShow = [],
      i, vi, ci, cc, fm, nc, nl, ni;
   colShow.length = l;
   for (i = vi = 0; i < l; i++) {
      ci = colVisible[i]; // view->model
      if (!(cc = colContent[ci]) || !(fm = colFormat[ci]))
         colShow[vi++] = cc;
      else {
         nc = [];
         nc.length = nl = cc.length;
         for (ni = 0; ni < nl; ni++)
            nc[ni] = fm(cc[ni]);
         colShow[vi++] = nc;
      }
   }
   rt.push({ t : eq.RqTag.content, c : colShow });
};

/*------------------------------------------------------------------------
   static ListBox.trimHtml()
   Remove HTML tags.
------------------------------------------------------------------------*/

eq.c.ListBox.trimHtml = function(s) {
   var cls = eq.c.ListBox, r = cls.reIsHtml.exec(s);
   if (r)
      return r[1].replace(cls.reTrimHtml, '');
   return s;
};

eq.c.ListBox.reIsHtml = new RegExp('^<html>(.*?)(</html>)?$', 'i');
eq.c.ListBox.reTrimHtml = new RegExp('<[^>]*>', 'g');

/*------------------------------------------------------------------------
   ListBox value scanning and formatting.
------------------------------------------------------------------------*/

eq.c.ListBox.getFormatMethod = function(ty, fmt) {
   var ct = eq.ColType;
   switch (ty) {
      case ct.colNumeric:
         return this.formatNumber(fmt);
      case ct.colBoolean:
         return this.formatBoolean;
      case ct.colBarGraph:
         return this.formatBarGraph;
      case ct.colDate:
         return this.formatDate(fmt);
   }
   return null;
};

eq.c.ListBox.IsEscape = new RegExp("'", 'g');
eq.c.ListBox.UnEscape = new RegExp("'[^']*'", 'g');
eq.c.ListBox.ScanNumber = new RegExp('((([0#]*),)?([0#]+))(\\.([0#]*))?', 'g');

eq.c.ListBox.formatNumber = function(f) {
   var fmt, pos, neg, ppc, npc;
   if (f && f.length) {
      var
         scan = this.ScanNumber,
         isEsc = this.IsEscape,
         unEsc = this.UnEscape,
         opt = {
            maximumFractionDigits : 20,
            useGrouping : false
         },
         st = 0,
         pa = 0,
         pf = [],
         pp = pf[0] = [],
         r, i, j, g, m, s, pc;
      while (r = scan.exec(f)) {
         if (pa < 2) {
            if ((i = r.index) > st) {
               // Prefix, may escape found pattern.
               if ((m = (s = f.substring(st, i)).match(isEsc)) && m.length & 1)
                  continue;
               pp[0] = s;
            }
            st = i + r[0].length;
            if (pa === 0) {
               // First pattern.
               if (r[2] !== undefined)
                  opt.useGrouping = true;
               if ((g = r[6]) === undefined)
                  opt.maximumFractionDigits = 0;
               else {
                  opt.maximumFractionDigits = g.length;
                  i = g.indexOf('#');
                  opt.minimumFractionDigits = i !== -1 ? i : g.length;
               }
               for (j = st;; j++) {
                  if ((i = (s = f.substring(j)).indexOf(';')) === -1)
                     break;
                  // Start of negative subpattern if not escaped.
                  if (   !(m = (s = f.substring(st, j += i)).match(isEsc))
                      || (m.length & 1) === 0)
                     break;
               }
               if (i === -1) {
                  // No negative subpattern.
                  pa = 2;
                  continue;
               }
               if (s.length)
                  pp[1] = s;
               pp = pf[1] = [];
               st = j + 1;
            }
         }
         pa++;
      }
      if (st < f.length)
         pp[1] = f.substring(st);
      // Setup formatter.
      for (i = 0; i < 2; i++) {
         if (pp = pf[i]) {
            pc = undefined;
            for (j = 0; j < 2; j++)
               if (pp[j]) {
                  if (pc === undefined) {
                     // Percent if not escaped.
                     if (   (pc = pp[j].indexOf('%')) === -1
                         || (   pc > 0
                             && (m = (pp[j].substring(0, pc)).match(isEsc))
                             && m.length & 1))
                        pc = undefined;
                  }
                  if ((pp[j] = pp[j].replace(unEsc, function(m) {
                     return m.length === 2 ? "'" : m.substring(1, m.length - 1);
                  })).length) {
                     if (i) {
                        neg = pp;
                        npc = pc;
                     }
                     else {
                        pos = pp;
                        ppc = pc;
                     }
                  }
               }
         }
      }
      fmt = new Intl.NumberFormat(eq.api.locale.tag, opt);
   }
   else if ((fmt = this.defaultNumberFormat) === undefined) {
      this.defaultNumberFormat = fmt =
         new Intl.NumberFormat(eq.api.locale.tag, {
            maximumFractionDigits : 20,
            useGrouping : false
         });
   }
   return this.getNumberFormatMethod(fmt, pos, neg, ppc >= 0, npc >= 0);
};

eq.c.ListBox.getNumberFormatMethod = function(fmt, pos, neg, ppc, npc) {
   var
      unHtml = new RegExp('<[^>]*>([^<]+)', 'g'),
      format = fmt.format;
   return function(s) {
      var r, rs, ri, rl, n, res, pp, pr, po;
      if (r = unHtml.exec(s)) {
         do {
            if (ri === undefined && (rl = (rs = r[1]).length)) {
               n = Number(rs);
               if (n === n)
                  ri = unHtml.lastIndex - rl;
            }
         } while (r = unHtml.exec(s));
         if (ri === undefined)
            return s;
      }
      else {
         if (!s)
            return '';
         n = Number(s);
         if (n !== n)
            return s;
      }
      if ((pp = neg) && n < 0) {
         if (npc)
            n *= 100;
         res = format(-n);
         if (pr = pp[0]) {
            if (po = pp[1])
               res = pr + res + po;
            else
               res = pr + res;
         }
         else if (po = pp[1])
            res += po;
      }
      else {
         if (ppc)
            n *= 100;
         res = format(n);
         if (pp = pos) {
            if (pr = pp[0]) {
               if (po = pp[1])
                  res = pr + res + po;
               else
                  res = pr + res;
            }
            else if (po = pp[1])
               res += po;
         }
      }
      if (ri !== undefined) {
         if (ri) {
            if (s.length > ri + rl)
               return s.substring(0, ri) + res + s.substring(ri + rl);
            return s.substring(0, ri) + res
         }
         if (s.length > ri + rl)
            return res + s.substring(ri + rl);
      }
      return res;
   };
};

eq.c.ListBox.formatBoolean = (function() {
   var trim = eq.c.ListBox.trimHtml;
   return function(s) {
      if (s === undefined)
         return '0';
      s = trim(s);
      return s.length && s !== '0' && s.toLowerCase() !== 'false' ? '1' : '0';
   };
})();

eq.c.ListBox.scanBarGraph = (function() {
   var re = new RegExp('([0-9]+)(;([0-9]+))?');
   return function(s) {
      var r = re.exec(s), n, m, mx;
      if (r) {
         n = Number(r[1]);
         if (n === n) {
            if ((m = r[3]) !== undefined) {
               mx = Number(m);
               n = mx === mx && mx !== 0 ? n * 100 / mx : 0;
            }
            return n > 100 ? 100 : Math.round(n * 1000) / 1000;
         }
      }
      return 0;
   };
})();

eq.c.ListBox.formatBarGraph = (function() {
   var
      scan = eq.c.ListBox.scanBarGraph,
      trim = eq.c.ListBox.trimHtml;
   return function(s) {
      return s !== undefined ? scan(trim(s)).toString() : '0';
   };
})();

eq.c.ListBox.formatDate = function(f) {
   var fmt, sep, self = this.formatDate;
   if (f && f.length && !self.invalid.test(f)) {
      var r, opt = {
         year : 'numeric',
         month : '2-digit',
         day : '2-digit'
      };
      if (f.indexOf('yyyy') === -1)
         opt.year = '2-digit';
      if (f.indexOf('MMM') !== -1)
         opt.month = 'short';
      else if (f.indexOf('MM') === -1)
         opt.month = 'numeric';
      if (f.indexOf('dd') === -1)
         opt.day = 'numeric';
      if (f.indexOf('EE') !== -1)
         opt.weekday = 'short';
      fmt = new Intl.DateTimeFormat(eq.api.locale.tag, opt);
      if (typeof fmt.formatToParts === 'function') {
         if (r = self.sep.exec(f)) {
            sep = [];
            do {
               sep.push(r[1]);
            } while (r = self.sep.exec(f));
         }
         if (sep === undefined)
            sep = [''];
      }
   }
   else if ((fmt = self.defaultDateFormat) === undefined) {
      self.defaultDateFormat = fmt =
         new Intl.DateTimeFormat(eq.api.locale.tag, {
            year : 'numeric',
            month : '2-digit',
            day : '2-digit'
         });
   }
   return this.getDateFormatMethod(fmt, sep);
};

eq.c.ListBox.formatDate.invalid = new RegExp('([A-DF-LN-Za-ce-xz]+)');
eq.c.ListBox.formatDate.sep = new RegExp('([^A-Z]+)', 'gi');

eq.c.ListBox.getDateFormatMethod = function(fmt, sep) {
   var
      re = new RegExp('([0-9]{4})([0-9]{2})([0-9]{2})'),
      format = fmt.format;
   return function(s) {
      if (s === undefined || s.length === 0)
         return '';
      var r = re.exec(s);
      if (r) {
         var d = new Date(Number(r[1]), Number(r[2])-1, Number(r[3])), f;
         if (sep !== undefined) {
            var p = fmt.formatToParts(d), i, l, si = 0, sl = sep.length;
            for (f = '', i = 0, l = p.length; i < l; i++)
               if (p[i].type === 'literal') {
                  f += sep[si];
                  if (si < sl-1)
                     si++;
               }
               else
                  f += p[i].value.replace(/\.$/, '');
         }
         else
            f = format(d);
         if (r.index) {
            if (s.length > r.index + 8)
               return s.substring(0, r.index) + f + s.substring(r.index + 8);
            return s.substring(0, r.index) + f;
         }
         if (s.length > 8)
            return f + s.substring(8);
         return f;
      }
      return s;
   };
};

/*------------------------------------------------------------------------
   ListBox.sort()
   Sort ListBox content, setup lineOrder.
------------------------------------------------------------------------*/

eq.c.ListBox.prototype.sort = function() {
   var
      lineSeq = this.lineSeq,
      colContent = this.colContent,
      trim = this.constructor.trimHtml;
   this.lineOrder.sort(function(i1, i2) {
      var lsi = 0, lsl = lineSeq.length;
      while (lsi < lsl) {
         var
            li = lineSeq[lsi++],
            ci = li.i,
            s1, s2;
         if (li.d) {
            s1 = colContent[ci][i2];
            s2 = colContent[ci][i1];
         }
         else {
            s1 = colContent[ci][i1];
            s2 = colContent[ci][i2];
         }
         if (s1 !== undefined)
            s1 = trim(s1);
         if (s2 !== undefined)
            s2 = trim(s2);
         if (s1 !== s2) {
            if (s1 === undefined)
               return -1;
            if (s2 === undefined)
               return 1;
            return li.c(s1, s2);
         }
      }
      return i1 - i2;
   });
};

eq.c.ListBox.getCompareMethod = function(ty) {
   var ct = eq.ColType;
   switch (ty) {
      case ct.colAlphaNofold:
         return this.compareAlphaNoFold;
      case ct.colNumeric:
         return this.compareNumber;
      case ct.colBoolean:
         return this.compareBoolean;
      case ct.colBarGraph:
         return this.compareBarGraph;
      case ct.colAlphaFold:
         return this.compareAlphaFold;
   }
   return this.comparePlain;
};

eq.c.ListBox.comparePlain = function(i1, i2) {
   return i1 === i2 ? 0 : (i1 > i2 ? 1 : -1);
};

eq.c.ListBox.compareAlphaNoFold = (function() {
   var compare, lowerCaseFirst;
   return function(i1, i2) {
      if (compare === undefined) {
         compare = new Intl.Collator(eq.api.locale.tag).compare;
         lowerCaseFirst = compare('A', 'a') > 0;
      }
      var
         l1 = i1.length,
         l2 = i2.length,
         i = 0, l = l1 < l2 ? l1 : l2,
         c1, c2;
      while (i < l && (c1 = i1[i]) === (c2 = i2[i]))
         i++;
      if (i < l) {
         var
            lc1 = c1.toLowerCase(),
            lc2 = c2.toLowerCase();
         if (c1 !== lc1 && c2 === lc2)
            return lowerCaseFirst ? 1 : -1;
         if (c2 !== lc2 && c1 === lc1)
            return lowerCaseFirst ? -1 : 1;
      }
      return compare(i1, i2);
   };
})();

eq.c.ListBox.compareAlphaFold = (function() {
   var compare, lowerCaseFirst;
   return function(i1, i2) {
      if (compare === undefined)
         compare = new Intl.Collator(eq.api.locale.tag).compare;
      return compare(i1, i2);
   };
})();

eq.c.ListBox.compareNumber = function(i1, i2) {
   var
      n1 = Number(i1),
      n2 = Number(i2);
   if (n1 !== n1)
      return n2 === n2 ? -1 : 0;
   if (n2 !== n2)
      return n1 === n1 ? 1 : 0;
   return n1 === n2 ? 0 : (n1 > n2 ? 1 : -1);
};

eq.c.ListBox.compareBoolean = function(i1, i2) {
   var
      b1 = i1.length && i1 !== '0' && i1.toLowerCase() !== 'false',
      b2 = i2.length && i2 !== '0' && i2.toLowerCase() !== 'false';
   if (b1 !== b2)
      return b1 ? 1 : -1;
   return 0;
};

eq.c.ListBox.compareBarGraph = (function() {
   var scan = eq.c.ListBox.scanBarGraph;
   return function(i1, i2) {
      var
         n1 = scan(i1),
         n2 = scan(i2);
      if (n1 !== n2)
         return n1 > n2 ? 1 : -1;
      return 0;
   };
})();

/** @preserve $Id: 10-api-menubar.js,v 29.2 2023/11/21 15:10:37 rhg Exp $
*//*======================================================================
   final class MenuBar extends DlgContainer
   DLG MenuBar class.
========================================================================*/

eq.c.DlgContainer.addClass('MenuBar', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.MenuBar.subClass; // final

eq.c.MenuBar.prototype.border = false;

/*------------------------------------------------------------------------
   MenuBar.setup()
   Setup MenuBar from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuBar.prototype.setup = function(dlg, rq, et) {
   et.tg = 'UL';
   et.cla = [ 'eq-menubar', 'eq-control', 'eq-container' ];
   if (this.parent)
      // Dialog MenuBar (not context menu),
      // work around unexpected height when resizing.
      et.cr = [{
         ty : eq.RuleType.height,
         rl : ">li{height:" + dlg.cssHeight(1) + "}"
      }];
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   MenuBar.setAttr()
   Set MenuBar attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuBar.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            // Background color passed on to (sub-)menus and items.
            r.rl = [
               ",",
               " ul{background-color:" + s + "}"
            ];
         cr.push(r);
         break;
      }

      case rqTag.scrollable: {
         var cl = et.cla = et.cla || [];
         cl.push('eq-scroll');
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   MenuBar.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuBar.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-menubar li>label.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/** @preserve $Id: 10-api-menuitem.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class MenuItem extends DlgControl
   DLG MenuItem class.
========================================================================*/

eq.c.DlgControl.addClass('MenuItem', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.MenuItem.subClass; // final

eq.c.MenuItem.prototype.focusable = false;
eq.c.MenuItem.prototype.border = false;

/*------------------------------------------------------------------------
   MenuItem.setup()
   Setup MenuItem from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuItem.prototype.setup = function(dlg, rq, et) {
   et.tg = 'LI';
   et.cla = [ 'eq-item', 'eq-control' ];
   et.ev = { c : [ 'mousedown', 'click', 'input' ] };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   MenuItem.setAttr()
   Set MenuItem attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuItem.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.textPos: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.type:
      case rqTag.active: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.text: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.accel: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.icon: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : eq.api.makeUrl(rq.decodeString()) });
         break;
      }

      case rqTag.itemId: {
         var ds = et.ds = et.ds || {};
         ds['item'] = rq.decodeString() || null;
         break;
      }

      case rqTag.objId: {
         var ds = et.ds = et.ds || {};
         ds['obj'] = rq.decodeString() || null;
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   MenuItem.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuItem.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var ev = et.ev = et.ev || {};
   if (rq.mode === eq.c.DlgRq.mode.updated) {
      // Context menu item.
      var ds = et.ds = et.ds || {};
      if (rule) {
         ev.a = [ 'mousedown' ];
         ds['rule'] = rule;
      }
      else {
         ev.a = null;
         ds['rule'] = null;
      }
   }
   else
      ev.a = rule ? [ 'click' ] : null;
};

/*------------------------------------------------------------------------
   MenuItem.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.MenuItem.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-item.eq-font>label.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/** @preserve $Id: 10-api-menu.js,v 29.2 2023/11/21 15:10:23 rhg Exp $
*//*======================================================================
   final class Menu extends DlgContainer
   DLG Menu class.
========================================================================*/

eq.c.DlgContainer.addClass('Menu', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.Menu.subClass; // final

eq.c.Menu.prototype.border = false;

/*------------------------------------------------------------------------
   Menu.setup()
   Setup Menu from DLG request.
------------------------------------------------------------------------*/

eq.c.Menu.prototype.setup = function(dlg, rq, et) {
   et.tg = 'LI';
   et.cla = [ 'eq-menu', 'eq-control' ];
   et.ev = { c : [ 'mousedown' ] };
   et.et = {
      tg  : 'UL',
      cla : [ 'eq-container' ]
   };
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Menu.setAttr()
   Set Menu attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Menu.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.textPos: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.align: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.text: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.icon: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : eq.api.makeUrl(rq.decodeString()) });
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (s.length)
            if (this.parent instanceof eq.c.MenuBar)
               // Direct MenuBar child,
               // Background color passed on to items and sub-menus.
               r.rl = [
                  ",",
                  " ul{background-color:" + s + "}"
               ];
            else
               // Sub-menu,
               // background color not passed on to items and sub-menus.
               r.rl = "{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   Menu.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Menu.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [], rv;
   if (rl)
      rv = [
         ".eq-menu>label.eq-fn,",
         ".eq-menu li>label.eq-fn" + rl
      ];
   cr.push({
      ty : eq.RuleType.font,
      rl : rv
   });
   return true;
};

/** @preserve $Id: 10-api-plugin.js,v 29.6 2025/03/05 09:28:41 rhg Exp $
*//*======================================================================
   Install plugin classes from main thread descriptor.
========================================================================*/

eq.installPlugins = function(desc) {   
   var
      eap = eq.api.plg,
      l = desc.length,
      i, di, pn, plg, prp, pt;
   eap = eq.api.plg = eap !== undefined ? eap.concat(desc) : desc;
   for (i = 0; i < l; i++) {
      pn = 'plg' + (di = desc[i]).n;
      plg = eq.c.Plugin.subClass(pn, function() {
         eq.c.Plugin.call(this);
      });
      delete plg.subClass; // final
      if (prp = di.p) {
         pt = plg.prototype;
         for (var p in prp)
            pt[p] = prp[p];
      }
      eq.d.push(plg);
   }
};

/*========================================================================
   class Plugin extends DlgControl
   DLG Plugin class.
========================================================================*/

eq.c.DlgControl.subClass('Plugin', function() {
   eq.c.DlgControl.call(this);
});

eq.c.Plugin.prototype.border = false;
eq.c.Plugin.prototype.tempUi = true;

/*------------------------------------------------------------------------
   Plugin.setup()
   Setup Plugin from DLG request.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.setup = function(dlg, rq, et) {
   var
      cl = et.cla = [ 'eq-plugin', 'eq-control' ],
      l = this.classList;
   if (l)
      et.cla = cl.concat(l.split(' '));
   if (l = this.changeEvent) {
      var ev = et.ev = et.ev || {};
      ev.c = l.split(' ');
   }
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Plugin.setAttr()
   Set Plugin attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.plgInt:
      case rqTag.plgString:
      case rqTag.plgBinary: {
         var
            rt = et.rt = et.rt || [],
            a = rq.decodeString(),
            i = rq.decodeString(),
            v = {},
            m = {
               t : tag,
               a : a,
               i : i.length ? i : null,
               v : v
            };
         if (tag == rqTag.plgInt)
            v.i = rq.decodeInt();
         else if (tag == rqTag.plgString)
            v.s = rq.decodeString();
         else
            v.b = rq.decodeData();
         rt.push(m);
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   Plugin.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var rt = et.rt = et.rt || [];
   rt.push({
      t : eq.RqTag.font,
      a : "font",
      i : null,
      v : { font : fn, fontRules : rl }
   });
   return true;
};

/*------------------------------------------------------------------------
   Plugin.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var l, cl;
   if (l = this.actionEvent) {
      var ev = et.ev = et.ev || {};
      ev.a = rule ? l.split(' ') : null;
   }
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/*------------------------------------------------------------------------
   Plugin.onUpdate()
   Act upon main thread requesting update.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.onUpdate = function(dlg, tag, arg) {
   var ut = eq.UpdateTag;
   if (tag === ut.plgUpdate || tag === ut.plgGetVal) {
      // Delegate to main thread.
      return false;
   }

   return eq.c.DlgControl.prototype.onUpdate.call(this, dlg, tag, arg);
};

/*------------------------------------------------------------------------
   Plugin.updated()
   Process update response.
------------------------------------------------------------------------*/

eq.c.Plugin.prototype.updated = function(dlg, tag, rq, rs, d) {
   var ut = eq.UpdateTag;
   switch (tag) {
      case ut.plgUpdate:
         break;

      case ut.plgGetVal:
         // Return Plugin attribute value.
         if (d !== undefined) {
            if (d.i !== undefined) {
               rs.encodeUint(eq.RsValType.plgInt);
               rs.encodeInt(d.i);
               break;
            }
            if (d.s !== undefined) {
               rs.encodeUint(eq.RsValType.plgString);
               rs.encodeString(d.s);
               break;
            }
            if (d.b !== undefined) {
               rs.encodeUint(eq.RsValType.plgBinary);
               rs.encodeData(d.b);
               break;
            }
         }
         rs.encodeUint(eq.RsValType.plgVoid);
         break;

      default:
         throw new Error(this.constructor.name + ".updated(" + this.id +
            ") failed: tag " + tag);
   }
};

/** @preserve $Id: 10-api-poptext.js,v 29.3 2025/02/12 12:51:07 rhg Exp $
*//*======================================================================
   final class PopText extends DlgControl
   DLG PopText class.
========================================================================*/

eq.c.DlgControl.addClass('PopText', function() {
   eq.c.DlgControl.call(this);

   // Default height.
   this.defaultHeight = 8;
});

delete eq.c.PopText.subClass; // final

/*------------------------------------------------------------------------
   PopText.setup()
   Setup PopText from DLG request.
------------------------------------------------------------------------*/

eq.c.PopText.prototype.setup = function(dlg, rq, et) {
   var h = this.defaultHeight;
   this.defaultPxHeight = dlg.pxHeight(h);
   this.defaultCssHeight = dlg.cssHeight(h);

   et.cla = [ 'eq-poptext', 'eq-control', 'eq-border', 'eq-dn' ];
   et.ev  = { c : [ 'input', 'mousedown' ] };
   et.et = {
      tg  : 'SPAN',
      cla : [ 'eq-fn' ]
   };

   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   PopText.setAttr()
   Set PopText attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.PopText.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.content: {
         var
            l = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            v = [];
         v.length = l;
         for (var i = 0; i < l; i++)
            v[i] = rq.decodeString();
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.activeLine: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.boxSize: {
         var
            w = rq.decodeUint(),
            h = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            cw, ch, mh;
         if (this.sizeRaster) {
            if (w)
               cw = dlg.pxWidth(w) + 'px';
            if (h && h > this.defaultHeight)
               ch = dlg.cssHeight(h);
            else
               ch = this.defaultCssHeight;
         }
         else {
            if (w)
               cw = w + 'px';
            if (h && h > this.defaultPxHeight)
               ch = h + 'px';
            else
               ch = this.defaultCssHeight;
         }
         rt.push({ t : tag, w : cw, h : ch });
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (sv.length)
            r.rl = "{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (sv.length)
            r.rl = ".eq-focused{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }
};

/*------------------------------------------------------------------------
   PopText.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.PopText.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-poptext>span.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   PopText.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.PopText.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var ev = et.ev = et.ev || {};
   ev.a = rule ? [ 'input' ] : null;
};

/** @preserve $Id: 10-api-progressbar.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class ProgressBar extends DlgControl
   DLG ProgressBar class.
========================================================================*/

eq.c.DlgControl.addClass('ProgressBar', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.ProgressBar.subClass; // final

eq.c.ProgressBar.prototype.focusable = false;

/*------------------------------------------------------------------------
   ProgressBar.setup()
   Setup ProgressBar from DLG request.
------------------------------------------------------------------------*/

eq.c.ProgressBar.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-progressbar', 'eq-control', 'eq-border' ];
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   ProgressBar.setAttr()
   Set ProgressBar attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ProgressBar.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.vertical: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.value:
      case rqTag.text: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.fgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (sv.length)
            r.rl = ">div{background-color:" + sv + "}";
         cr.push(r);
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   ProgressBar.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ProgressBar.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [], rv;
   if (rl)
      rv = [
         ".eq-progressbar>span.eq-fn,",
         ".eq-progressbar>div>span.eq-fn" + rl
      ];
   cr.push({
      ty : eq.RuleType.font,
      rl : rv
   });
   return true;
};

/** @preserve $Id: 10-api-pushbutton.js,v 29.2 2025/01/28 12:13:35 rhg Exp $
*//*======================================================================
   final class PushButton extends DlgControl
   DLG PushButton class.
========================================================================*/

eq.c.DlgControl.addClass('PushButton', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.PushButton.subClass; // final

eq.c.PushButton.prototype.textPos = 0;
eq.c.PushButton.prototype.icon = null;

/*------------------------------------------------------------------------
   PushButton.setup()
   Setup PushButton from DLG request.
------------------------------------------------------------------------*/

eq.c.PushButton.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-pushbutton', 'eq-control', 'eq-border' ];
   et.et = {
      tg  : 'BUTTON',
      at  : { 'type' : 'button' },
      cla : [ 'eq-fn' ],
      ev  : {
         // Element is focusable and can submit,
         // use 'mousedown', not 'click'.
         a : [ 'mousedown' ]
      }
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   PushButton.setAttr()
   Set PushButton attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.PushButton.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.bgColor:
      case rqTag.focusColor:
         break;
      case rqTag.textPos:
      case rqTag.text:
      case rqTag.icon:
      case rqTag.iconActive:
      case rqTag.fgColor:
         eq.c.DlgControl.isSetup(rq, et, 2);
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.textPos: {
         var clr = et.clr = et.clr || [];
         if (!(this.textPos = rq.decodeUint())) {
            // No icon.
            clr.push('eq-horz');
            clr.push('eq-vert');
            if (this.parent instanceof eq.c.ToolBar)
               this.defaultHeight = false;
            else
               delete this.defaultHeight;
         }
         else {
            // Icon.
            var
               tp = eq.TextPos,
               ch = et.et.ch = [ { tg : 'DIV' } ],
               cla = et.cla = et.cla || [];
            ch = ch[0].ch = [{},{},{}];
            switch (this.textPos) {
               case tp.right: {
                  ch[0].tg = 'IMG';
                  ch[1].tg = 'IMG';
                  ch[2].tg = 'SPAN';
                  clr.push('eq-vert');
                  cla.push('eq-horz');
                  break;
               }
               case tp.left: {
                  ch[0].tg = 'SPAN';
                  ch[1].tg = 'IMG';
                  ch[2].tg = 'IMG';
                  clr.push('eq-vert');
                  cla.push('eq-horz');
                  break;
               }
               case tp.bottom: {
                  ch[0].tg = 'IMG';
                  ch[1].tg = 'IMG';
                  ch[2].tg = 'SPAN';
                  clr.push('eq-horz');
                  cla.push('eq-vert');
                  break;
               }
               case tp.top: {
                  ch[0].tg = 'SPAN';
                  ch[1].tg = 'IMG';
                  ch[2].tg = 'IMG';
                  clr.push('eq-horz');
                  cla.push('eq-vert');
                  break;
               }
               default:
                  throw new Error("PushButton.setAttr(" + this.id +
                     ") invalid textPos " + this.textPos);
            }
            ch = ch[1];
            cla = ch.cla = ch.cla || [];
            cla.push('eq-active');
            et.et.hc = '';
            this.defaultHeight = false;
         }
         break;
      }

      case rqTag.text: {
         var s = rq.decodeString(), tp = eq.TextPos;
         if (!this.textPos) {
            // No icon.
            et = et.et;
         }
         else {
            // Icon.
            var
               tp = eq.TextPos,
               ch = et.et.ch = et.et.ch || [ { tg : 'DIV' } ];
            et = ch[0];
            ch = et.ch = et.ch || [{},{},{}];
            switch (this.textPos) {
               case tp.right:
               case tp.bottom:
                  et = ch[2];
                  break;
               default:
                  et = ch[0];
            }
         }
         et.hca = s;
         break;
      }

      case rqTag.icon: {
         var s = eq.api.makeUrl(rq.decodeString());
         if (this.textPos) {
            var
               tp = eq.TextPos,
               ch = et.et.ch = et.et.ch || [ { tg : 'DIV' } ];
            et = ch[0];
            ch = et.ch = et.ch || [{},{},{}];
            switch (this.textPos) {
               case tp.right:
               case tp.bottom:
                  et = ch[0];
                  break;
               default:
                  et = ch[2];
            }
            et.at = et.at || {};
            et.at['src'] = this.icon = s.length ? s : null;
         }
         else
            this.icon = null;
         break;
      }

      case rqTag.iconActive: {
         var s = eq.api.makeUrl(rq.decodeString());
         if (this.textPos) {
            var
               tp = eq.TextPos,
               ch = et.et.ch = et.et.ch || [ { tg : 'DIV' } ];
            et = ch[0];
            ch = et.ch = et.ch || [{},{}];
            et = ch[1];
            et.at = et.at || {};
            et.at['src'] = s.length ? s : this.icon;
         }
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            r.rl = ">button{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (s.length)
            r.rl = ".eq-focused>button{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.fgColor: {
         var
            s = rq.decodeString(),
            st = et.et.st = et.et.st || {};
         st['color'] = s.length ? s : null;
         break;
      }
   }
};

/*------------------------------------------------------------------------
   PushButton.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.PushButton.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-pushbutton>button.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/** @preserve $Id: 10-api-radiobutton.js,v 29.2 2025/01/28 12:13:35 rhg Exp $
*//*======================================================================
   final class RadioButton extends DlgControl
   DLG RadioButton class.
========================================================================*/

eq.c.DlgControl.addClass('RadioButton', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.RadioButton.subClass; // final

/*------------------------------------------------------------------------
   RadioButton.setup()
   Setup RadioButton from DLG request.
------------------------------------------------------------------------*/

eq.c.RadioButton.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-radiobutton', 'eq-control' ];
   et.et = {
      ch : [
         { id : '*' },
         { id : '*' }
      ]
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   RadioButton.setAttr()
   Set RadioButton attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.RadioButton.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.fgColor:
      case rqTag.focusColor:
         break;
      case rqTag.textPos:
      case rqTag.radioGroup:
      case rqTag.active:
      case rqTag.text:
         if (eq.c.DlgControl.isSetup(rq, et, 2))
            et.et.ch = et.et.ch || [{},{}];
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.textPos: {
         // Button index, 0 = label right, 1 = label left.
         this.iBtn = rq.decodeUint();
         // Button.
         var ch = et.et.ch[this.iBtn];
         ch.tg = 'INPUT';
         ch.at = { 'type' : 'radio' };
         ch.ev = {
            c : [ 'click' ]
         };
         // Label.
         ch = et.et.ch[this.iBtn ? 0 : 1];
         ch.tg = 'LABEL';
         ch.at = { 'for' : et.id + '-' + (this.iBtn + 1) };
         ch.cla = [ 'eq-fn' ];
         break;
      }

      case rqTag.radioGroup: {
         var ch = et.et.ch[this.iBtn];
         ch.at = ch.at || {};
         ch.at.name = rq.decodeString();
         break;
      }

      case rqTag.active: {
         var ch = et.et.ch[this.iBtn];
         ch.at = ch.at || {};
         ch.at.checked = rq.decodeUint() !== 0;
         break;
      }

      case rqTag.text: {
         var ch = et.et.ch[this.iBtn ? 0 : 1];
         ch.hca = rq.decodeString();
         break;
      }

      case rqTag.fgColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (sv.length)
            r.rl = ">*>label{color:" + sv + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            sv = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (sv.length)
            r.rl = ".eq-focused>*>label{color:" + sv + "}";
         cr.push(r);
         break;
      }
   }
};

/*------------------------------------------------------------------------
   DlgControl.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.RadioButton.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-radiobutton>*>label.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   RadioButton.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.RadioButton.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   if (eq.c.DlgControl.isSetup(rq, et, 2))
      et.et.ch = et.et.ch || [{},{}];
   var ch = et.et.ch[this.iBtn], ev = ch.ev = ch.ev || {};
   // Element is focusable and can submit,
   // use 'mousedown', not 'click'.
   ev.a = rule ? [ 'mousedown' ] : null;
};

/** @preserve $Id: 10-api-separator.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class Separator extends DlgControl
   DLG Separator class.
========================================================================*/

eq.c.DlgControl.addClass('Separator', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.Separator.subClass; // final

eq.c.Separator.prototype.defaultWidth = true;
eq.c.Separator.prototype.focusable = false;
eq.c.Separator.prototype.border = false;

/*------------------------------------------------------------------------
   Separator.setup()
   Setup Separator from DLG request.
------------------------------------------------------------------------*/

eq.c.Separator.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-separator', 'eq-control' ];
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Separator.setAttr()
   Set Separator attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Separator.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }
};

/** @preserve $Id: 10-api-splitter.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class Splitter extends DlgContainer
   DLG Splitter class.
========================================================================*/

eq.c.DlgContainer.addClass('Splitter', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.Splitter.subClass; // final

eq.c.Splitter.prototype.vertical = false;

/*------------------------------------------------------------------------
   Splitter.setup()
   Setup Splitter from DLG request.
------------------------------------------------------------------------*/

eq.c.Splitter.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-splitter', 'eq-control', 'eq-container', 'eq-border' ];
   et.ev = { c : [ 'mousedown', 'change' ] };
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Splitter.setAttr()
   Set Splitter attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Splitter.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.panes: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag });
         break;
      }

      case rqTag.vertical: {
         var clh, clv;
         if (rq.decodeUint()) {
            this.vertical = true;
            clh = et.clr = et.clr || [];
            clv = et.cla = et.cla || [];
         }
         else {
            this.vertical = false;
            clh = et.cla = et.cla || [];
            clv = et.clr = et.clr || [];
         }
         clh.push('eq-horz');
         clv.push('eq-vert');
         break;
      }

      case rqTag.position: {
         var
            iv = rq.decodeInt(),
            rt = et.rtd = et.rtd || [];
         if (iv > 0 && this.sizeRaster)
            iv = this.vertical ? dlg.pxHeight(iv) : dlg.pxWidth(iv);
         rt.push({ t : tag, v : iv });
         break;
      }

      case rqTag.quickExpand: {
         var cl;
         if (rq.decodeUint())
            cl = et.cla = et.cla || [];
         else
            cl = et.clr = et.clr || [];
         cl.push('eq-quick');
         break;
      }

      case rqTag.bgColor:
      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   Splitter.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.Splitter.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var cl;
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/** @preserve $Id: 10-api-statictext.js,v 29.1 2023/06/07 08:47:33 rhg Exp $
*//*======================================================================
   final class StaticText extends DlgControl
   DLG StaticText class.
========================================================================*/

eq.c.DlgControl.addClass('StaticText', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.StaticText.subClass; // final

eq.c.StaticText.prototype.focusable = false;
eq.c.StaticText.prototype.border = false;

/*------------------------------------------------------------------------
   StaticText.setup()
   Setup StaticText from DLG request.
------------------------------------------------------------------------*/

eq.c.StaticText.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-statictext', 'eq-control' ];
   et.et = {
      tg  : 'SPAN',
      cla : [ 'eq-fn' ]
   };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   StaticText.setAttr()
   Set StaticText attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.StaticText.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.text:
      case rqTag.align:
         eq.c.DlgControl.isSetup(rq, et, 2);
         break;

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
         return;
   }

   switch (tag) {
      case rqTag.text:
         et.et.hca = rq.decodeString();
         break;

      case rqTag.align: {
         var st = et.et.st = et.et.st || {};
         switch (rq.decodeUint()) {
            case 0:
               st['text-align'] = null;
               break;
            case 1:
               st['text-align'] = 'center';
               break;
            case 2:
               st['text-align'] = 'right';
         }
         break;
      }
   }
};

/*------------------------------------------------------------------------
   StaticText.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.StaticText.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-statictext>span.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/** @preserve $Id: 10-api-statusbar.js,v 29.1 2023/06/07 08:47:34 rhg Exp $
*//*======================================================================
   final class StatusBar extends DlgContainer
   DLG StatusBar class.
========================================================================*/

eq.c.DlgContainer.addClass('StatusBar', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.StatusBar.subClass; // final

eq.c.StatusBar.prototype.border = false;

/*------------------------------------------------------------------------
   StatusBar.setup()
   Setup StatusBar from DLG request.
------------------------------------------------------------------------*/

eq.c.StatusBar.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-statusbar', 'eq-control', 'eq-container' ];
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   StatusBar.setAttr()
   Set StatusBar attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.StatusBar.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.posSize: {
         var
            st = et.st = et.st || {},
            iv = rq.decodeUint();
            this.sizeRaster = !!(iv & eq.DialogRaster.size);
            if (iv = rq.decodeInt()) { // height
               if (this.sizeRaster)
                  st['height'] = dlg.cssHeight(iv);
               else
                  st['height'] = iv + 'px';
            }
            else
               st['height'] = dlg.cssHeight(1);
         break;
      }

      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/** @preserve $Id: 10-api-tabbox.js,v 29.2 2023/10/11 13:48:01 rhg Exp $
*//*======================================================================
   final class TabBox extends DlgContainer
   DLG TabBox class.
========================================================================*/

eq.c.DlgContainer.addClass('TabBox', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.TabBox.subClass; // final

eq.c.TabBox.prototype.focusable = true;

/*------------------------------------------------------------------------
   TabBox.setup()
   Setup TabBox from DLG request.
------------------------------------------------------------------------*/

eq.c.TabBox.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-tabbox', 'eq-control', 'eq-border' ];
   et.ev = {
      // Element is focusable and can submit,
      // use 'mousedown', not 'click'.
      c : [ 'mousedown' ]
   };
   et.et = {
      cla : [ 'eq-container' ]
   };
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   TabBox.setAttr()
   Set TabBox attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.TabBox.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.tabs: {
         var
            api = eq.api,
            rt = et.rt = et.rt || [],
            ets = eq.TabState,
            v = [],
            l = rq.decodeUint(),
            i, ts, s;
         v.length = l;
         for (i = 0; i < l; i++)
         {
            if ((ts = rq.decodeUint()) & ets.visible)
               v[i] = {
                  s : ts,
                  t : (s = rq.decodeString()).length ? s : null,
                  i : api.makeUrl(rq.decodeString()),
                  f : api.fixupFont({
                     fc : rq.decodeString(),                  // Font face
                     sz : api.fixupFontSize(rq.decodeUint()), //      size
                     st : rq.decodeUint()                     //      style
                  }),
                  tt : (s = rq.decodeString()).length ? s : null
               };
            else
               v[i] = { s : ts };
         }
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.selectedTab: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            r.rl = ">.eq-tabs>*{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (s.length)
            r.rl = ".eq-focused>.eq-tabs>.eq-selected{background-color:" +
                   s + "}";
         cr.push(r);
         break;
      }

      case rqTag.fgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (s.length)
            r.rl = ">.eq-tabs>*{color:" + s + "}";
         cr.push(r);
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   TabBox.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.TabBox.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var cl;
   if (rule)
      cl = et.cla = et.cla || [];
   else
      cl = et.clr = et.clr || [];
   cl.push('eq-rule');
};

/*------------------------------------------------------------------------
   TabBox.onUpdate()
   Act upon main thread requesting update.
------------------------------------------------------------------------*/

eq.c.TabBox.prototype.onUpdate = function(dlg, tag, arg) {
   if (tag !== eq.UpdateTag.tabOrder)
      return eq.c.DlgContainer.prototype.onUpdate.call(this, dlg, tag, arg);

   for (var i = 0, l = this.ch.length; i < l; i++) {
      var ch = this.ch[i];
      if (ch.visible && ch.sensitive && arg-- === 0)
         delete ch.inactive;
      else
         ch.inactive = true;
   }

   dlg.updateTabOrder();
   return this.notifyUpdated(dlg, tag, dlg.tabOrder);
};

/** @preserve $Id: 10-api-toolbar.js,v 29.1 2023/06/07 08:47:34 rhg Exp $
*//*======================================================================
   final class ToolBar extends DlgContainer
   DLG ToolBar class.
========================================================================*/

eq.c.DlgContainer.addClass('ToolBar', function() {
   eq.c.DlgContainer.call(this);
});

delete eq.c.ToolBar.subClass; // final

eq.c.ToolBar.prototype.border = false;

/*------------------------------------------------------------------------
   ToolBar.setup()
   Setup ToolBar from DLG request.
------------------------------------------------------------------------*/

eq.c.ToolBar.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-toolbar', 'eq-control', 'eq-container' ];
   eq.c.DlgContainer.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   ToolBar.setAttr()
   Set ToolBar attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.ToolBar.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.posSize: {
         var
            cla = et.cla = et.cla || [],
            clr = et.clr = et.clr || [];
         switch (rq.decodeUint()) {
            case 1:
               cla.push('eq-bottom');
               clr.push('eq-top');
               clr.push('eq-left');
               clr.push('eq-right');
               break;
            case 2:
               cla.push('eq-left');
               clr.push('eq-top');
               clr.push('eq-bottom');
               clr.push('eq-right');
               break;
            case 3:
               cla.push('eq-right');
               clr.push('eq-top');
               clr.push('eq-bottom');
               clr.push('eq-left');
               break;
            default:
               cla.push('eq-top');
               clr.push('eq-bottom');
               clr.push('eq-left');
               clr.push('eq-right');
         }
         break;
      }

      case rqTag.fgColor:
         // Ignore.
         rq.decodeString();
         break;

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgContainer.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   ToolBar.addChild()
   Add child control.
------------------------------------------------------------------------*/

eq.c.ToolBar.prototype.addChild = function(child) {
   child.defaultHeight = false;
   eq.c.DlgContainer.prototype.addChild.call(this, child);
};

/*------------------------------------------------------------------------
   ToolBar.containerFocusable()
   Check 'focusable' based on container.
------------------------------------------------------------------------*/

eq.c.ToolBar.prototype.containerFocusable = function() {
   // Objects in ToolBar not focusable.
   return false;
};

/*------------------------------------------------------------------------
   ToolBar.updateTabOrder()
   Fill current tab order array.
------------------------------------------------------------------------*/

eq.c.ToolBar.prototype.updateTabOrder = function(tabOrder) {
   // Objects in ToolBar not focusable.
};

/** @preserve $Id: 10-api-tree.js,v 29.1 2023/06/07 08:47:34 rhg Exp $
*//*======================================================================
   final class Tree extends DlgControl
   DLG Tree class.
========================================================================*/

eq.c.DlgControl.addClass('Tree', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.Tree.subClass; // final

/*------------------------------------------------------------------------
   Tree.setup()
   Setup Tree from DLG request.
------------------------------------------------------------------------*/

eq.c.Tree.prototype.setup = function(dlg, rq, et) {
   et.cla = [ 'eq-tree', 'eq-control', 'eq-border' ];
   et.ev = { c : [ 'mousedown', 'change', 'status', 'emptynode', 'dnd' ] };
   et.et = { cla : [ 'eq-pane', 'eq-fn' ] };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   Tree.setAttr()
   Set Tree attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Tree.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.content: {
         var
            rt = et.rtd = et.rtd || [],
            v = [];
         this.setContent(rq, v);
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.activeLine: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.emptyNode: {
         var
            ds = et.ds = et.ds || {},
            rule = rq.decodeInt(),
            cl;
         if (rule >= 0) {
            ds['rule'] = rule ? rule : null;
            cl = et.clr = et.clr || [];
            cl.push('eq-auto');
         }
         else {
            ds['rule'] = -rule;
            cl = et.cla = et.cla || [];
            cl.push('eq-auto');
         }
         break;
      }

      case rqTag.status: {
         var
            l = rq.decodeUint(),
            rt = et.rtd = et.rtd || [],
            v = [];
         v.length = l;
         for (var i = 0; i < l; i++)
            v[i] = rq.decodeUint();
         rt.push({ t : tag, v : v });
         break;
      }

      case rqTag.ruleMode:
         this.ruleMode = rq.decodeUint();
         break;

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.bgColor };
         if (s.length)
            r.rl = "{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.focusColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.focusColor };
         if (s.length)
            r.rl = ".eq-focused{background-color:" + s + "}";
         cr.push(r);
         break;
      }

      case rqTag.dndMode: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, i : rq.decodeUint() });
         break;
      }

      case rqTag.dropRule:
      case rqTag.globalDropRule: {
         var rt = et.rt = et.rt || [];
         rt.push({ t : tag, i : rq.decodeInt() });
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   Tree.setContent()
   Set Tree content from DLG request.
------------------------------------------------------------------------*/

eq.c.Tree.prototype.setContent = function(rq, v) {
   // Number of items.
   var l = rq.decodeUint();

   // Items.
   v.length = l;
   for (var i = 0; i < l; i++)
   {
      var
         f = rq.decodeUint(),    // Flags
         hc = rq.decodeString(); // Text or HTML content
      if (f === 0) {
         // Leaf.
         v[i] = hc;
      }
      else {
         // Node, children.
         var ch = [];
         this.setContent(rq, ch);
         v[i] = {
            f : f,
            hc : hc,
            ch : ch
         };
      }
   }
};

/*------------------------------------------------------------------------
   Tree.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.Tree.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [];
   if (rl)
      rl = ".eq-tree>.eq-pane.eq-fn" + rl;
   cr.push({
      ty : eq.RuleType.font,
      rl : rl
   });
   return true;
};

/*------------------------------------------------------------------------
   Tree.setRuleAttr()
   Set 'rule' attribute from DLG request.
------------------------------------------------------------------------*/

eq.c.Tree.prototype.setRuleAttr = function(dlg, rq, et, rule) {
   var ev = et.ev = et.ev || {};
   if (rule) {
      // Element is focusable and can submit,
      // use 'mousedown', not 'click'.
      ev.a = [ 'mousedown', 'keydown' ];
      switch (this.ruleMode) {
         case 1:
            ev.m = 1;
            ev.f = null;
            break;
         case 2:
            ev.m = 2;
            ev.f = 1;
            break;
         case 3:
            ev.m = 1;
            ev.f = 1;
            break;
         default:
            ev.m = 2;
            ev.f = null;
      }
   }
   else {
      ev.a = null;
      ev.m = null;
      ev.f = null;
   }
};

/** @preserve $Id: 10-api-windowmenu.js,v 29.1 2023/06/07 08:47:34 rhg Exp $
*//*======================================================================
   final class WindowMenu extends DlgControl
   DLG WindowMenu class.
========================================================================*/

eq.c.DlgControl.addClass('WindowMenu', function() {
   eq.c.DlgControl.call(this);
});

delete eq.c.WindowMenu.subClass; // final

eq.c.WindowMenu.prototype.focusable = false;
eq.c.WindowMenu.prototype.border = false;

/*------------------------------------------------------------------------
   WindowMenu.setup()
   Setup WindowMenu from DLG request.
------------------------------------------------------------------------*/

eq.c.WindowMenu.prototype.setup = function(dlg, rq, et) {
   et.tg = 'LI';
   et.cla = [ 'eq-windowmenu', 'eq-menu', 'eq-control' ];
   et.ev = { c : [ 'mousedown' ] };
   et.et = { tg : 'UL' };
   eq.c.DlgControl.prototype.setup.call(this, dlg, rq, et);
};

/*------------------------------------------------------------------------
   WindowMenu.setAttr()
   Set WindowMenu attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.WindowMenu.prototype.setAttr = function(dlg, rq, tag, et) {
   var rqTag = eq.RqTag;
   switch (tag) {
      case rqTag.textPos: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeUint() });
         break;
      }

      case rqTag.text: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : rq.decodeString() });
         break;
      }

      case rqTag.icon: {
         var rt = et.rtd = et.rtd || [];
         rt.push({ t : tag, v : eq.api.makeUrl(rq.decodeString()) });
         break;
      }

      case rqTag.bgColor: {
         var
            cr = et.cr = et.cr || [],
            s = rq.decodeString(),
            r = { ty : eq.RuleType.fgColor };
         if (s.length)
            r.rl = [
               ",",
               " ul{background-color:" + s + "}"
            ];
         cr.push(r);
         break;
      }

      default:
         eq.c.DlgControl.prototype.setAttr.call(this, dlg, rq, tag, et);
   }
};

/*------------------------------------------------------------------------
   WindowMenu.setFontAttr()
   Set 'font' attributes from DLG request.
------------------------------------------------------------------------*/

eq.c.WindowMenu.prototype.setFontAttr = function(dlg, rq, et, fn, rl) {
   var cr = et.cr = et.cr || [], rv;
   if (rl)
      rv = [
         ".eq-windowmenu>label.eq-fn,",
         ".eq-windowmenu li>label.eq-fn" + rl
      ];
   cr.push({
      ty : eq.RuleType.font,
      rl : rv
   });
   return true;
};

/** @preserve $Id: 99-api-worker.js,v 29.11 2025/07/03 14:15:50 rhg Exp $
*//*======================================================================
   final class Api extends Obj
   Application API protocol thread.
========================================================================*/

eq.c.Obj.subClass('Api', function() {
   eq.c.Obj.call(this);
   this.reset();

   // Application base URL.
   this.appBaseUrl = null;

   // User agent tag, session id.
   this.uat = null;
   this.sid = null;

   // Dialog map.
   this.dlg = null;

   // Application locale.
   this.locale = null;

   // Reconnect timeout.
   this.reconnectTimeout = 60; // Seconds
});

delete eq.c.Api.subClass; // final

/*------------------------------------------------------------------------
   Api.reset()
   Reset API protocol thread.
------------------------------------------------------------------------*/

eq.c.Api.prototype.reset = function() {
   this.dlgRq = new eq.c.DlgRq(); // DLG request
   this.dlgRs = new eq.c.DlgRs(); // DLG response
   this.pendingCall = undefined;
   this.pendingCallCanceled = undefined;
   this.currDlg = undefined;
   this.baseUrl = null;
   this.helpBaseUrl = null;
   this.clipboardMenu = undefined;
   this.fnDefault = null;
   this.fnSizeFactor = undefined;
};

/*------------------------------------------------------------------------
   Api.resetDlg()
   Reset DLG session.
------------------------------------------------------------------------*/

eq.c.Api.prototype.resetDlg = function() {
   if (this.dlg) {
      var cls = this.constructor;
      this.dlg.forEach(cls.cbCloseDlg);
      this.dlg.clear();
      this.dlg = null;
   }
};

eq.c.Api.cbCloseDlg = function(d) {
   d.close();
};

/*------------------------------------------------------------------------
   Api.invalidateThenResume()
   Notify main thread to invalidate dialogs and resume DLG session.
------------------------------------------------------------------------*/

eq.c.Api.prototype.invalidateThenResume = function() {
   var cls = this.constructor, dlg = this.dlg, d = [];
   dlg.forEach(cls.cbInvalidateThenResume, { d : d });
   dlg.clear();
   postMessage([{ o : eq.MainOp.invalidate, d : d },
                { o : eq.MainOp.resume }]);
};

eq.c.Api.cbInvalidateThenResume = function(d) {
   this.d.push(d.id);
   d.close();
};

/*------------------------------------------------------------------------
   Api.instanceName()
   Make name instance-specific, prepend session id.
------------------------------------------------------------------------*/

eq.c.Api.prototype.instanceName = function(n) {
   if (!this.sid)
      throw new Error("instanceName: sid undefined");
   return this.sid + '.' + n;
};

/*------------------------------------------------------------------------
   Api.makeUrl()
   Prepend base URL if necessary.
------------------------------------------------------------------------*/

eq.c.Api.prototype.makeUrl = function(u) {
   var cls = this.constructor;
   if (u.length && this.baseUrl !== null && !cls.reIsAbsoluteUrl.test(u))
      u = this.baseUrl + u;
   // Support JDLG built-in Eloquence logo.
   if (cls.reIsJdlgLogo.test(u))
      return this.appBaseUrl + 'eq-webdlg-logo-109x64.png';
   return u;
};

eq.c.Api.reIsAbsoluteUrl = new RegExp('^(/|[A-Z0-9_]+:)', 'i');
eq.c.Api.reIsJdlgLogo = new RegExp('^jar:.+\\.gif', 'i');

/*------------------------------------------------------------------------
   Api.fixupFont() .fixupFontSize()
   Fixup font descriptor / font size if necessary.
------------------------------------------------------------------------*/

eq.c.Api.prototype.fixupFont = function(fn) {
   var fc = fn.fc;
   if (fc) {
      var cls = this.constructor;
      if (cls.reFnLeaveAlone.test(fc))
         fn.fc = fc.replace(cls.reFnFixQuotes, '"');
      else if (!cls.reFnIsIdent.test(fc))
         fn.fc = '"' + fc + '"';
   }
   return fn;
};

eq.c.Api.prototype.fixupFontSize = function(sz) {
   if (sz) {
      var sf = this.fnSizeFactor;
      if (sf && sf !== 1)
         return (Math.round(sz * sf * 1000) / 1000) + 'pt';
      return sz  + 'pt';
   }
   return "";
};

eq.c.Api.reFnLeaveAlone = new RegExp('[\'",]');
eq.c.Api.reFnFixQuotes = new RegExp('[\']');
eq.c.Api.reFnIsIdent = new RegExp('^[A-Z][A-Z0-9-]*$', 'i');

/*------------------------------------------------------------------------
   Api.setDefaultFont()
   Set default font and size factor.
------------------------------------------------------------------------*/

eq.c.Api.prototype.setDefaultFont = function(fc, sz, sf) {
   this.fnDefault = this.fixupFont({
      fc : fc,
      sz : this.constructor.reFnIsNumber.test(sz) ? sz + 'pt' : sz,
      st : 0
   });
   if (sf) {
      var n = Number(sf);
      if (n === n) {
         this.fnSizeFactor = n;
         return;
      }
   }
   this.fnSizeFactor = undefined;
};

eq.c.Api.reFnIsNumber = new RegExp('^[0-9.]+$');

/*------------------------------------------------------------------------
   static Api.mainReceived()
   Process message from main thread.
------------------------------------------------------------------------*/

eq.c.Api.mainReceived = function(m) {
   var self = eq.api, cls = self.constructor, op = eq.ApiOp;
   switch (m.data.o) {
      case op.documentComplete:
         if (m.data.v !== eq.p.version)
            throw new Error("mainReceived documentComplete:" +
               " main thread version " + m.data.v +
               ", expected: " + eq.p.version);
         // Document is complete.
         self.appBaseUrl = m.data.b;
         self.documentComplete = true;
         // Start or resume application if already possible.
         self.startOrResumeApplication();
         break;

      case op.installPlugins:
         // Install plugins.
         eq.installPlugins(m.data.p);
         break;

      case op.injectDone:
         // Done injecting plugins.
         if (!self.pendingInject)
            throw new Error("mainReceived injectDone: " +
               "invalid, plugin injection not pending");
         self.pendingInject = undefined;
         // Start or resume application if already possible.
         self.startOrResumeApplication();
         break;

      case op.openWebSocket:
         if (m.data.v !== eq.p.version)
            throw new Error("mainReceived openWebSocket:" +
               " main thread version " + m.data.v +
               ", expected: " + eq.p.version);
         // Open WebSocket connection.
         self.app = m.data.app;
         self.webdURL = m.data.url;
         self.sid = m.data.sid ? m.data.sid : null;
         self.args = m.data.args ? m.data.args : undefined;
         self.webdOpen();
         break;

      case op.closeWebSocket:
         if (self.ws)
            self.ws.close(1000); // Normal closure.
         break;

      case op.onLoginEvent: {
         var pl, login, pswd;
         if (self.ws === undefined)
            self.pendingLogin = undefined;
         else if ((pl = self.pendingLogin) === undefined)
            throw new Error("mainReceived: unexpected login event");
         if ((login = m.data.l) && (pswd = m.data.p)) {
            pl.login = login;
            pl.pswd = pswd;
            pl.save = m.data.s;
            self.startOrResumeApplication();
         }
         else if (self.ws)
            self.ws.close(1000); // Normal closure.
         break;
      }

      case op.onEvent: {
         if (self.pendingCall === undefined) {
            if (self.pendingCallCanceled)
               break;
            throw new Error("mainReceived onEvent: " +
               "invalid, no pending call");
         }

         var dlg = self.currDlg;
         if (dlg === undefined)
            throw new Error("mainReceived onEvent: " +
               "invalid, no current DLG");
         if (!dlg.root)
            throw new Error("mainReceived onEvent: " +
               "invalid, no DLG root object");

         // Submit event to application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.event);
            rs.encodeUint(self.pendingCall);
            self.pendingCall = undefined;

            // 1st and 2nd event, Element id.
            var ev = m.data.d, id = ev.id, did;
            if (!id)
               id = did = dlg.root.id;
            rs.encodeString(id);
            if (ev.ev !== undefined) {
               if (!(id = ev.ev.id))
                  id = did !== undefined ? did : (did = dlg.root.id);
               rs.encodeString(id);
            }
            else
               rs.encodeUint(0); // No 2nd event.

            // Changed elements.
            cls.packElements(dlg, rs, ev.changed, did);

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
            self.currDlg = undefined;
         }
         catch (e) {
            self.currDlg = undefined;
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.onPopupEvent: {
         if (self.pendingCall === undefined) {
            if (self.pendingCallCanceled)
               break;
            throw new Error("mainReceived onPopupEvent: " +
               "invalid, no pending call");
         }

         // Submit event to application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.popup);
            rs.encodeUint(self.pendingCall);
            self.pendingCall = undefined;

            // Button number.
            rs.encodeInt(m.data.d);

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.onSynced: {
         if (self.pendingCall === undefined) {
            if (self.pendingCallCanceled)
               break;
            throw new Error("mainReceived onSynced: " +
               "invalid, no pending call");
         }

         // Notify main thread.
         postMessage([{ o : eq.MainOp.synced }]);

         // Unconditionally reset current DLG,
         // could be Dialog.do implicit DLG DRAW.
         self.currDlg = undefined;

         // Submit SYNCED notification to application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.synced);
            rs.encodeUint(self.pendingCall);
            self.pendingCall = undefined;

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.onDo: {
         var id = m.data.i, dlg = self.dlg.get(id);
         if (dlg === undefined)
            throw new Error("mainReceived onDo: DLG " +
               id + " does not exist");

         // Notify application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.update);

            // Update request: Dialog.do triggered by user.
            rs.encodeUint(id);
            rs.encodeString(m.data.f);
            rs.encodeUint(eq.UpdateTag.dialogDo);

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.onHelp: {
         if (self.pendingCall === undefined) {
            if (self.pendingCallCanceled)
               break;
            throw new Error("mainReceived onHelp: " +
               "invalid, no pending call");
         }

         var dlg = self.currDlg;
         if (dlg === undefined)
            throw new Error("mainReceived onHelp: " +
               "invalid, no current DLG");
         if (!dlg.root)
            throw new Error("mainReceived onHelp: " +
               "invalid, no DLG root object");
         var
            ct = m.data.d ? dlg.findById(m.data.d, true) : dlg.root,
            url = ct.onHelp();
         if (url.length) {
            // Notify main thread with help URL.
            var key = 'help';
            postMessage([{
               o : eq.MainOp.openUrl,
               d : { u : url, t : self.instanceName(key), k : key }
            }]);
         }
         break;
      }

      case op.onMouseButton2: {
         var id = m.data.d, dlg = self.dlg.get(id);
         if (dlg === undefined)
            throw new Error("mainReceived onMouseButton2: DLG " +
               id + " does not exist");
         dlg.findById(m.data.i, true).onMouseButton2(
            dlg, m.data.x, m.data.y, m.data.c);
         break;
      }

      case op.invalidate: {
         var id = m.data.d, dlg = self.dlg.get(id);
         if (dlg !== undefined) {
            dlg.close();
            self.dlg.delete(id);
         }
         // Notify application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.invalidate);
            rs.encodeUint(id); // Dialog id.

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.update: {
         var id = m.data.d, dlg = self.dlg.get(id);
         if (dlg === undefined)
            throw new Error("mainReceived update: DLG " +
               id + " does not exist");
         dlg.findById(m.data.i || dlg.root.id, true).onUpdate(
            dlg, m.data.t, m.data.a);
         break;
      }

      case op.updated: {
         // Finish application update request.
         var
            data = m.data,
            id = data.i,
            ctid = data.c,
            rs = self.dlgRs,
            dlg, ct;
         if (ctid.length) {
            if ((dlg = self.dlg.get(id)) === undefined)
               throw new Error("mainReceived updated: DLG " +
                  id + " does not exist");
            ct = dlg.findById(ctid, true);
         }
         else
            dlg = ct = undefined;
         try {
            rs.putBuffer(data.b);
            if (ct !== undefined)
               ct.updated(dlg, data.t, null, rs, data.d);
            else {
               var d = data.d, val;
               for (var i = 0, l = d.length; i < l; i++) {
                  if ((val = d[i]) === null || val === undefined)
                     rs.encodeUint(0);
                  else if (typeof val === 'number')
                     rs.encodeInt(val);
                  else
                     rs.encodeString(val);
               }
            }
            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
            if (dlg !== undefined && dlg.temp) {
               dlg.close();
               self.dlg.delete(id);
            }
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.canceled: {
         var id = m.data.i, dlg = self.dlg.get(id);
         if (dlg === undefined)
            throw new Error("mainReceived canceled: DLG " +
               id + " does not exist");

         // Notify application.
         var rs = self.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.canceled);
            rs.encodeUint(id); // Dialog id.

            rs.encodeUint(0); // End of message.
            self.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (self.ws !== undefined)
               throw e;
         }
         break;
      }

      case op.grid: {
         var id = m.data.d, dlg = self.dlg.get(id);
         if (dlg === undefined)
            throw new Error("mainReceived grid: DLG " +
               id + " does not exist");
         // Set Dialog grid, resume processing message from webd server.
         dlg.grid = m.data.g;
         cls.webdReceived();
         break;
      }

      case op.logError:
         // Forward to application if enabled and possible.
         if (self.logErrorEnabled) {
            var rs = self.dlgRs
            try {
               rs.encodeUint(eq.c.DlgRs.op.logError);
               rs.encodeString(m.data.msg);

               rs.encodeUint(0); // End of message.
               self.ws.send(rs.buffer());
            }
            catch (e) {
               rs.dispose();
            }
         }
         break;

      default:
         throw new Error("mainReceived invalid op: " + m.data.o);
   }
};

eq.c.Api.packElements = function(dlg, rs, changed, did) {
   var pack = this.packValue, id, lastId;
   for (var i = 0, l = changed.length; i < l; i++) {
      var el = changed[i];
      if (!(id = el.id))
         id = did !== undefined ? did : (did = dlg.root.id);
      if (id !== lastId) {
         if (lastId !== undefined)
            rs.encodeUint(0); // End of lastId value(s).
         rs.encodeString(id);
         lastId = id;
      }
      if (el.v === undefined)
         pack(dlg, rs, id, el);  // Single value.
      else {
         // Multiple values.
         for (var vi = 0, vl = el.v.length; vi < vl; vi++)
            pack(dlg, rs, id, el.v[vi]);
      }
   }
   if (lastId !== undefined)
      rs.encodeUint(0); // End of value(s).
   rs.encodeUint(0); // End of changed elements.
};

eq.c.Api.packValue = function(dlg, rs, id, v) {
   var vt = eq.RsValType, ty = v.ty;
   rs.encodeUint(ty);
   switch (ty) {
      case vt.idFocus:
      case vt.textValue:
      case vt.dragFrom:
      case vt.dragContent:
      case vt.dropContent:
      case vt.dropElement: {
         // String value.
         var sv = v.sv;
         if (sv === undefined)
            throw new Error("packValue: " + ty + " sv expected");
         rs.encodeString(sv);
         break;
      }

      case vt.ruleOnce:
      case vt.dropLine: {
         // INT value.
         var iv = v.iv;
         if (iv === undefined)
            throw new Error("packValue: " + ty + " iv expected");
         rs.encodeInt(iv);
         break;
      }

      case vt.buttonChecked:
      case vt.caretPos:
      case vt.activeLine:
      case vt.topItem:
      case vt.selectedTab:
      case vt.emptyNode:
      case vt.ruleKey:
      case vt.mouseBtns:
      case vt.mouseCnt:
      case vt.mouseMods:
      case vt.dialogMax:
      case vt.dropAction:
      case vt.dropPos: {
         // UINT value.
         var iv = v.iv;
         if (iv === undefined)
            throw new Error("packValue: " + ty + " iv expected");
         rs.encodeUint(iv);
         break;
      }

      case vt.splitterPos: {
         // UINT position, grid-adjusted.
         var ct = dlg.findById(id, true), iv = v.iv;
         if (iv === undefined)
            throw new Error("packValue: splitterPos iv expected");
         if (ct.sizeRaster)
            iv = ct.vertical ? dlg.gridHeight(iv) : dlg.gridWidth(iv);
         rs.encodeUint(iv);
         break;
      }

      case vt.scrollPos: {
         // UINT x/y, grid-adjusted.
         var ct = dlg.findById(id, true), x = v.x, y = v.y;
         if (x === undefined || y === undefined)
            throw new Error("packValue: scrollPos x/y expected");
         if (ct.sizeRaster) {
            x = dlg.gridWidth(x);
            y = dlg.gridHeight(y);
         }
         rs.encodeUint(x);
         rs.encodeUint(y);
         break;
      }

      case vt.status: {
         // ARRAY value.
         var vv = v.vv, l;
         if (vv === undefined)
            throw new Error("packValue: status vv expected");
         rs.encodeUint(l = vv.length);
         for (var i = 0; i < l; i++)
            rs.encodeUint(vv[i]);
         break;
      }

      case vt.colOrder:
      case vt.colWidth:
      case vt.lineOrder:
      case vt.activeLineCol:
      case vt.dropColumn:
         dlg.findById(id, true).packValue(rs, ty, v);
         break;

      case vt.screenSize: {
         // INT w/h
         var w = v.w, h = v.h;
         if (w === undefined || h === undefined)
            throw new Error("packValue: screenSize w/h expected");
         rs.encodeInt(w);
         rs.encodeInt(h);
         break;
      }

      case vt.dialogPos: {
         // INT x/y, grid-adjusted.
         var ct = dlg.root, x = v.x, y = v.y;
         if (x === undefined || y === undefined)
            throw new Error("packValue: dialogPos x/y expected");
         if (ct.posRaster) {
            x = dlg.gridWidth(x);
            y = dlg.gridHeight(y);
         }
         rs.encodeInt(x);
         rs.encodeInt(y);
         break;
      }

      case vt.dialogSize: {
         // INT w/h, grid-adjusted.
         var ct = dlg.root, w = v.w, h = v.h;
         if (w === undefined || h === undefined)
            throw new Error("packValue: dialogSize w/h expected");
         if (ct.sizeRaster) {
            w = dlg.gridWidth(w);
            h = dlg.gridHeight(h);
         }
         rs.encodeInt(w);
         rs.encodeInt(h);
         break;
      }

      case vt.plgInt: {
         // Plugin INT attribute value.
         var atr = v.a, idx = v.i, iv = v.iv;
         if (atr === undefined)
            throw new Error("packValue: plgInt atr expected");
         if (iv === undefined)
            throw new Error("packValue: plgInt iv expected");
         rs.encodeString(atr);
         rs.encodeString(idx ? idx : '');
         rs.encodeInt(iv);
         break;
      }

      case vt.plgString: {
         // Plugin STRING attribute value.
         var atr = v.a, idx = v.i, sv = v.sv;
         if (atr === undefined)
            throw new Error("packValue: plgString atr expected");
         if (sv === undefined)
            throw new Error("packValue: plgString sv expected");
         rs.encodeString(atr);
         rs.encodeString(idx !== undefined && idx !== null ? idx : '');
         rs.encodeString(sv);
         break;
      }

      case vt.plgBinary: {
         // Plugin BINARY attribute value.
         var atr = v.a, idx = v.i, bv = v.bv;
         if (atr === undefined)
            throw new Error("packValue: plgBinary atr expected");
         if (bv === undefined)
            throw new Error("packValue: plgString bv expected");
         rs.encodeString(atr);
         rs.encodeString(idx !== undefined && idx !== null ? idx : '');
         rs.encodeData(bv);
         break;
      }

      default:
         throw new Error("packValue: unknown type " + ty);
   }
};

/*------------------------------------------------------------------------
   Api.startOrResumeApplication()
   Start or resume application.
------------------------------------------------------------------------*/

eq.c.Api.prototype.startOrResumeApplication = function(rqMsg) {
   if (this.webdOpened && this.documentComplete) {
      if (this.pendingInject)
         return;
      var pl = this.pendingLogin;
      if (pl && pl.pswd === undefined) {
         if (pl.open) {
            pl.open = false;
            var m = {
               o     : eq.MainOp.login,
               login : pl.login,
               save  : pl.save
            };
            if (rqMsg)
               rqMsg.push(m);
            else
               postMessage([ m ]);
         }
         return;
      }
      this.pendingLogin = undefined;
      if (this.startSession) {
         this.startSession = undefined;
         // Initialize DLG session, start application.
         this.resetDlg();
         this.dlg = new Map();
         var rs = this.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.start);

            // Login, password.
            if (pl && pl.login && pl.pswd) {
               rs.encodeString(pl.login);
               rs.encodeString(pl.pswd);
               rs.encodeUint(pl.save ? 1 : 0);
            }
            else {
               rs.encodeUint(0);
               rs.encodeUint(0);
               rs.encodeUint(0);
            }

            // Plugin class descriptors.
            var
               pfx = 'plugin.',
               pcf = eq.PluginFlags,
               plg = this.plg,
               pl = plg ? plg.length : 0,
               i, p, f, pp, pa, al, ai, a;
            rs.encodeUint(pl);
            for (i = 0; i < pl; i++) {
               rs.encodeString(pfx + (p = plg[i]).n);
               f = pcf.focusable | pcf.tabOnEnter;
               if (pp = p.p) {
                  if (pp.focusable === false)
                     f &= ~pcf.focusable;
                  if (pp.tabOnEnter === false)
                     f &= ~pcf.tabOnEnter;
               }
               rs.encodeUint(f);
               rs.encodeUint(al = (pa = p.a).length);
               for (ai = 0; ai < al; ai++) {
                  rs.encodeString((a = pa[ai])[0]);
                  rs.encodeUint(a[1]);
               }
            }

            // User interface class names.
            var l = eq.d.length - pl;
            for (i = 0; i < l; i++)
               rs.encodeString(eq.d[i].name);

            // Plugin class names.
            for (i = 0; i < pl; i++)
               rs.encodeString(pfx + plg[i].n);

            rs.encodeUint(0); // End of class names.

            // Application arguments.
            var args = this.args;
            if (args)
               for (var arg in args) {
                  rs.encodeString(arg);
                  rs.encodeString(args[arg]);
               }
            rs.encodeUint(0); // End of arguments.

            rs.encodeUint(0); // End of message.
            this.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (this.ws !== undefined)
               throw e;
         }
      }
      else {
         if (!this.resumeSession)
            throw new Error("BUG: startApplication command expected");
         if (this.dlg === null)
            this.dlg = new Map();
         // Resume application.
         var rs = this.dlgRs;
         try {
            rs.encodeUint(eq.c.DlgRs.op.resume);
            rs.encodeUint(0); // End of message.
            this.ws.send(rs.buffer());
         }
         catch (e) {
            rs.dispose();
            if (this.ws !== undefined)
               throw e;
         }
      }
   }
};

/*------------------------------------------------------------------------
   Api.webdOpen()
   Open WebSocket connection to webd server.
------------------------------------------------------------------------*/

eq.c.Api.prototype.webdOpen = function() {
   var cls = this.constructor;
   if (this.webdHandshakeTimer !== undefined)
      throw new Error( "BUG: webdOpen pending handshake");
   var p = 'eloquence.webdlg.' + eq.p.version;
   if (this.sid)
      p += '.' + this.sid;
   this.ws = new WebSocket(this.webdURL + this.app, p);
   this.ws.binaryType = 'arraybuffer';
   this.ws.onopen = cls.webdOpened;
   this.ws.onmessage = cls.webdReceived;
   this.ws.onclose = cls.webdClosed;
   this.ws.onerror = cls.webdFailed;
};

/*------------------------------------------------------------------------
   static Api.webdReceived()
   Process WebSocket message from webd server.
------------------------------------------------------------------------*/

eq.c.Api.webdReceived = function(m) {
   var
      self = eq.api, cls = self.constructor,
      rq = self.dlgRq, md = eq.c.DlgRq.mode;
   if (m !== undefined) {
      if (rq.msg !== undefined) {
         // Request pending, chain new DLG request.
         while (rq.next)
            rq = rq.next;
         rq = rq.next = new eq.c.DlgRq();
         rq.assign(m.data);
         rq.acquire();
         return;
      }
      rq.assign(m.data);
   }
   else if (rq.msg === undefined)
      throw new Error("webdReceived BUG: dlgRq not assigned");
   while (rq) {
      try {
         while (rq.mode = rq.decodeUint()) {
            switch (rq.mode) {
               case md.cancel: {
                  // Cancel current call.
                  var id = rq.decodeUint(), dlg;
                  if (id) {
                     if ((dlg = self.dlg.get(id)) === undefined)
                        throw new Error("webdReceived: DLG " + id +
                           " does not exist, mode: " + rq.mode);
                     if (dlg !== self.currDlg)
                        continue;
                  }
                  self.pendingCall = undefined;
                  self.pendingCallCanceled = true;
                  self.currDlg = undefined;
                  rq.msg.push({
                     o : eq.MainOp.cancelCurrentCall,
                     i : id
                  });
                  continue;
               }

               case md.message:
                  rq.msg.push({
                     o : eq.MainOp.message,
                     s : rq.decodeString()
                  });
                  continue;

               case md.update: {
                  var
                     pendingCall = rq.decodeUint(),
                     id = rq.decodeUint(), // Root id.
                     did = rq.decodeUint(), // Dialog id.
                     ctid = rq.decodeString(), // Control id.
                     tag = rq.decodeUint(),
                     dlg, ct, rs;
                  if (!ctid.length)
                     dlg = ct = undefined;
                  else if (   (dlg = self.dlg.get(id)) === undefined
                           || (ct = dlg.findById(ctid, false)) === null)
                  {
                     // Consume remaining message.
                     // Server ensures that UPDATE is last request.
                     rq.dispose();
                     return;
                  }
                  rs = self.dlgRs;
                  try {
                     // Setup UPDATED response.
                     var cl, ai, ix, ty;
                     rs.encodeUint(eq.c.DlgRs.op.updated);
                     rs.encodeUint(pendingCall);
                     rs.encodeUint(did);    // Dialog id.
                     rs.encodeString(ctid); // Control id.
                     rs.encodeUint(tag);    // Tag.
                     rs.encodeString(rq.decodeString());     // Request object.
                     rs.encodeUint(cl = rq.decodeUint());    // Call.
                     rs.encodeUint(ai = rq.decodeUint());    // Attribute id.
                     rs.encodeString(ix = rq.decodeString());// Attribute index.
                     if (cl === eq.DlgCall.set) { // DLG SET value.
                        var vt = eq.DlgValType;
                        rs.encodeUint(rq.decodeUint()); // DLG SET wildcard flag.
                        rs.encodeUint(ty = rq.decodeUint());
                        switch (ty) {
                           case vt.tInt:
                              rs.encodeInt(rq.decodeInt());
                              break;
                           case vt.tString:
                              rs.encodeString(rq.decodeString());
                              break;
                           default:
                              throw new Error("webdReceived update: " +
                                 "invalid value type: " + ty);
                        }
                     }

                     // Process update request.
                     if (   ct !== undefined
                         && ct.onUpdate(dlg, tag, { rs : rs })) {
                        // Send immediate response.
                        rs.encodeUint(0); // End of message.
                        self.ws.send(rs.buffer());
                        if (dlg.temp) {
                           dlg.close();
                           self.dlg.delete(id);
                        }
                     }
                     else {
                        // Delegate to main thread,
                        // then finished by mainReceived(updated).
                        rq.msg.push({
                           o : eq.MainOp.update,
                           i : id,
                           c : ctid,
                           t : tag,
                           a : ai,
                           x : ix,
                           b : rs.takeBuffer()
                        });
                     }
                  }
                  catch (e) {
                     rs.dispose();
                     if (dlg !== undefined && dlg.temp) {
                        dlg.close();
                        self.dlg.delete(id);
                     }
                     if (self.ws !== undefined)
                        throw e;
                  }
                  continue;
               }

               case md.updated: {
                  var
                     dlg = self.dlg.get(rq.decodeUint()), // Dialog.
                     ctid = rq.decodeString(), // Control id.
                     tag = rq.decodeUint(),
                     ct;
                  if (   dlg === undefined
                      || (ct = dlg.findById(ctid, false)) === null)
                  {
                     // Don't know what's coming,
                     // must consume remaining message.
                     // Server ensures that UPDATED is last request.
                     rq.dispose();
                     return;
                  }
                  ct.updated(dlg, tag, rq, null, null);
                  continue;
               }
            }

            if (self.currDlg !== undefined)
               throw new Error("webdReceived: invalid request " + rq.mode +
                  " during DLG call");
            if (self.pendingCall !== undefined)
               throw new Error("webdReceived: invalid request " + rq.mode +
                  " while call pending");
            if (self.resumeSession)
               switch (rq.mode) {
                  case md.global:
                  case md.rootFont:
                  case md.add:
                  case md.POPUP_BOX:
                     break;
                  default:
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + " while resuming session");
               }

            switch (rq.mode) {
               case md.ssnNew:
               case md.ssnResume: {
                  cls.webdStopHandshakeTimer();
                  self.uat = rq.decodeString();
                  self.sid = rq.decodeString();
                  var
                     app = rq.decodeString(),
                     title = rq.decodeString(),
                     locale = rq.decodeString(),
                     fsf = rq.decodeString(), // Font size factor
                     ffc = rq.decodeString(), //      face
                     fsz = rq.decodeString(), //      size
                     icon = rq.decodeString();
                  rq.msg.push({
                     o : eq.MainOp.popupPos,
                     d : rq.decodeUint()
                  });
                  rq.msg.push({
                     o : eq.MainOp.warnLeavingPage,
                     d : rq.decodeUint()
                  });
                  self.reconnectTimeout = rq.decodeUint();
                  self.logErrorEnabled = rq.decodeUint();
                  if (rq.decodeUint()) {
                     // Login credentials present.
                     if (self.pendingLogin !== undefined)
                        throw new Error("webdReceived: invalid request " +
                           rq.mode + ": pending login unexpected");
                  }
                  else {
                     // Request login credentials as soon as possible.
                     self.pendingLogin = {};
                  }

                  var
                     n_inj = rq.decodeUint(), // Number of plugins to inject.
                     n_wfs = rq.decodeUint(), // Number of window features.
                     inj = [], wfs = [],
                     pendingInject;

                  // Inject plugins if necessary.
                  inj.length = n_inj;
                  for (var i = 0; i < n_inj; i++)
                     inj[i] = rq.decodeString();
                  if (pendingInject = n_inj)
                     rq.msg.push({ o : eq.MainOp.inject, inj : inj });

                  // Window features.
                  wfs.length = n_wfs;
                  for (var i = 0; i < n_wfs; i++) {
                     wfs[i] = {
                        k : rq.decodeString(),
                        v : rq.decodeString()
                     };
                  }
                  rq.msg.push({ o : eq.MainOp.windowFeatures, d : wfs });

                  // Application CSS classes.
                  var sv, css = [];
                  while ((sv = rq.decodeString()).length)
                     css.push(sv);

                  if (locale.length === 0)
                     locale = (new Intl.NumberFormat()).resolvedOptions().locale;
                  self.locale = { tag : locale };
                  self.setDefaultFont(ffc, fsz, fsf);

                  if (self.uat.length === 0)
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + ": user agent tag undefined");
                  if (self.sid.length === 0)
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + ": session id undefined");
                  if (self.app === undefined)
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + ": app undefined, expected: '" + app + "'");
                  if (self.app !== app) {
                     if (self.app.toUpperCase() !== app.toUpperCase())
                        throw new Error("webdReceived: invalid request " +
                           rq.mode + ": expected app: '" + self.app +
                           "', received: '" + app + "'");
                     self.app = app;
                  }
                  if (self.startSession !== undefined)
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + ": startSession unexpected, app: '" +
                        app + "'");
                  if (self.resumeSession !== undefined)
                     throw new Error("webdReceived: invalid request " +
                        rq.mode + ": resumeSession unexpected, app: '" +
                        app + "'");

                  self.pendingInject = pendingInject !== 0;
                  if (rq.mode === md.ssnNew)
                     self.startSession = true;
                  else
                     self.resumeSession = true;
                  rq.msg.push({
                     o : eq.MainOp.ssnNewOrResume,
                     d : {
                        uat    : self.uat,
                        sid    : self.sid,
                        title  : title,
                        locale : self.locale,
                        fn     : self.fnDefault,
                        sf     : self.fnSizeFactor,
                        icon   : icon,
                        css    : css
                     }
                  });
                  // Start or resume application if already possible.
                  self.startOrResumeApplication(rq.msg);
                  continue;
               }

               case md.login: {
                  var pl = self.pendingLogin, save;
                  if (pl === undefined)
                     throw new Error("webdReceived: invalid login");
                  pl.login = rq.decodeString();
                  switch (rq.decodeInt()) {
                     case 0:
                        pl.save = false;
                        break;
                     case 1:
                        pl.save = true;
                  }
                  pl.open = true;
                  // Open login dialog if already possible.
                  self.startOrResumeApplication();
                  continue;
               }
            }

            if (self.dlg === null)
               throw new Error("webdReceived: invalid request " +
                  rq.mode + ": DLG session closed");

            switch (rq.mode) {
               case md.global: {
                  var tag, rqTag = eq.RqTag;
                  while (tag = rq.decodeUint()) {
                     switch (tag) {
                        case rqTag.popupPos: {
                           rq.msg.push({
                              o : eq.MainOp.popupPos,
                              d : rq.decodeUint()
                           });
                           break;
                        }
                        case rqTag.baseUrl: {
                           var url = rq.decodeString();
                           self.baseUrl = url.length ? url : null;
                           break;
                        }
                        case rqTag.helpBaseUrl: {
                           var url = rq.decodeString();
                           self.helpBaseUrl = url.length ? url : null;
                           break;
                        }
                        case rqTag.clipboardMenu: {
                           var menu = rq.decodeString();
                           self.clipboardMenu = menu.length ? menu : undefined;
                           break;
                        }
                        case rqTag.windowFeature:
                           rq.msg.push({
                              o : eq.MainOp.windowFeature,
                              k : rq.decodeString(),
                              v : rq.decodeString()
                           });
                           break;
                        case rqTag.layout: {
                           rq.msg.push({
                              o : eq.MainOp.layout,
                              d : rq.decodeUint()
                           });
                           break;
                        }
                        case rqTag.notifyBusy: {
                           rq.msg.push({
                              o : eq.MainOp.notifyBusy,
                              d : rq.decodeUint()
                           });
                           break;
                        }
                        case rqTag.typeAhead: {
                           rq.msg.push({
                              o : eq.MainOp.typeAhead,
                              d : rq.decodeUint()
                           });
                           break;
                        }
                        case rqTag.openUrl: {
                           var
                              url = self.makeUrl(rq.decodeString()),
                              key = rq.decodeString(), target;
                              if (key)
                                 target = key;
                              else {
                                 key = 'doc';
                                 target = self.instanceName(key);
                              }
                           if (url.length)
                              rq.msg.push({
                                 o : eq.MainOp.openUrl,
                                 d : { u : url, t : target, k : key }
                              });
                           break;
                        }
                        case rqTag.openHelpUrl: {
                           var url = rq.decodeString();
                           if (   self.helpBaseUrl !== null
                               && !cls.reIsAbsoluteUrl.test(url))
                              url = self.helpBaseUrl + url;
                           if (url.length) {
                              var key = 'help';
                              rq.msg.push({
                                 o : eq.MainOp.openUrl,
                                 d : {
                                    u : url,
                                    t : self.instanceName(key),
                                    k : key
                                 }
                              });
                           }
                           break;
                        }
                        case rqTag.beep: {
                           rq.msg.push({ o : eq.MainOp.beep });
                           break;
                        }
                        case rqTag.soundUrl: {
                           var url = self.makeUrl(rq.decodeString());
                           if (url.length)
                              rq.msg.push({
                                 o : eq.MainOp.playSound,
                                 d : { url : url }
                              });
                           break;
                        }
                        case rqTag.startApp: {
                           var
                              app = rq.decodeString(),
                              wf = rq.decodeString();
                           if (app.length)
                              rq.msg.push({
                                 o : eq.MainOp.startApp,
                                 d : { app : app, wf : wf }
                              });
                           break;
                        }
                        case rqTag.activate: {
                           var title = rq.decodeString();
                           if (title.length)
                              rq.msg.push({
                                 o : eq.MainOp.activate,
                                 d : title
                              });
                           break;
                        }
                        case rqTag.clipboard: {
                           rq.msg.push({
                              o : eq.MainOp.clipboard,
                              d : rq.decodeString()
                           });
                           break;
                        }
                        default:
                           throw new Error(
                              "webdReceived global: invalid tag " + tag);
                     }
                  }
                  break;
               }

               case md.rootFont: {
                  // Resume session?
                  if (self.resumeSession) {
                     self.resumeSession = undefined;
                     self.invalidateThenResume();
                  }
                  var
                     id = rq.decodeUint(),      // Dialog id.
                     dlg = self.dlg.get(id),
                     ffc = rq.decodeString(), // Font face
                     fsz = rq.decodeUint(),   //      size
                     fst = rq.decodeUint(),   //      style
                     dfn = self.fnDefault,
                     fn;
                  if (dlg === undefined)
                     self.dlg.set(id, (dlg = new eq.c.Dlg(id)));
                  else if (dlg.temp)
                     throw new Error("webdReceived: DLG " + id +
                        " is temporary, mode: " + rq.mode);
                  // Merge with default font if necessary.
                  fn = self.fixupFont({
                     fc : ffc ? ffc : (dfn ? dfn.fc : ''),
                     sz : fsz ? fsz + 'pt' : (dfn ? dfn.sz : ''),
                     st : fst
                  });
                  // Delegate grid calculation to main thread.
                  postMessage([{
                     o : eq.MainOp.grid,
                     i : id,
                     f : fn
                  }]);
                  // Continue when main thread returns ApiOp.grid.
                  rq.acquire();
                  return;
               }

               case md.add: {
                  // Resume session?
                  if (self.resumeSession) {
                     self.resumeSession = undefined;
                     self.invalidateThenResume();
                  }
                  var
                     id = rq.decodeUint(),       // Dialog id.
                     dlg = self.dlg.get(id),
                     parent = rq.decodeString(), // Parent id (root if empty).
                     ct;
                  if (dlg === undefined) {
                     if (parent !== 'eq-temp')
                        throw new Error("webdReceived: DLG " + id +
                           " does not exist, mode: " + rq.mode);
                     self.dlg.set(id, (dlg = new eq.c.Dlg(id)));
                  }
                  if (parent === 'eq-temp')
                     dlg.temp = true;
                  else if (parent) {
                     if (dlg.temp)
                        throw new Error("webdReceived: DLG " + id +
                           " is temporary, mode: " + rq.mode);
                     ct = dlg.findById(parent, true);
                  }
                  // Create control(s).
                  dlg.addChild(rq, ct);
                  // Invalidate tab order.
                  dlg.tabOrderValid = false;
                  break;
               }

               case md.modify: {
                  var
                     id = rq.decodeUint(), // Dialog id.
                     dlg = self.dlg.get(id),
                     ctid = rq.decodeString(), // Control id.
                     ct, et;
                  if (dlg === undefined)
                     throw new Error("webdReceived: DLG " + id +
                        " does not exist, mode: " + rq.mode);
                  if (dlg.temp)
                     throw new Error("webdReceived: DLG " + id +
                        " is temporary, mode: " + rq.mode);
                  ct = dlg.findById(ctid, true);
                  et = { id : ct.id };
                  rq.msg.push({
                     o : eq.MainOp.modifyControl,
                     i : id,
                     d : et
                  });
                  ct.modify(dlg, rq, et);
                  break;
               }

               case md.del: {
                  var
                     id = rq.decodeUint(), // Dialog id.
                     dlg = self.dlg.get(id),
                     ctid = rq.decodeString(), // Control id.
                     ct;
                  if (dlg === undefined)
                     throw new Error("webdReceived: DLG " + id +
                        " does not exist, mode: " + rq.mode);
                  if (dlg.temp)
                     throw new Error("webdReceived: DLG " + id +
                        " is temporary, mode: " + rq.mode);
                  ct = dlg.findById(ctid, true);
                  rq.msg.push({
                     o : eq.MainOp.deleteControl,
                     i : id,
                     d : ct.id
                  });
                  if (ct.del()) {
                     dlg.root = null;
                     dlg.close();
                     self.dlg.delete(id);
                  }
                  else {
                     // Invalidate tab order.
                     dlg.tabOrderValid = false;
                  }
                  break;
               }

               case md.invalidate: {
                  var id, dlg, d = [];
                  while (id = rq.decodeUint()) { // Dialog id.
                     if ((dlg = self.dlg.get(id)) === undefined)
                        continue;
                     if (!dlg.temp)
                        d.push(id);
                     dlg.close();
                     self.dlg.delete(id);
                  }
                  if (d.length) {
                     rq.msg.push({
                        o : eq.MainOp.invalidate,
                        d : d
                     });
                  }
                  break;
               }

               case md.DLG_DRAW: {
                  var
                     pendingCall = rq.decodeUint(),
                     id = rq.decodeUint(), // Dialog id.
                     dlg = self.dlg.get(id);
                  if (dlg === undefined)
                     throw new Error("webdReceived: DLG " + id +
                        " does not exist, mode: " + rq.mode);
                  if (dlg.temp)
                     throw new Error("webdReceived: DLG " + id +
                        " is temporary, mode: " + rq.mode);
                  self.pendingCall = pendingCall;
                  self.pendingCallCanceled = undefined;
                  rq.msg.push({
                     o : eq.MainOp.dlg_DRAW,
                     i : id
                  });
                  break;
               }

               case md.DLG_DO: {
                  var
                     pendingCall = rq.decodeUint(),
                     id = rq.decodeUint(), // Dialog id.
                     dlg = self.dlg.get(id),
                     d = {
                        f : rq.decodeString() // Focus object id
                     };
                  if (dlg === undefined)
                     throw new Error("webdReceived: DLG " + id +
                        " does not exist, mode: " + rq.mode);
                  if (dlg.temp)
                     throw new Error("webdReceived: DLG " + id +
                        " is temporary, mode: " + rq.mode);
                  if (!dlg.tabOrderValid) {
                     dlg.updateTabOrder();
                     d.t = dlg.tabOrder;
                  }
                  self.currDlg = dlg;
                  self.pendingCall = pendingCall;
                  self.pendingCallCanceled = undefined;
                  rq.msg.push({
                     o : eq.MainOp.dlg_DO,
                     i : id,
                     d : d
                  });
                  break;
               }

               case md.POPUP_BOX: {
                  if (self.resumeSession) {
                     self.resumeSession = undefined;
                     self.invalidateThenResume();
                  }
                  var
                     pendingCall = rq.decodeUint(),
                     popup = { btn : [] },
                     btn;
                  popup.x = rq.decodeInt();
                  popup.y = rq.decodeInt();
                  popup.title = rq.decodeString();
                  popup.msg = rq.decodeString();
                  popup.btnDefault = rq.decodeInt();
                  while (btn = rq.decodeString())
                     popup.btn.push(btn);
                  self.pendingCall = pendingCall;
                  self.pendingCallCanceled = undefined;
                  rq.msg.push({
                     o : eq.MainOp.dlg_POPUP_BOX,
                     d : popup
                  });
                  break;
               }

               default:
                  throw new Error("webdReceived: invalid mode: " + rq.mode);
            }
         }

         if (rq.msg.length) {
            // Notify main thread.
            postMessage(rq.msg);
         }

         // Dispose request, prepare processing chained request if present.
         var nextRq = rq.next;
         rq.next = null;
         rq.dispose();
         rq = nextRq;
      }
      catch (e) {
         rq.dispose();
         throw e;
      }
   }
};

/*------------------------------------------------------------------------
   static Api.webdOpened()
   WebSocket connection to webd server opened.
------------------------------------------------------------------------*/

eq.c.Api.webdOpened = function(e) {
   var self = eq.api, cls = self.constructor;
   if (self.webdHandshakeTimer !== undefined)
      throw new Error( "BUG: webdOpened pending handshake");
   self.webdOpened = true;
   cls.webdStopRetry();
   self.webdHandshakeTimer = setTimeout(cls.webdHandshakeFailed, 30000);
};

eq.c.Api.webdHandshakeFailed = function() {
   console.log("WebSocket handshake failed");
   eq.c.Api.webdStopHandshakeTimer();
   eq.api.ws.close(1002); // Protocol error.
};

eq.c.Api.webdStopHandshakeTimer = function() {
   var self = eq.api;
   if (self.webdHandshakeTimer !== undefined) {
      clearTimeout(self.webdHandshakeTimer);
      self.webdHandshakeTimer = undefined;
   }
};

/*------------------------------------------------------------------------
   static Api.webdClosed()
   WebSocket connection to webd server closed.
------------------------------------------------------------------------*/

eq.c.Api.webdClosed = function(e) {
   var
      self = eq.api,
      cls = self.constructor,
      retryTimer = self.webdRetryTimer,
      mayRetry = false,
      mayRestart = false,
      stopped = false;
   switch (e.code) {
      case 1006:
         if (self.pendingLogin) {
            mayRestart = true;
            stopped = true;
         }
         else
            mayRetry = true;
         break;
      case 1000:
      case 1005: // IE
         mayRestart = true;
         // FALLTHROUGH
      default:
         stopped = true;
   }
   self.ws = undefined;
   if (retryTimer !== undefined && !mayRetry) {
      cls.webdStopRetry();
      retryTimer = undefined;
   }
   self.startSession = undefined;
   self.resumeSession = undefined;
   self.pendingLogin = undefined;
   self.pendingInject = undefined;
   cls.webdStopHandshakeTimer();
   if (retryTimer === undefined) {
      self.webdOpened = undefined;
      if (stopped) {
         // Application has stopped.
         self.reset();
         self.resetDlg();
         // Notify main thread: Application has stopped.
         postMessage([{
            o : eq.MainOp.applicationStopped,
            d : mayRestart
         }]);
         // Cleanup classes, dispose static properties if necessary.
         for (var c in eq.c) {
            var cc = eq.c[c];
            if (cc.dispose)
               cc.dispose();
         }
      }
   }
};

/*------------------------------------------------------------------------
   static Api.webdFailed()
   WebSocket connection to webd server failed.
------------------------------------------------------------------------*/

eq.c.Api.webdFailed = function(e) {
   var self = eq.api, cls = self.constructor;
   if (   self.webdRetryTimer === undefined
       && (self.webdRetryCnt = self.reconnectTimeout) > 0) {
      self.webdRetryTimer = setInterval(cls.webdRetry, 1000);
      console.log("WebSocket connection failed, retry: " +
         self.webdRetryCnt + " seconds");
      // Cancel current call, notify main thread.
      self.pendingCall = undefined;
      self.pendingCallCanceled = true;
      self.currDlg = undefined;
      postMessage([{ o : eq.MainOp.cancelCurrentCall, i : 0 }]);
      return;
   }
   if (self.webdRetryCnt <= 0) {
      cls.webdStopRetry();
      console.log("WebSocket connection failed");
      // Notify main thread: Application has failed.
      postMessage([{ o : eq.MainOp.applicationFailed }]);
   }
};

eq.c.Api.webdRetry = function() {
   var self = eq.api;
   if (self.webdRetryCnt > 0)
      if ((--self.webdRetryCnt % 10) === 0 && self.webdRetryCnt)
         console.log("WebSocket connection failed, retry: " +
            self.webdRetryCnt + " seconds");
   if (self.ws === undefined)
      self.webdOpen();
};

eq.c.Api.webdStopRetry = function() {
   var self = eq.api;
   if (self.webdRetryTimer !== undefined) {
      clearInterval(self.webdRetryTimer);
      self.webdRetryTimer = undefined;
      self.webdRetryCnt = undefined;
   }
};

/*========================================================================
   Start API thread.
========================================================================*/

eq.api = new eq.c.Api();
self.onmessage = eq.c.Api.mainReceived;
postMessage([{ o : eq.MainOp.apiThreadStarted }]);

