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

Reply via email to