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