    /*

    Siesta 5.3.0
    Copyright(c) 2009-2019 Bryntum AB
    https://bryntum.com/contact
    https://bryntum.com/products/siesta/license

    */
    Ext.define('Siesta.Project.Browser.UI.DomContainer', {
        extend : 'Ext.Panel',
        alias  : 'widget.domcontainer',

        requires                : [
            'Siesta.Project.Browser.UI.ComponentInspector'
        ],

        cls                     : 'siesta-domcontainer',
        header                  : false,
        collapsible             : true,
        animCollapse            : false,
        padding                 : 10,

        test                    : null,
        testListeners           : null,

        scaleToFit              : true,
        scaleToFitMode          : 'full',

        maintainViewportSize    : true,
        canManageDOM            : true,
        suspendAfterLayoutAlign : false,

        inspector               : null,
        inspectorConfig         : null,
        enableConsole           : true,

        trackMouse              : true,

        initComponent : function () {
            var me = this;

            this.testListeners  = []

            this.title          = Siesta.Resource('Siesta.Project.Browser.UI.DomContainer', 'title');

            //this.addEvents(
            //    'inspectionstart',
            //    'inspectionstop',
            //    'targethover',
            //    'targetselected'
            //)

            if (this.enableConsole) {
                Ext.apply(this, {
                    dockedItems : this.consoleCt = {
                        xtype     : 'component',
                        dock      : 'bottom',
                        hidden    : true,
                        height    : 20,
                        cls       : 'domcontainer-console',
                        renderTpl : '<div><input type="text" /></div>'
                    }
                });
            }

            this.callParent()

            this.on({
                afterlayout : this.onAfterLayout,
                expand      : this.onExpand,
                collapse    : this.onCollapse,

                scope : this
            });

            this.inspector = new Siesta.Project.Browser.UI.ComponentInspector(this.inspectorConfig);

            if (this.trackMouse) {
                this.inspector.on({
                    start : this.onInspectionStart,
                    stop  : this.onInspectionStop,
                    scope : this
                });
            }

            this.relayEvents(this.inspector, ['start', 'stop'], 'inspection')

            this.enableBubble('statechange');
        },

        afterRender : function () {
            this.callParent(arguments);

            if (this.enableConsole) {
                var input = this.consoleInput = this.el.down('.domcontainer-console input');

                this.inspector.on('targethover', function (dc, cmp) {
                    input.dom.value = 'Ext.getCmp("' + cmp.id + '").';
                });

                this.inspector.on('targetselected', function (dc, cmp) {
                    input.focus(true);
                });

                input.on({
                    keyup : function (e, t) {
                        var val = input.dom.value;

                        if (e.getKey() === e.ENTER && val) {
                            var frame = this.getIFrame();

                            try {
                                var retVal = frame.contentWindow.eval(val);
                                if (window.console) {
                                    console.log(retVal);
                                }
                            } catch (e) {
                                window.console && console.log(e.message);
                            }
                        }
                    },
                    scope : this
                });
            }
        },

        setScaleToFit : function (scaleToFit, mode) {
            if (this.scaleToFit == scaleToFit && (!mode || this.scaleToFitMode == mode)) return

            this.scaleToFit = scaleToFit
            if (mode) this.scaleToFitMode = mode

            if (scaleToFit) {
                this.doScaleToFit();
            } else {
                this.undoScaleToFit();
            }

            this.fireEvent('statechange')
        },

        doScaleToFit : function (isInitial) {
            var iframe      = this.getIFrame();
            if (!iframe) return

            var mode            = this.scaleToFitMode
            var scrollbarSize   = Ext.getScrollbarSize()

            var innerWrapper    = iframe.parentNode
            var scrollCanvas    = innerWrapper.parentNode
            var wrapper         = scrollCanvas.parentNode

            var availableWidth  = this.body.dom.offsetWidth - (mode === 'width' ? scrollbarSize.width : 0)
            var availableHeight = this.body.dom.offsetHeight - (mode === 'height' ? scrollbarSize.height : 0)

            var contentWidth    = iframe.offsetWidth
            var contentHeight   = iframe.offsetHeight

            var widthScale      = availableWidth / contentWidth
            var heightScale     = availableHeight / contentHeight

            var scale           = mode === 'width' ? widthScale :
                                  mode === 'height' ? heightScale :
                                  Math.min(widthScale, heightScale)

            var leftWidth       = availableWidth - scale * contentWidth
            var leftHeight      = availableHeight - scale * contentHeight

            var left            = Math.max(0, leftWidth / 2)
            var top             = Math.max(0, leftHeight / 2)

            innerWrapper.style.transition    = isInitial ? 'none' : 'transform 0.3s'
            innerWrapper.style.transform     = 'translate(' + left + 'px, ' + top + 'px) scale(' + scale + ') ';

            scrollCanvas.style.width    = scale * contentWidth + 'px'
            scrollCanvas.style.height   = scale * contentHeight + 'px'

            this.removeScaleToFitClasses(wrapper)

            Ext.fly(wrapper).addCls('scaleToFit' + Joose.S.uppercaseFirst(mode));

            this.scrollTo(0, 0);

            // the "min" in the name should be removed
            var viewportSize        = this.test.scopeProvider.minViewportSize

            innerWrapper.style.width     = viewportSize.width + 'px'
            innerWrapper.style.height    = viewportSize.height + 'px'
        },

        removeScaleToFitClasses : function (wrapper) {
            Ext.fly(wrapper).removeCls('scaleToFitFull');
            Ext.fly(wrapper).removeCls('scaleToFitWidth');
            Ext.fly(wrapper).removeCls('scaleToFitHeight');
        },

        undoScaleToFit : function () {
            var iframe                  = this.getIFrame();
            if (!iframe) return

            var innerWrapper            = iframe.parentNode
            var scrollCanvas            = innerWrapper.parentNode

            this.removeScaleToFitClasses(scrollCanvas.parentNode)

            innerWrapper.style.transform    = ''
            innerWrapper.style.width        = iframe.style.width
            innerWrapper.style.height       = iframe.style.height
            innerWrapper.style.left         = ''
            innerWrapper.style.top          = ''

            scrollCanvas.style.width        = iframe.style.width
            scrollCanvas.style.height       = iframe.style.height
        },

        setCanManageDOM : function (value) {
            this.canManageDOM = value

            if (value && !this.hidden) this.alignIFrame()
        },

        getIFrameWrapper : function (force) {
            var test = this.test;

            if (test)
                return (this.canManageDOM || force) && test.scopeProvider && test.scopeProvider.wrapper || null
            else
                return null;
        },

        getIFrame : function (force) {
            var test = this.test;

            if (test)
                return (this.canManageDOM || force) && test.scopeProvider && test.scopeProvider.iframe || null
            else
                return null;
        },

        onAfterLayout : function () {
            if (!this.suspendAfterLayoutAlign) this.alignIFrame();
        },

        alignIFrame : function (force, isInitial) {
            var wrapper = this.getIFrameWrapper(force);

            if (!this.isFrameVisible() || !wrapper) return

            Ext.fly(wrapper).removeCls('tr-iframe-hidden')
            Ext.fly(wrapper).removeCls('tr-iframe-forced')

            var box = this.body.getBox()

            Ext.fly(wrapper).setBox(box)

            if (!this.maintainViewportSize) {
                Ext.fly(this.getIFrame(force)).setSize(this.body.getSize())
            }

            if (this.scaleToFit)
                this.doScaleToFit(isInitial)
            else
                this.undoScaleToFit()

            var test = this.test

            test && test.fireEvent('testframeshow')
        },

        onCollapse : function () {
            this.hideIFrame();
        },

        onExpand : function () {
            this.alignIFrame();
        },

        hideIFrame : function () {
            var iframe = this.getIFrameWrapper()

            iframe && Ext.fly(iframe).setStyle({
                left : '-10000px',
                top  : '-10000px'
            })

            var test = this.test

            test && test.fireEvent('testframehide')
        },

        isFrameVisible : function () {
            return !(this.hidden || this.collapsed)
        },

        showTest : function (test) {
            this.stopInspection();

            if (this.test) {
                Joose.A.each(this.testListeners, function (listener) {
                    listener.remove()
                })

                this.testListeners = []

                this.hideIFrame()
            }

            this.test = test

            this.testListeners = [
                test.on('testfinalize', this.onTestFinalize, this)
            ]

            // when starting the test with forcedIframe - do not allow the assertion grid to change the location of the iframe
            // (canManageDOM is set to false)
            this.setCanManageDOM(!test.hasForcedIframe())

            this.alignIFrame(false, true);
        },

        onTestFinalize : function (event, test) {
            this.setCanManageDOM(true)

            // this prevents project from hiding the iframe, because "test.hasForcedIframe()" will return null
            // we've moved the iframe to the correct position, and it can never be "forced" again anyway
            if (this.isFrameVisible()) {
                test.forceDOMVisible    = false
                test.isDOMForced        = false
            }
        },

        destroy                 : function () {
            Ext.destroy(this.boxIndicator);
            this.boxIndicator = null;

            // just in case
            this.hideIFrame()

            Joose.A.each(this.testListeners, function (listener) {
                listener.remove()
            })

            this.test = null

            this.callParent(arguments)
        },

        // BEGIN Inspection related code
        // -----------------------------
        inspectedComponent      : null,
        inspectedComponentXType : null,
        boxIndicator            : null,

        inspecting : false,

        toggleInspectionMode : function (on) {
            if (!this.test) return;

            if (on) {
                this.startInspection();

            } else {
                this.stopInspection();
            }
        },

        startInspection : function (showConsole) {
            if (!this.test || !this.test.global) return;

            var wrap = Ext.get(this.getIFrameWrapper());

            this.inspector.start(this.test.global, wrap.down('.tr-iframe-wrapper-inner', true));

            if (this.trackMouse) {
                wrap.un('mouseout', this.onMouseLeave, this);
                wrap.on('mouseout', this.onMouseLeave, this);
            }

            if (this.enableConsole && showConsole !== false) {
                if (!(this.consoleCt instanceof Ext.Component)) {
                    this.consoleCt = Ext.widget(this.consoleCt);
                }
                this.consoleCt.show();
            }
            this.addCls('inspection-mode');
        },

        stopInspection : function () {
            this.inspector.stop();

            this.removeCls('inspection-mode');

            if (this.enableConsole && this.consoleCt.rendered) {
                this.consoleCt.hide();
            }
        },

        onMouseLeave : function (e, t) {
            if (!this.el.contains(e.relatedTarget) && !Ext.fly(this.getIFrameWrapper()).contains(e.relatedTarget)) {
                this.stopInspection();
            }
        },

        onInspectionStart : function () {
            var wrap = Ext.get(this.getIFrameWrapper());

            if (wrap) {
                wrap.on('mouseout', this.onMouseLeave, this);
            }
        },

        onInspectionStop : function () {
            var wrap = Ext.get(this.getIFrameWrapper());

            if (wrap) {
                wrap.un('mouseout', this.onMouseLeave, this);
            }
        },

        clearHighlight : function () {
            this.stopInspection();
        },

        highlightTarget : function (target, content, offset) {
            var targetIsPoint = target instanceof Array;

            if (!this.inspector.active) {
                this.startInspection();
            }

            if (targetIsPoint) {
                offset = target;
                target = null;
            } else {
                target = this.test.normalizeElement(target, true, true, false);

                if (target) {
                    // Detect if we're trying to highlight a hidden target, then skip the target
                    if (!target.offsetWidth && !target.offsetHeight) {
                        target = offset = null;
                    }
                    else if (offset) {
                        offset = this.test.normalizeOffset(offset, $(target));
                    }
                }
            }

            this.inspector.highlightTarget(target, content, offset);
        }
    });