Thanks everyone,

John, your code does precisely what I need! Look forward to seeing it
proposed for core, if possible.

Thanks John, Dietrich, and Derrell,
Reed

On Tue, Jan 12, 2016 at 11:48 AM, Derrell Lipman <
derrell.lip...@unwireduniverse.com> wrote:

> Thanks, John! This would be a nice contribution to core qooxdoo.
>
> D
>
>
> On Tue, Jan 12, 2016 at 11:46 AM John Spackman <john-li...@zenesis.com>
> wrote:
>
>> No not really, I think it probably should go across just fine but OTOH I
>> first wrote it several years ago (e.g. Qx v3.x or maybe even earlier) so
>> there could be things that were added to Qx that mine doesn’t have.  I’ll
>> have a go at comparing them side by side to give you a better answer!
>>
>> John.
>>
>> From: Derrell Lipman <derrell.lip...@unwireduniverse.com>
>> Reply-To: qooxdoo Development <qooxdoo-devel@lists.sourceforge.net>
>> Date: Tuesday, 12 January 2016 at 13:25
>> To: qooxdoo Development <qooxdoo-devel@lists.sourceforge.net>
>> Subject: Re: [qooxdoo-devel] Scaling (but not skewing) Atom image
>>
>> Thanks, John! Do you know if there anything about your Image class that
>> would prevent it from directly replacing the native qooxdoo Image class?
>> Any known non-backward-compatibilities? As I searched the archives, I found
>> that there have been many questions about how to do this over the years...
>>
>> Derrell
>>
>>
>> On Tue, Jan 12, 2016 at 8:09 AM John Spackman <john-li...@zenesis.com>
>> wrote:
>>
>>> The only solution I have for this is to reimplement qx.core.basic.Image,
>>> but with added tweaks for scaling.  I’ve created a playground demo but it
>>> is too large for a shortened URL because out is basically my entire Image
>>> class plus the demo code, so here it is in email:
>>>
>>>
>>> /**
>>>
>>>  * Image which preserves the aspect ratio while scaling the image and
>>> constrains
>>>
>>>  * the dimensions to stay within the min/max width/height. The image is
>>> placed
>>>
>>>  * centrally within the dimensions of the widget.
>>>
>>>  *
>>>
>>>  * Based on the Qooxdoo image
>>>
>>>  */
>>>
>>> qx.Class.define("grasshopper.af.ui.image.Image", {
>>>
>>>   extend : qx.ui.core.Widget,
>>>
>>>
>>>   /*
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    * CONSTRUCTOR
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    */
>>>
>>>
>>>   /**
>>>
>>>    * @param source
>>>
>>>    *          {String?null} The URL of the image to display.
>>>
>>>    */
>>>
>>>   construct : function(source) {
>>>
>>>     this.__contentElements = {};
>>>
>>>
>>>     this.base(arguments);
>>>
>>>
>>>     if (source) {
>>>
>>>       this.setSource(source);
>>>
>>>     }
>>>
>>>   },
>>>
>>>
>>>   /*
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    * PROPERTIES
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    */
>>>
>>>
>>>   properties : {
>>>
>>>     /** The URL of the image */
>>>
>>>     source : {
>>>
>>>       check : "String",
>>>
>>>       init : null,
>>>
>>>       nullable : true,
>>>
>>>       event : "changeSource",
>>>
>>>       apply : "_applySource",
>>>
>>>       themeable : true
>>>
>>>     },
>>>
>>>
>>>
>>>     /**
>>>
>>>      * Whether the image should be scaled to the given dimensions
>>>
>>>      *
>>>
>>>      * This is disabled by default because it prevents the usage of
>>> image
>>>
>>>      * clipping when enabled.
>>>
>>>      */
>>>
>>>     scale : {
>>>
>>>       check : "Boolean",
>>>
>>>       init : true,
>>>
>>>       themeable : true,
>>>
>>>       apply : "_applyScale"
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Whether to preserve the image ratio (ie prevent distortion), and
>>> which dimension
>>>
>>>      * to prioritise
>>>
>>>      */
>>>
>>>     forceRatio : {
>>>
>>>       init : 'auto',
>>>
>>>       check : [ 'disabled', 'height', 'width', 'auto' ],
>>>
>>>       apply : '_applyDimension'
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Whether to allow scaling the image up
>>>
>>>      */
>>>
>>>     allowScaleUp : {
>>>
>>>       init : false,
>>>
>>>       check : "Boolean",
>>>
>>>       apply : "_applyDimension"
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     appearance : {
>>>
>>>       refine : true,
>>>
>>>       init : "image"
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     allowShrinkX : {
>>>
>>>       refine : true,
>>>
>>>       init : false
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     allowShrinkY : {
>>>
>>>       refine : true,
>>>
>>>       init : false
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     allowGrowX : {
>>>
>>>       refine : true,
>>>
>>>       init : false
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     allowGrowY : {
>>>
>>>       refine : true,
>>>
>>>       init : false
>>>
>>>     }
>>>
>>>   },
>>>
>>>
>>>   /*
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    * EVENTS
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    */
>>>
>>>
>>>   events : {
>>>
>>>     /**
>>>
>>>      * Fired if the image source can not be loaded.
>>>
>>>      *
>>>
>>>      * *Attention*: This event is only used for images which are loaded
>>>
>>>      * externally (aka unmanaged images).
>>>
>>>      */
>>>
>>>     loadingFailed : "qx.event.type.Event",
>>>
>>>
>>>     /**
>>>
>>>      * Fired if the image has been loaded.
>>>
>>>      *
>>>
>>>      * *Attention*: This event is only used for images which are loaded
>>>
>>>      * externally (aka unmanaged images).
>>>
>>>      */
>>>
>>>     loaded : "qx.event.type.Event"
>>>
>>>   },
>>>
>>>
>>>   /*
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    * MEMBERS
>>>
>>>    *
>>> ****************************************************************************
>>>
>>>    */
>>>
>>>
>>>   members : {
>>>
>>>     __width : null,
>>>
>>>     __height : null,
>>>
>>>     __mode : null,
>>>
>>>     __contentElements : null,
>>>
>>>     __currentContentElement : null,
>>>
>>>     __wrapper : null,
>>>
>>>
>>>     // overridden
>>>
>>>     _onChangeTheme : function() {
>>>
>>>       this.base(arguments);
>>>
>>>       // restyle source (theme change might have changed the resolved
>>> url)
>>>
>>>       this._styleSource();
>>>
>>>     },
>>>
>>>
>>>
>>>     /*
>>>
>>>      *
>>> ---------------------------------------------------------------------------
>>>
>>>      * WIDGET API
>>>
>>>      *
>>> ---------------------------------------------------------------------------
>>>
>>>      */
>>>
>>>
>>>     // overridden
>>>
>>>     getContentElement : function() {
>>>
>>>       return this.__getSuitableContentElement();
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     _createContentElement : function() {
>>>
>>>       return this.__getSuitableContentElement();
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     _getContentHint : function() {
>>>
>>>       return {
>>>
>>>         width : this.__width || 0,
>>>
>>>         height : this.__height || 0
>>>
>>>       };
>>>
>>>     },
>>>
>>>
>>>
>>>     _applyDimension: function(value, oldValue) {
>>>
>>>       this.base(arguments, value, oldValue);
>>>
>>>       this.__updateContentHint();
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     _applyDecorator : function(value, old) {
>>>
>>>       this.base(arguments, value, old);
>>>
>>>
>>>       var source = this.getSource();
>>>
>>>       source = qx.util.AliasManager.getInstance().resolve(source);
>>>
>>>       var el = this.getContentElement();
>>>
>>>       if (this.__wrapper) {
>>>
>>>         el = el.getChild(0);
>>>
>>>       }
>>>
>>>       this.__setSource(el, source);
>>>
>>>     },
>>>
>>>
>>>     // overridden
>>>
>>>     _applyPadding : function(value, old, name) {
>>>
>>>       this.base(arguments, value, old, name);
>>>
>>>
>>>       var element = this.getContentElement();
>>>
>>>       if (this.__wrapper) {
>>>
>>>         element.getChild(0).setStyles({
>>>
>>>           top : this.getPaddingTop() || 0,
>>>
>>>           left : this.getPaddingLeft() || 0
>>>
>>>         });
>>>
>>>       } else {
>>>
>>>         element.setPadding(this.getPaddingLeft() || 0, this.getPaddingTop()
>>> || 0);
>>>
>>>       }
>>>
>>>
>>>     },
>>>
>>>
>>>     renderLayout : function(left, top, width, height) {
>>>
>>>       this.base(arguments, left, top, width, height);
>>>
>>>
>>>       var element = this.getContentElement();
>>>
>>>       if (this.__wrapper) {
>>>
>>>         element.getChild(0).setStyles({
>>>
>>>           width : width - (this.getPaddingLeft() || 0) - 
>>> (this.getPaddingRight()
>>> || 0),
>>>
>>>           height : height - (this.getPaddingTop() || 0) - 
>>> (this.getPaddingBottom()
>>> || 0),
>>>
>>>           top : this.getPaddingTop() || 0,
>>>
>>>           left : this.getPaddingLeft() || 0
>>>
>>>         });
>>>
>>>       }
>>>
>>>     },
>>>
>>>
>>>     /*
>>>
>>>      *
>>> ---------------------------------------------------------------------------
>>>
>>>      * IMAGE API
>>>
>>>      *
>>> ---------------------------------------------------------------------------
>>>
>>>      */
>>>
>>>
>>>     // property apply, overridden
>>>
>>>     _applyEnabled : function(value, old) {
>>>
>>>       this.base(arguments, value, old);
>>>
>>>
>>>       if (this.getSource()) {
>>>
>>>         this._styleSource();
>>>
>>>       }
>>>
>>>     },
>>>
>>>
>>>     // property apply
>>>
>>>     _applySource : function(value) {
>>>
>>>       this._styleSource();
>>>
>>>     },
>>>
>>>
>>>     // property apply
>>>
>>>     _applyScale : function(value) {
>>>
>>>       this._styleSource();
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Remembers the mode to keep track which contentElement is
>>> currently in
>>>
>>>      * use.
>>>
>>>      *
>>>
>>>      * @param mode
>>>
>>>      *          {String} internal mode (alphaScaled|scaled|nonScaled)
>>>
>>>      */
>>>
>>>     __setMode : function(mode) {
>>>
>>>       this.__mode = mode;
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Returns the current mode if set. Otherwise checks the current
>>> source and
>>>
>>>      * the current scaling to determine the current mode.
>>>
>>>      *
>>>
>>>      * @return {String} current internal mode
>>>
>>>      */
>>>
>>>     __getMode : function() {
>>>
>>>       if (this.__mode == null) {
>>>
>>>         var source = this.getSource();
>>>
>>>         var isPng = false;
>>>
>>>         if (source != null) {
>>>
>>>           isPng = qx.lang.String.endsWith(source, ".png");
>>>
>>>         }
>>>
>>>
>>>         if (this.getScale() && isPng && qx.core.Environment.get(
>>> "css.alphaimageloaderneeded")) {
>>>
>>>           this.__mode = "alphaScaled";
>>>
>>>         } else if (this.getScale()) {
>>>
>>>           this.__mode = "scaled";
>>>
>>>         } else {
>>>
>>>           this.__mode = "nonScaled";
>>>
>>>         }
>>>
>>>       }
>>>
>>>
>>>       return this.__mode;
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Creates a contentElement suitable for the current mode
>>>
>>>      *
>>>
>>>      * @param mode
>>>
>>>      *          {String} internal mode
>>>
>>>      * @return {qx.html.Image} suitable image content element
>>>
>>>      */
>>>
>>>     __createSuitableContentElement : function(mode) {
>>>
>>>       var scale;
>>>
>>>       var tagName;
>>>
>>>       if (mode == "alphaScaled") {
>>>
>>>         scale = true;
>>>
>>>         tagName = "div";
>>>
>>>       } else if (mode == "nonScaled") {
>>>
>>>         scale = false;
>>>
>>>         tagName = "div";
>>>
>>>       } else {
>>>
>>>         scale = true;
>>>
>>>         tagName = "img";
>>>
>>>       }
>>>
>>>
>>>       var element = new qx.html.Image(tagName);
>>>
>>>       element.setAttribute("$$widget", this.toHashCode());
>>>
>>>       element.setScale(scale);
>>>
>>>       element.setStyles({
>>>
>>>         "overflowX" : "hidden",
>>>
>>>         "overflowY" : "hidden",
>>>
>>>         "boxSizing" : "border-box"
>>>
>>>       });
>>>
>>>
>>>       if (qx.core.Environment.get("css.alphaimageloaderneeded")) {
>>>
>>>         var wrapper = this.__wrapper = new qx.html.Element("div");
>>>
>>>         wrapper.setAttribute("$$widget", this.toHashCode());
>>>
>>>         wrapper.setStyle("position", "absolute");
>>>
>>>         wrapper.add(element);
>>>
>>>         return wrapper;
>>>
>>>       }
>>>
>>>
>>>       return element;
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Returns a contentElement suitable for the current mode
>>>
>>>      *
>>>
>>>      * @return {qx.html.Image} suitable image contentElement
>>>
>>>      */
>>>
>>>     __getSuitableContentElement : function() {
>>>
>>>       if (this.$$disposed) {
>>>
>>>         return null;
>>>
>>>       }
>>>
>>>
>>>       var mode = this.__getMode();
>>>
>>>
>>>       if (this.__contentElements[mode] == null) {
>>>
>>>         this.__contentElements[mode] = this
>>> .__createSuitableContentElement(mode);
>>>
>>>       }
>>>
>>>
>>>       var element = this.__contentElements[mode];
>>>
>>>
>>>       if (!this.__currentContentElement) {
>>>
>>>         this.__currentContentElement = element;
>>>
>>>       }
>>>
>>>
>>>       return element;
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Applies the source to the clipped image instance or preload an
>>> image to
>>>
>>>      * detect sizes and apply it afterwards.
>>>
>>>      *
>>>
>>>      */
>>>
>>>     _styleSource : function() {
>>>
>>>       var source = qx.util.AliasManager.getInstance().resolve(this
>>> .getSource());
>>>
>>>
>>>       var element = this.getContentElement();
>>>
>>>       if (this.__wrapper) {
>>>
>>>         element = element.getChild(0);
>>>
>>>       }
>>>
>>>
>>>       if (!source) {
>>>
>>>         element.resetSource();
>>>
>>>         return;
>>>
>>>       }
>>>
>>>
>>>       this.__checkForContentElementSwitch(source);
>>>
>>>
>>>       if ((qx.core.Environment.get("engine.name") == "mshtml")
>>>
>>>           && (parseInt(qx.core.Environment.get("engine.version"), 10) <
>>> 9 || qx.core.Environment.get("browser.documentmode") < 9)) {
>>>
>>>         var repeat = this.getScale() ? "scale" : "no-repeat";
>>>
>>>         element.tagNameHint =
>>> qx.bom.element.Decoration.getTagName(repeat, source);
>>>
>>>       }
>>>
>>>
>>>       var contentEl = this.__currentContentElement;
>>>
>>>       if (this.__wrapper) {
>>>
>>>         contentEl = contentEl.getChild(0);
>>>
>>>       }
>>>
>>>
>>>       // Detect if the image registry knows this image
>>>
>>>       if (qx.util.ResourceManager.getInstance().has(source)) {
>>>
>>>         this.__setManagedImage(contentEl, source);
>>>
>>>       } else if (qx.io.ImageLoader.isLoaded(source)) {
>>>
>>>         this.__setUnmanagedImage(contentEl, source);
>>>
>>>       } else {
>>>
>>>         this.__loadUnmanagedImage(contentEl, source);
>>>
>>>       }
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Checks if the current content element is capable to display the
>>> image
>>>
>>>      * with the current settings (scaling, alpha PNG)
>>>
>>>      *
>>>
>>>      * @param source
>>>
>>>      *          {String} source of the image
>>>
>>>      */
>>>
>>>     __checkForContentElementSwitch : qx.core.Environment.select("
>>> engine.name", {
>>>
>>>       "mshtml" : function(source) {
>>>
>>>         var alphaImageLoader = qx.core.Environment.get(
>>> "css.alphaimageloaderneeded");
>>>
>>>         var isPng = qx.lang.String.endsWith(source, ".png");
>>>
>>>
>>>         if (alphaImageLoader && isPng) {
>>>
>>>           if (this.getScale() && this.__getMode() != "alphaScaled") {
>>>
>>>             this.__setMode("alphaScaled");
>>>
>>>           } else if (!this.getScale() && this.__getMode() != "nonScaled")
>>> {
>>>
>>>             this.__setMode("nonScaled");
>>>
>>>           }
>>>
>>>         } else {
>>>
>>>           if (this.getScale() && this.__getMode() != "scaled") {
>>>
>>>             this.__setMode("scaled");
>>>
>>>           } else if (!this.getScale() && this.__getMode() != "nonScaled")
>>> {
>>>
>>>             this.__setMode("nonScaled");
>>>
>>>           }
>>>
>>>         }
>>>
>>>
>>>         this.__checkForContentElementReplacement(this
>>> .__getSuitableContentElement());
>>>
>>>       },
>>>
>>>
>>>       "default" : function(source) {
>>>
>>>         if (this.getScale() && this.__getMode() != "scaled") {
>>>
>>>           this.__setMode("scaled");
>>>
>>>         } else if (!this.getScale() && this.__getMode("nonScaled")) {
>>>
>>>           this.__setMode("nonScaled");
>>>
>>>         }
>>>
>>>
>>>         this.__checkForContentElementReplacement(this
>>> .__getSuitableContentElement());
>>>
>>>       }
>>>
>>>     }),
>>>
>>>
>>>     /**
>>>
>>>      * Checks the current child and replaces it if necessary
>>>
>>>      *
>>>
>>>      * @param elementToAdd
>>>
>>>      *          {qx.html.Image} content element to add
>>>
>>>      */
>>>
>>>     __checkForContentElementReplacement : function(elementToAdd) {
>>>
>>>       var currentContentElement = this.__currentContentElement;
>>>
>>>
>>>       if (currentContentElement != elementToAdd) {
>>>
>>>         if (currentContentElement != null) {
>>>
>>>           var pixel = "px";
>>>
>>>           var styles = {};
>>>
>>>
>>>           // Copy dimension and location of the current content element
>>>
>>>           var bounds = this.getBounds();
>>>
>>>           if (bounds != null) {
>>>
>>>             styles.width = bounds.width + pixel;
>>>
>>>             styles.height = bounds.height + pixel;
>>>
>>>           }
>>>
>>>
>>>           var insets = this.getInsets();
>>>
>>>           styles.left = parseInt(currentContentElement.getStyle("left")
>>> || insets.left) + pixel;
>>>
>>>           styles.top = parseInt(currentContentElement.getStyle("top")
>>> || insets.top) + pixel;
>>>
>>>
>>>           styles.zIndex = 10;
>>>
>>>
>>>           var newEl = this.__wrapper ? elementToAdd.getChild(0) :
>>> elementToAdd;
>>>
>>>           newEl.setStyles(styles, true);
>>>
>>>           newEl.setSelectable(this.getSelectable());
>>>
>>>
>>>           var container = currentContentElement.getParent();
>>>
>>>
>>>           if (container) {
>>>
>>>             var index =
>>> container.getChildren().indexOf(currentContentElement);
>>>
>>>             container.removeAt(index);
>>>
>>>             container.addAt(elementToAdd, index);
>>>
>>>           }
>>>
>>>           // force re-application of source so __setSource is called
>>> again
>>>
>>>           var hint = newEl.getNodeName();
>>>
>>>           newEl.setSource(null);
>>>
>>>           var currentEl = this.__wrapper ? 
>>> this.__currentContentElement.getChild(0)
>>> : this.__currentContentElement;
>>>
>>>           newEl.tagNameHint = hint;
>>>
>>>           newEl.setAttribute("class", currentEl.getAttribute("class"));
>>>
>>>
>>>           // Flush elements to make sure the DOM elements are created.
>>>
>>>           qx.html.Element.flush();
>>>
>>>           var currentDomEl = currentEl.getDomElement();
>>>
>>>           var newDomEl = elementToAdd.getDomElement();
>>>
>>>           if (currentDomEl && newDomEl) {
>>>
>>>             // Switch the DOM elements' hash codes. This is required
>>> for the
>>>
>>>             // event
>>>
>>>             // layer to work [BUG #7447]
>>>
>>>             var currentHash = currentDomEl.$$hash;
>>>
>>>             currentDomEl.$$hash = newDomEl.$$hash;
>>>
>>>             newDomEl.$$hash = currentHash;
>>>
>>>           }
>>>
>>>
>>>           this.__currentContentElement = elementToAdd;
>>>
>>>         }
>>>
>>>       }
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Use the ResourceManager to set a managed image
>>>
>>>      *
>>>
>>>      * @param el
>>>
>>>      *          {Element} image DOM element
>>>
>>>      * @param source
>>>
>>>      *          {String} source path
>>>
>>>      */
>>>
>>>     __setManagedImage : function(el, source) {
>>>
>>>       var ResourceManager = qx.util.ResourceManager.getInstance();
>>>
>>>
>>>       // Try to find a disabled image in registry
>>>
>>>       if (!this.getEnabled()) {
>>>
>>>         var disabled = source.replace(/\.([a-z]+)$/, "-disabled.$1");
>>>
>>>         if (ResourceManager.has(disabled)) {
>>>
>>>           source = disabled;
>>>
>>>           this.addState("replacement");
>>>
>>>         } else {
>>>
>>>           this.removeState("replacement");
>>>
>>>         }
>>>
>>>       }
>>>
>>>
>>>       // Optimize case for enabled changes when no disabled image was
>>> found
>>>
>>>       if (el.getSource() === source) {
>>>
>>>         return;
>>>
>>>       }
>>>
>>>
>>>       // Apply source
>>>
>>>       this.__setSource(el, source);
>>>
>>>
>>>       // Compare with old sizes and relayout if necessary
>>>
>>>       this.__updateContentHint(ResourceManager.getImageWidth(source),
>>> ResourceManager.getImageHeight(source));
>>>
>>>     },
>>>
>>>
>>>     /**
>>>
>>>      * Use the infos of the
>>>
>> ------------------------------------------------------------------------------
>> Site24x7 APM Insight: Get Deep Visibility into Application Performance APM
>> + Mobile APM + RUM: Monitor 3 App instances at just $35/Month Monitor
>> end-to-end web transactions and take corrective actions now Troubleshoot
>> faster and improve end-user experience. Signup Now!
>> http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140_______________________________________________
>> qooxdoo-devel mailing list qooxdoo-devel@lists.sourceforge.net
>> https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
>>
>> ------------------------------------------------------------------------------
>> Site24x7 APM Insight: Get Deep Visibility into Application Performance
>> APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month
>> Monitor end-to-end web transactions and take corrective actions now
>> Troubleshoot faster and improve end-user experience. Signup Now!
>> http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140
>> _______________________________________________
>> qooxdoo-devel mailing list
>> qooxdoo-devel@lists.sourceforge.net
>> https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
>>
>
>
> ------------------------------------------------------------------------------
> Site24x7 APM Insight: Get Deep Visibility into Application Performance
> APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month
> Monitor end-to-end web transactions and take corrective actions now
> Troubleshoot faster and improve end-user experience. Signup Now!
> http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140
> _______________________________________________
> qooxdoo-devel mailing list
> qooxdoo-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel
>
>
------------------------------------------------------------------------------
Site24x7 APM Insight: Get Deep Visibility into Application Performance
APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month
Monitor end-to-end web transactions and take corrective actions now
Troubleshoot faster and improve end-user experience. Signup Now!
http://pubads.g.doubleclick.net/gampad/clk?id=267308311&iu=/4140
_______________________________________________
qooxdoo-devel mailing list
qooxdoo-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel

Reply via email to