1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 'use strict'; 6 7 base.requireStylesheet('cc.picture_ops_list_view'); 8 9 base.require('cc.constants'); 10 base.require('cc.selection'); 11 base.require('ui.list_view'); 12 base.require('ui.dom_helpers'); 13 14 base.exportTo('cc', function() { 15 var ANNOTATION = 'Comment'; 16 var BEGIN_ANNOTATION = 'BeginCommentGroup'; 17 var END_ANNOTATION = 'EndCommentGroup'; 18 var ANNOTATION_ID = 'ID: '; 19 var ANNOTATION_CLASS = 'CLASS: '; 20 var ANNOTATION_TAG = 'TAG: '; 21 22 var constants = cc.constants; 23 24 /** 25 * @constructor 26 */ 27 var PictureOpsListView = ui.define('picture-ops-list-view'); 28 29 PictureOpsListView.prototype = { 30 __proto__: HTMLUnknownElement.prototype, 31 32 decorate: function() { 33 this.opsList_ = new ui.ListView(); 34 this.appendChild(this.opsList_); 35 36 this.selectedOp_ = undefined; 37 this.selectedOpIndex_ = undefined; 38 this.opsList_.addEventListener( 39 'selection-changed', this.onSelectionChanged_.bind(this)); 40 41 this.picture_ = undefined; 42 }, 43 44 get picture() { 45 return this.picture_; 46 }, 47 48 set picture(picture) { 49 this.picture_ = picture; 50 this.updateContents_(); 51 }, 52 53 updateContents_: function() { 54 this.opsList_.clear(); 55 56 if (!this.picture_) 57 return; 58 59 var ops = this.picture_.getOps(); 60 if (!ops) 61 return; 62 63 ops = this.opsTaggedWithAnnotations_(ops); 64 65 for (var i = 0; i < ops.length; i++) { 66 var op = ops[i]; 67 var item = document.createElement('div'); 68 item.opIndex = op.opIndex; 69 item.textContent = i + ') ' + op.cmd_string; 70 71 // Display the element info associated with the op, if available. 72 if (op.elementInfo.tag || op.elementInfo.id || op.elementInfo.class) { 73 var elementInfo = document.createElement('span'); 74 elementInfo.classList.add('elementInfo'); 75 var tag = op.elementInfo.tag ? op.elementInfo.tag : 'unknown'; 76 var id = op.elementInfo.id ? 'id=' + op.elementInfo.id : undefined; 77 var className = op.elementInfo.class ? 'class=' + 78 op.elementInfo.class : undefined; 79 elementInfo.textContent = 80 '<' + tag + (id ? ' ' : '') + 81 (id ? id : '') + (className ? ' ' : '') + 82 (className ? className : '') + '>'; 83 item.appendChild(elementInfo); 84 } 85 86 // Display each of the Skia ops. 87 op.info.forEach(function(info) { 88 var infoItem = document.createElement('div'); 89 infoItem.textContent = info; 90 item.appendChild(infoItem); 91 }); 92 93 this.opsList_.appendChild(item); 94 } 95 }, 96 97 onSelectionChanged_: function(e) { 98 var beforeSelectedOp = true; 99 100 // Deselect on re-selection. 101 if (this.opsList_.selectedElement === this.selectedOp_) { 102 this.opsList_.selectedElement = undefined; 103 beforeSelectedOp = false; 104 this.selectedOpIndex_ = undefined; 105 } 106 107 this.selectedOp_ = this.opsList_.selectedElement; 108 109 // Set selection on all previous ops. 110 var ops = this.opsList_.children; 111 for (var i = 0; i < ops.length; i++) { 112 var op = ops[i]; 113 if (op === this.selectedOp_) { 114 beforeSelectedOp = false; 115 this.selectedOpIndex_ = op.opIndex; 116 } else if (beforeSelectedOp) { 117 op.setAttribute('beforeSelection', 'beforeSelection'); 118 } else { 119 op.removeAttribute('beforeSelection'); 120 } 121 } 122 123 base.dispatchSimpleEvent(this, 'selection-changed', false); 124 }, 125 126 get selectedOpIndex() { 127 return this.selectedOpIndex_; 128 }, 129 130 /** 131 * Return Skia operations tagged by annotation. 132 * 133 * The ops returned from Picture.getOps() contain both Skia ops and 134 * annotations threaded together. This function removes all annotations 135 * from the list and tags each op with the associated annotations. 136 * Additionally, the last {tag, id, class} is stored as elementInfo on 137 * each op. 138 * 139 * @param {Array} ops Array of Skia operations and annotations. 140 * @return {Array} Skia ops where op.annotations contains the associated 141 * annotations for a given op. 142 */ 143 opsTaggedWithAnnotations_: function(ops) { 144 // This algorithm works by walking all the ops and pushing any 145 // annotations onto a stack. When a non-annotation op is found, the 146 // annotations stack is traversed and stored with the op. 147 var annotationGroups = new Array(); 148 var opsWithoutAnnotations = new Array(); 149 for (var opIndex = 0; opIndex < ops.length; opIndex++) { 150 var op = ops[opIndex]; 151 op.opIndex = opIndex; 152 switch (op.cmd_string) { 153 case BEGIN_ANNOTATION: 154 annotationGroups.push(new Array()); 155 break; 156 case END_ANNOTATION: 157 annotationGroups.pop(); 158 break; 159 case ANNOTATION: 160 annotationGroups[annotationGroups.length - 1].push(op); 161 break; 162 default: 163 var annotations = new Array(); 164 var elementInfo = {}; 165 annotationGroups.forEach(function(annotationGroup) { 166 elementInfo = {}; 167 annotationGroup.forEach(function(annotation) { 168 annotation.info.forEach(function(info) { 169 if (info.indexOf(ANNOTATION_TAG) != -1) 170 elementInfo.tag = info.substring( 171 info.indexOf(ANNOTATION_TAG) + 172 ANNOTATION_TAG.length).toLowerCase(); 173 else if (info.indexOf(ANNOTATION_ID) != -1) 174 elementInfo.id = info.substring( 175 info.indexOf(ANNOTATION_ID) + 176 ANNOTATION_ID.length); 177 else if (info.indexOf(ANNOTATION_CLASS) != -1) 178 elementInfo.class = info.substring( 179 info.indexOf(ANNOTATION_CLASS) + 180 ANNOTATION_CLASS.length); 181 182 annotations.push(info); 183 }); 184 }); 185 }); 186 op.annotations = annotations; 187 op.elementInfo = elementInfo; 188 opsWithoutAnnotations.push(op); 189 } 190 } 191 return opsWithoutAnnotations; 192 } 193 }; 194 195 return { 196 PictureOpsListView: PictureOpsListView 197 }; 198 }); 199