    /*

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

    */
    Ext.define('Siesta.Recorder.UI.Model.Action', {
        extend      : 'Ext.data.TreeModel',

        fields      : Object.keys ? Object.keys(Siesta.Recorder.Action.meta.getAttributes().properties) : [],

        actionClass : Siesta.Recorder.Action,

        $action     : null,

        // initialized in the prototype (see below), shared by all instances
        formatStringHelper      : null,

        constructor : function (data) {
            if (!data.children && !('leaf' in data) && !data.root) data.leaf = true;

            // surprisingly the change in "data" variable will be reflected in "arguments"
            this.callParent([ data ]);

            if (data && !data.root) {
                var action      = data;

                if (!(action instanceof this.actionClass)) {
                    action      = new this.actionClass(action)
                }

                this.$action    = action;

                Ext.applyIf(action, this.data);

                this.data       = action;
            }

        },

        getReadableStringWithModifierKeys : function (text, options) {
            options = options || {};

            var modifierKeys = '';
            var modifierRe   = /CTRL|CMD|SHIFT|ALT/;
            var ctrlKey      = options.ctrlKey,
                metaKey      = options.metaKey && Ext.isMac,
                shiftKey     = options.shiftKey,
                altKey       = options.altKey;

            if (altKey) {
                modifierKeys = '[ALT]';
            }

            if (ctrlKey) {
                modifierKeys = '[CTRL]';
            }

            if (metaKey) {
                modifierKeys += '[CMD]';
            }

            if (shiftKey) {
                modifierKeys += '[SHIFT]';
            }

            // Prepend modifier keys to all chars typed
            if (modifierKeys) {
                var keys            = this.formatStringHelper.extractKeysAndSpecialKeys(text);

                var prependedKeys   = keys.map(function (character) {
                    // Don't prepend recorded special keys
                    if (!modifierRe.test(character)) {
                        // should use single quotes
                        return "<span class='modifierkeys'>" + modifierKeys + '</span>' + Ext.String.htmlEncode(character);
                    }

                    return '';
                });

                text                = prependedKeys.join('');
            }

            return text;
        }
    //    ,

    //    setTargetByType : function (targetType, target) {
    //        return this.$action.setTargetByType()
    //    },

    //    resetValues : function () {
    //        this.$action.resetValues()
    //
    //        this.afterEdit([ 'targets', 'value', '__offset__' ])
    //    },
    //
    //
    //    clearTargetOffset : function () {
    //        this.$action.clearTargetOffset()
    //
    //        this.afterEdit([ 'targets' ])
    //    },
    //
    //
    //    setTargetOffset : function (value) {
    //        this.$action.setTargetOffset(value)
    //
    //        this.afterEdit([ '__offset__' ])
    //    }

    }, function () {
        var prototype   = this.prototype

        // anonymous class that consumes the "CanFormatStrings" role
        var cls         = Class({ does : Siesta.Util.Role.CanFormatStrings })

        // anonymous helper instance of anonymous class :)
        prototype.formatStringHelper = new cls()

        //var attributeNames  = [];
        //Siesta.Recorder.Action.meta.getAttributes().each(function(attr){ attributeNames.push(attr.name)});
        //
        //if (this.addFields) {
        //    this.addFields(attributeNames);
        //} else {
        //    var fields      = prototype.fields
        //    fields.addAll(attributeNames);
        //}

        Joose.A.each([
            'getTargetOffset', 'isMouseAction', 'parseOffset', 'getTarget', 'getTargets', 'hasTarget', 'asStep', 'asCode'
        ], function (methodName) {
            prototype[ methodName ] = function () {
                return this.$action[ methodName ].apply(this.$action, arguments)
            }
        })

        Joose.O.each({
            clearTargetOffset       : [ 'target' ],
            setTargetOffset         : [ 'target' ],
            resetValues             : [ 'target', 'value' ],
            setAction               : [ 'action', 'target' ]
        }, function (fields, methodName) {
            prototype[ methodName ] = function () {
                var res     = this.$action[ methodName ].apply(this.$action, arguments)

                // TODO not needed since we do refreshNode
                //this.afterEdit(fields)

                return res
            }
        })
    });