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