<commit_before>/**
工作区面板的view
@module
@exports 工作区面板的view实例
**/
define([
    'jquery', 'underscore', 'backbone',
    'tmpl!html/panel.workspace.bone.html', 'tmpl!html/workspace.html', 'tmpl!html/panel.workspace.transformUtil.html',
    'view.panel',
    'base/math'
], function(
    $, _, Backbone,
    boneTmpl, workspaceTmpl, transformUtilTmpl,
    PanelView,
    math
){
    var WorkspacePanelView, BoneView;

    // 减少搜索作用域链的局部变量
    var win = window,
        Math = win.Math;

    /**
    @class WorkspacePanelView
    @extends PanelView
    **/
    var WorkspacePanelView = PanelView.extend({

        // 使用DOM中已有的元素作为此view的根元素
        el: '#js-workspacePanel',

        initialize: function(){
            // 复用父类的initialize方法
            this.constructor.__super__.initialize.apply(this, arguments);

            // 此面板中所有骨骼view构成的hash，用骨骼的id索引
            this._boneHash = {};

            // 当前被激活的骨骼
            // **只要有骨骼，总有一个骨骼处于激活状态**
            this._activeBone = null;
            // 重置调节骨骼时的状态表示
            this._resetState();

            // 保存在实例上，避免搜索作用域链，尤其是在频繁调用的函数中
            this._cos = Math.cos;
            this._sin = Math.sin;
            this._atan = Math.atan;
            this._pow = Math.pow;
            this._180_DIV_PI = 180 / Math.PI;
            this._PI_DIV_180 = Math.PI / 180;
            this._stringify = win.JSON.stringify;
            this._rotationAngle = math.rotationAngle;
        },

        /**
        渲染此面板
        @method render
        @param {Array} [bonesData] 多个骨骼的的当前数据
        **/
        render: function(bonesData){
            // 渲染空面板
            this.$el.html( workspaceTmpl());

            // 如果有传入骨架数据，渲染出骨架view
            if(bonesData && bonesData.length){
                bonesData.forEach(function(boneData){
                    this.addBone(boneData);
                }, this);
            }
            // 缓存DOM元素：
            // 工作区面板的坐标系元素，其位置表示坐标系原点，
            // x轴水平向右，y轴竖直向下，
            // 骨骼的坐标就是相对于这个坐标系而言的
            this._$coordSys = this.$el.children('.js-coordinate-system');

            return this;
        },


        /* Start: 对本面板中的骨骼的增删改 */

        /**
        如果有激活骨骼，用所给的数据创建骨骼并添加为激活骨骼的子骨骼。
        如果没有激活骨骼（即还没有任何骨骼），添加此骨骼并激活之。
        如果所添加骨骼的数据中带有子骨骼的数据，递归添加子骨骼。
        @param {Object} boneData 所添加的骨骼的数据
        @param {Object} [options]
        @return {BoneView} 新创建的实例
        **/
        addBone: function(boneData, options){
            var bone, childrenData;

            if(this._activeBone){
                bone = this._addBone(boneData, this._activeBone, options);
            }
            else{
                bone = this._addBone(boneData, options);
                this.changeActiveBone(bone.id);
            }
            return bone;
        },

        /**
        彻底移除指定的骨骼。
        如果指定的骨骼有子骨骼，先递归移除子骨骼。
        @param {String} id 骨骼的id
        @param {Object} [options]
        @return {BoneView[]} 所移除的骨骼组成的数组。移除的过程中会断开它们的父子关系
        **/
        removeBone: function(id, options){
            var bone, children, removedBones;

            bone = this._getBone(id, options);
            removedBones = [];
            if( (children = bone.children) && children.length ){
                children.forEach(function(child){
                    removedBones.concat( this.removeBone(child.id, options) );
                }, this);
            }

            bone.remove();
            removedBones.push(bone);

            delete this._boneHash[id];
            return removedBones;
        },

        /**
        更新此面板中的某个骨骼
        @param {String} id 骨骼的id
        @param {Object} data 要更新的数据
        @param {Object} options
        @return this
        **/
        updateBone: function(id, data, options){
            this._getBone(id, options)
                .update(data);
            return this;
        },

        /* End: 对本面板中的骨骼的增删改 */

        // 配置要委派的DOM事件
        events: {
            // 这个函数是是需要的,不然会按照浏览器的默认行为
            'dragover': function(){ return false },
            'drop': 'onDrop',
            'click': 'onClick',
            'mousedown .js-activeBone': 'onMouseDownActiveBone',
            'mousedown .js-resize': 'onMouseDownResizePoint',
            'mousedown .js-rotate': 'onMouseDownRotatePoint',
            'mousedown .js-joint': 'onMouseDownJoint',
            'mousemove': 'onMouseMove',
            'mouseup': 'onMouseUp'
        },

        onDrop: function(e){
            var panel = this,
                files, reader, i;

            //这里有个注意的地方，jquery帮你封event，而且里面居然没有我们需要的数据
            e = window.event;
            e.stopPropagation();
            e.preventDefault();
           
            files = e.dataTransfer.files;
            for(i = 0; i < files.length; i++){
                reader = new FileReader();
                // 先监听事件，再读取数据，等待事件触发
                reader.onload = onload;
                reader.readAsDataURL(files[i]);
            }

            this.$('#empty_wording').hide();

            /**
            写成函数声明而不是函数表达式，以免每循环一次重复创建一个函数对象
            @triggerObj {FileReader} `reader`
            @event onload
            **/
            function onload(){
                // 使用 `this` 即可访问到触发 `onload` 事件的 `reader`
                var texture = this.result,
                    img = new Image(),
                    boneData;
                img.src = texture;
                boneData = {
                    texture: texture
                };
                if(img.width){
                    boneData.w = img.width;
                    boneData.jointX = img.width / 2;
                }
                if(img.height){
                    boneData.h = img.height;
                    boneData.jointY = img.height / 2;
                }

                // 通知外界
                panel.trigger('addBone', boneData);
            }
        },

        onClick: function($event){
            var $target = $($event.target),
                $bone;

            // 当点击此面板时，如果点击了某个骨骼，改变激活元素为所点击的元素。
            // **只要有骨骼，总有一个骨骼处于激活状态**
            if( ( $target.hasClass('js-bone') && ($bone = $target) ) ||
                ( $bone = $target.parentsUntil(this.$el, '.js-bone') ).length
            ){
                this.changeActiveBone( boneHtmlId2boneId($bone.attr('id')) );
            }
        },

        onMouseDownActiveBone: function($event){
            var bone;

            // 不是鼠标左键，直接返回，避免不必要的运算
            if($event.which !== 1) return;

            console.debug('Start adjust active bone: move');<commit_msg>When drop image on workspace panel, use current active bone as parent<commit_after>/**
工作区面板的view
@module
@exports 工作区面板的view实例
**/
define([
    'jquery', 'underscore', 'backbone',
    'tmpl!html/panel.workspace.bone.html', 'tmpl!html/workspace.html', 'tmpl!html/panel.workspace.transformUtil.html',
    'view.panel',
    'base/math'
], function(
    $, _, Backbone,
    boneTmpl, workspaceTmpl, transformUtilTmpl,
    PanelView,
    math
){
    var WorkspacePanelView, BoneView;

    // 减少搜索作用域链的局部变量
    var win = window,
        Math = win.Math;

    /**
    @class WorkspacePanelView
    @extends PanelView
    **/
    var WorkspacePanelView = PanelView.extend({

        // 使用DOM中已有的元素作为此view的根元素
        el: '#js-workspacePanel',

        initialize: function(){
            // 复用父类的initialize方法
            this.constructor.__super__.initialize.apply(this, arguments);

            // 此面板中所有骨骼view构成的hash，用骨骼的id索引
            this._boneHash = {};

            // 当前被激活的骨骼
            // **只要有骨骼，总有一个骨骼处于激活状态**
            this._activeBone = null;
            // 重置调节骨骼时的状态表示
            this._resetState();

            // 保存在实例上，避免搜索作用域链，尤其是在频繁调用的函数中
            this._cos = Math.cos;
            this._sin = Math.sin;
            this._atan = Math.atan;
            this._pow = Math.pow;
            this._180_DIV_PI = 180 / Math.PI;
            this._PI_DIV_180 = Math.PI / 180;
            this._stringify = win.JSON.stringify;
            this._rotationAngle = math.rotationAngle;
        },

        /**
        渲染此面板
        @method render
        @param {Array} [bonesData] 多个骨骼的的当前数据
        **/
        render: function(bonesData){
            // 渲染空面板
            this.$el.html( workspaceTmpl());

            // 如果有传入骨架数据，渲染出骨架view
            if(bonesData && bonesData.length){
                bonesData.forEach(function(boneData){
                    this.addBone(boneData);
                }, this);
            }
            // 缓存DOM元素：
            // 工作区面板的坐标系元素，其位置表示坐标系原点，
            // x轴水平向右，y轴竖直向下，
            // 骨骼的坐标就是相对于这个坐标系而言的
            this._$coordSys = this.$el.children('.js-coordinate-system');

            return this;
        },


        /* Start: 对本面板中的骨骼的增删改 */

        /**
        如果有激活骨骼，用所给的数据创建骨骼并添加为激活骨骼的子骨骼。
        如果没有激活骨骼（即还没有任何骨骼），添加此骨骼并激活之。
        如果所添加骨骼的数据中带有子骨骼的数据，递归添加子骨骼。
        @param {Object} boneData 所添加的骨骼的数据
        @param {Object} [options]
        @return {BoneView} 新创建的实例
        **/
        addBone: function(boneData, options){
            var bone, childrenData;

            if(this._activeBone){
                bone = this._addBone(boneData, this._activeBone, options);
            }
            else{
                bone = this._addBone(boneData, options);
                this.changeActiveBone(bone.id);
            }
            return bone;
        },

        /**
        彻底移除指定的骨骼。
        如果指定的骨骼有子骨骼，先递归移除子骨骼。
        @param {String} id 骨骼的id
        @param {Object} [options]
        @return {BoneView[]} 所移除的骨骼组成的数组。移除的过程中会断开它们的父子关系
        **/
        removeBone: function(id, options){
            var bone, children, removedBones;

            bone = this._getBone(id, options);
            removedBones = [];
            if( (children = bone.children) && children.length ){
                children.forEach(function(child){
                    removedBones.concat( this.removeBone(child.id, options) );
                }, this);
            }

            bone.remove();
            removedBones.push(bone);

            delete this._boneHash[id];
            return removedBones;
        },

        /**
        更新此面板中的某个骨骼
        @param {String} id 骨骼的id
        @param {Object} data 要更新的数据
        @param {Object} options
        @return this
        **/
        updateBone: function(id, data, options){
            this._getBone(id, options)
                .update(data);
            return this;
        },

        /* End: 对本面板中的骨骼的增删改 */

        // 配置要委派的DOM事件
        events: {
            // 这个函数是是需要的,不然会按照浏览器的默认行为
            'dragover': function(){ return false },
            'drop': 'onDrop',
            'click': 'onClick',
            'mousedown .js-activeBone': 'onMouseDownActiveBone',
            'mousedown .js-resize': 'onMouseDownResizePoint',
            'mousedown .js-rotate': 'onMouseDownRotatePoint',
            'mousedown .js-joint': 'onMouseDownJoint',
            'mousemove': 'onMouseMove',
            'mouseup': 'onMouseUp'
        },

        /**
        当把图片拖拽放到工作区面板中时，读取图片的data url、宽高作为纹理图的url、骨骼的宽高，
        如果当前有激活元素，使用激活元素作为父骨骼。
        最后抛出 `addBone` 事件给controller以创建骨骼，带上骨骼的数据。
        TODO: 考虑把事件名改为 `dropTexture` ，因为 `addBone` 有“骨骼视图已添加”的含义
        TODO: 检验拖拽进来的文件是否图片文件
        TODO: 考虑优化，data url数据量太大了，直接设置到DOM属性中对性能不友好
        @triggerObj `workspacePanelView.$el` 工作区面板的根DOM元素
        @event drop DOM事件drop
        **/
        onDrop: function(e){
            var panel = this,
                files, reader, i;

            //这里有个注意的地方，jquery帮你封event，而且里面居然没有我们需要的数据
            e = window.event;
            e.stopPropagation();
            e.preventDefault();
           
            files = e.dataTransfer.files;
            for(i = 0; i < files.length; i++){
                reader = new FileReader();
                // 先监听事件，再读取数据，等待事件触发
                reader.onload = onload;
                reader.readAsDataURL(files[i]);
            }

            this.$('#empty_wording').hide();

            /**
            写成函数声明而不是函数表达式，以免每循环一次重复创建一个函数对象
            @triggerObj {FileReader} `reader`
            @event onload
            **/
            function onload(){
                // 使用 `this` 即可访问到触发 `onload` 事件的 `reader`
                var texture = this.result,
                    img = new Image(),
                    boneData,
                    activeBone;

                img.src = texture;
                boneData = {
                    texture: texture
                };
                if(img.width){
                    boneData.w = img.width;
                    boneData.jointX = img.width / 2;
                }
                if(img.height){
                    boneData.h = img.height;
                    boneData.jointY = img.height / 2;
                }
                if(activeBone = panel._activeBone){
                    boneData.parent = activeBone.id;
                }

                // 通知外界
                panel.trigger('addBone', boneData);
            }
        },

        onClick: function($event){
            var $target = $($event.target),
                $bone;

            // 当点击此面板时，如果点击了某个骨骼，改变激活元素为所点击的元素。
            // **只要有骨骼，总有一个骨骼处于激活状态**
            if( ( $target.hasClass('js-bone') && ($bone = $target) ) ||
                ( $bone = $target.parentsUntil(this.$el, '.js-bone') ).length
            ){
                this.changeActiveBone( boneHtmlId2boneId($bone.attr('id')) );
            }
        },

        onMouseDownActiveBone: function($event){
            var bone;

            // 不是鼠标左键，直接返回，避免不必要的运算
            if($event.which !== 1) return;

            console.debug('Start adjust active bone: move');
{'fraction_non_alphanumeric': 0.11530582382206614, 'fraction_numerical': 0.0024875621890547263, 'mean_word_length': 1.4192920353982301, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5547730', 'n_tokens_mistral': 5006, 'n_tokens_neox': 5123, 'n_words': 680}