http://git-wip-us.apache.org/repos/asf/flex-radii8/blob/f370bfcf/Radii8Library/src/com/flexcapacitor/utils/HTMLDocumentExporter.as ---------------------------------------------------------------------- diff --git a/Radii8Library/src/com/flexcapacitor/utils/HTMLDocumentExporter.as b/Radii8Library/src/com/flexcapacitor/utils/HTMLDocumentExporter.as new file mode 100644 index 0000000..c179e86 --- /dev/null +++ b/Radii8Library/src/com/flexcapacitor/utils/HTMLDocumentExporter.as @@ -0,0 +1,1370 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.flexcapacitor.utils { + import com.flexcapacitor.model.IDocument; + import com.flexcapacitor.utils.supportClasses.ComponentDescription; + import com.flexcapacitor.views.supportClasses.Styles; + + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.JPEGEncoderOptions; + import flash.display.PNGEncoderOptions; + import flash.geom.Rectangle; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.getTimer; + + import mx.core.IUIComponent; + import mx.core.IVisualElement; + import mx.core.IVisualElementContainer; + import mx.graphics.codec.JPEGEncoder; + import mx.graphics.codec.PNGEncoder; + import mx.styles.IStyleClient; + import mx.utils.Base64Encoder; + + import spark.components.BorderContainer; + import spark.components.HGroup; + import spark.components.supportClasses.GroupBase; + import spark.layouts.BasicLayout; + import spark.layouts.HorizontalLayout; + import spark.layouts.TileLayout; + import spark.layouts.VerticalLayout; + import spark.utils.BitmapUtil; + + /** + * Exports a document to HTML + * */ + public class HTMLDocumentExporter extends DocumentExporter { + + public function HTMLDocumentExporter() { + + } + + /** + * Sets explicit size regardless if size is explicit + * */ + public var setExplicitSize:Boolean = true; + + /** + * Sets styles inline + * */ + public var useInlineStyles:Boolean; + + /** + * Name of token in the template that is replaced by the + * */ + public var contentToken:String = "<!--template_content-->"; + + /** + * CSS to add to + * */ + public var css:String; + + /** + * Adds border box CSS + * */ + public var borderBoxCSS:String; + + /** + * Show outline + * */ + public var showBordersCSS:String; + + /** + * Zoom CSS + * */ + public var zoomCSS:String; + + /** + * Page zoom level + * */ + public var scaleLevel:Number; + + /** + * CSS for SVG button + * */ + public var buttonCSS:String; + + public var buttonCSS2:String; + + public var stylesheets:String; + + public var template:String; + + /** + * Creates a snapshot of the application and sets it as the background image + * */ + public var showScreenshotBackground:Boolean = false; + + /** + * Alpha of the background image + * */ + public var backgroundImageAlpha:Number = .5; + + /** + * Used to create PNG images + * */ + public var pngEncoder:PNGEncoder; + + /** + * Used to create JPEG images + * */ + public var jpegEncoder:JPEGEncoder; + + /** + * Extension of the document when exporting to a file. + * */ + public var extension:String; + + /** + * Indicates when the user has typed in the text area + * */ + [Bindable] + public var isCodeModifiedByUser:Boolean; + + /** + * Show borders around HTML elements + * */ + [Bindable] + public var showBorders:Boolean; + + /** + * Use SVG button class + * */ + [Bindable] + public var useSVGButtonClass:Boolean = true; + + /** + * Show full HTML page source + * */ + [Bindable] + public var showFullHTMLPageSource:Boolean = false; + + /** + * Last source code + * */ + [Bindable] + public var sourceCode:String; + + public var includePreviewCode:Boolean; + + public var horizontalPositions:Array = ["x","left","right","horizontalCenter"]; + public var horizontalCenterPosition:String = "horizontalCenter"; + public var verticalPositions:Array = ["y","top","bottom","verticalCenter"]; + public var verticalCenterPosition:String = "verticalCenter"; + public var sizesPositions:Array = ["width","height"]; + + public var addZoom:Boolean; + public var output:String = ""; + public var cssOutput:String = ""; + public var wrapInPreview:Boolean; + + /** + * + * */ + public var useWrapperDivs:Boolean; + public var showOnlyHTML:Boolean; + public var showOnlyCSS:Boolean; + + /** + * @inheritDoc + * */ + public function export(iDocument:IDocument, reference:Boolean = false, target:Object = null):String { + var XML1:XML; + var application:Object = iDocument ? iDocument.instance : null; + var targetDescription:ComponentDescription; + var componentTree:ComponentDescription; + var zoomOutput:String; + var xml:XML; + + componentTree = iDocument.componentDescription; + cssOutput = ""; + + // find target in display list and get it's code + targetDescription = DisplayObjectUtils.getTargetInComponentDisplayList(target, componentTree); + + + if (targetDescription) { + + // see the top of this document on how to generate source code + getAppliedPropertiesFromHistory(iDocument, targetDescription); + + if (!reference) { + //output = getHTMLOutputString(iDocument, iDocument.componentDescription); + + var includePreviewCode:Boolean = true; + var tabDepth:String = ""; + + if (showFullHTMLPageSource) { + tabDepth = ""; //"\t\t\t"; + } + + output = getHTMLOutputString(iDocument, targetDescription, true, tabDepth, includePreviewCode); + output += "\n"; + + var applicationContainerID:String = "applicationContainer"; + var zoomInID:String = wrapInPreview ? application.name : applicationContainerID; + + // not enabled at the moment - see code inspector + if (wrapInPreview) { + var wrapper:String = "<div id=\"" + applicationContainerID +"\" style=\"position:absolute;"; + //output += "width:" + (component.instance.width + 40) + "px;"; + wrapper += "width:100%;"; + wrapper += "height:" + (targetDescription.instance.height + 40) + "px;"; + wrapper += "background-color:#666666;\">\n" + output + "</div>"; + output = wrapper; + } + + if (stylesheets) { + output += "\n" + stylesheets; + } + + var styles:String = ""; + + if (showOnlyCSS) { + + // SPOT NUMBER 1 + // you have to include css options in another spot as well below + // SEE SPOT NUMBER 2 + // refactor + if (!useInlineStyles) { + styles = "\n" + cssOutput; + } + + if (css) { + styles += "\n" + css; + } + + if (useSVGButtonClass) { + styles += "\n" + buttonCSS2; + } + + if (showBorders) { + styles += "\n" + showBordersCSS; + } + + if (addZoom) { + //zoomOutput = zoomCSS.replace(/IFRAME_ID/g, "#" + application.name); + zoomOutput = zoomCSS.replace(/IFRAME_ID/g, "#" + zoomInID); + zoomOutput = zoomOutput.replace(/ZOOM_VALUE/g, iDocument.scale); + styles += "\n" + zoomOutput; + } + + output = styles; + } + else if (showOnlyHTML) { + + if (showFullHTMLPageSource) { + output = template.replace(contentToken, output); + } + } + else { + // THIS IS SPOT NUMBER 2 + // You have to include CSS options in another place as well + // see spot number 1 + if (!useInlineStyles) { + styles += "\n" + cssOutput; + } + + if (css) { + styles += "\n" + css; + } + + if (useSVGButtonClass) { + styles += "\n" + buttonCSS2; + } + + if (showBorders) { + styles += "\n" + showBordersCSS; + } + + + if (addZoom) { + //zoomOutput = zoomCSS.replace(/IFRAME_ID/g, "#" + application.name); + zoomOutput = zoomCSS.replace(/IFRAME_ID/g, "#" + zoomInID); + zoomOutput = zoomOutput.replace(/ZOOM_VALUE/g, iDocument.scale); + styles += "\n" + zoomOutput; + } + + + + + // add styles in style tags and add to output + if (styles!="") { + output += "\n" + wrapInStyleTags(styles); + } + + + + if (showFullHTMLPageSource) { + output = template.replace(contentToken, output); + } + } + + + isValid = XMLUtils.isValidXML(output); + + if (!isValid) { + error = XMLUtils.validationError; + errorMessage = XMLUtils.validationErrorMessage; + } + else { + error = null; + errorMessage = null; + } + + var checkValidXML:Boolean = false; + if (checkValidXML) { + try { + // don't use XML for HTML output because it converts this: + // <div ></div> + // to this: + // <div /> + // and that breaks the html page + + // we can still try it to make sure it's valid + // we could be saving CPU cycles here? + var time:int = getTimer(); + + // check if valid XML + // we could also use XMLUtils.isValid but this is also for formatting + xml = new XML(output); + time = getTimer() -time; + //trace("xml validation parsing time=" + time); + sourceCode = output; + } + catch (error:Error) { + // Error #1083: The prefix "s" for element "Group" is not bound. + // <s:Group x="93" y="128"> + // <s:Button x="66" y="17"/> + //</s:Group> + time = getTimer() -time; + //trace("xml validation parsing time with error=" + time); + sourceCode = output; + } + } + else { + sourceCode = output; + } + } + else {// this should not be here - it should be in DocumentData + XML1 = <document />; + XML1.@host = iDocument.host; + XML1.@id = iDocument.id; + XML1.@name = iDocument.name; + XML1.@uid = iDocument.uid; + XML1.@uri = iDocument.uri; + output = XML1.toXMLString(); + } + } + + return output; + } + + + + /** + * Gets the formatted output from a component. + * Yes, this is a mess. It needs refactoring. + * I wanted to see if I could quickly generate valid HTML + * from the component tree. + * + * There is partial work with CSS properties objects but those are not implemented yet. + * */ + public function getHTMLOutputString(iDocument:IDocument, component:ComponentDescription, addLineBreak:Boolean = false, tabs:String = "", includePreview:Boolean = false):String { + var property:Object = component.properties; + var componentName:String = component.name ? component.name.toLowerCase() : ""; + var htmlName:String = componentName ? componentName : ""; + var componentChild:ComponentDescription; + var contentToken:String = "[child_content]"; + var styleValue:String = "position:absolute;"; + var styles:Styles = new Styles(); + var wrapperStyles:Styles = new Styles(); + var isHorizontalLayout:Boolean; + var isVerticalLayout:Boolean; + var isBasicLayout:Boolean; + var isTileLayout:Boolean; + var childContent:String = ""; + var wrapperTag:String = ""; + var centeredHorizontally:Boolean; + var wrapperTagStyles:String = ""; + var properties:String = ""; + var outlineStyle:String; + var output:String = ""; + var type:String = ""; + var instance:Object; + var numElements:int; + var index:int; + var value:*; + var gap:int; + + + // we are setting the styles in a string now + // the next refactor should use the object so we can output to CSS + styles.position = Styles.ABSOLUTE; + outlineStyle = "outline:1px solid red;"; // we should enable or disable outlines via code not markup on in the export + + // get layout positioning + if (component.parent && component.parent.instance is IVisualElementContainer) { + + if (component.parent.instance.layout is HorizontalLayout) { + isHorizontalLayout = true; + styleValue = styleValue.replace("absolute", "relative"); + //styleValue += "vertical-align:middle;"; + styles.position = Styles.RELATIVE; + index = GroupBase(component.parent.instance).getElementIndex(component.instance as IVisualElement); + numElements = GroupBase(component.parent.instance).numElements; + wrapperTagStyles += hasExplicitSizeSet(component.instance as IVisualElement) ? "display:inline-block;" : "display:inline;"; + wrapperStyles.display = hasExplicitSizeSet(component.instance as IVisualElement) ? Styles.INLINE_BLOCK : Styles.INLINE; + gap = HorizontalLayout(component.parent.instance.layout).gap - 4; + + + if (index<numElements-1 && numElements>1) { + //wrapperTagStyles += "padding-right:" + gap + "px;"; + wrapperTagStyles += Styles.MARGIN_RIGHT+":" + gap + "px;"; + wrapperStyles.marginRight = gap + "px"; + + } + + wrapperTag = "div"; + } + else if (component.parent.instance.layout is TileLayout) { + //isHorizontalLayout = true; + isTileLayout = true; + styleValue = styleValue.replace("absolute", "relative"); + styles.position = Styles.RELATIVE; + index = GroupBase(component.parent.instance).getElementIndex(component.instance as IVisualElement); + numElements = GroupBase(component.parent.instance).numElements; + wrapperTagStyles += hasExplicitSizeSet(component.instance as IVisualElement) ? "display:inline-block;" : "display:inline;"; + wrapperStyles.display = hasExplicitSizeSet(component.instance as IVisualElement) ? Styles.INLINE_BLOCK : Styles.INLINE; + gap = TileLayout(component.parent.instance.layout).horizontalGap - 4; + + if (index<numElements-1 && numElements>1) { + //wrapperTagStyles += "padding-right:" + gap + "px;"; + // using "margin-right" because if you set a fixed width padding was not doing anything + wrapperTagStyles += Styles.MARGIN_RIGHT+":" + gap + "px;"; + //wrapperStyles.paddingRight = gap + "px"; + wrapperStyles.marginRight = gap + "px"; + } + + wrapperTag = "div"; + } + + else if (component.parent.instance.layout is VerticalLayout) { + isVerticalLayout = true; + styleValue = styleValue.replace("absolute", "relative"); + styles.position = Styles.RELATIVE; + index = GroupBase(component.parent.instance).getElementIndex(component.instance as IVisualElement); + numElements = GroupBase(component.parent.instance).numElements; + gap = VerticalLayout(component.parent.instance.layout).gap; + + + if (index<numElements-1 && numElements>1) { + //wrapperTagStyles += "padding-bottom:" + gap + "px;"; + wrapperTagStyles += Styles.MARGIN_BOTTOM+":" + gap + "px;"; + //wrapperStyles.paddingBottom = gap + "px"; + wrapperStyles.marginBottom = gap + "px"; + } + + wrapperTag = "div"; + } + + else if (component.parent.instance.layout is BasicLayout) { + isBasicLayout = true; + + + + //styleValue = styleValue.replace("absolute", "relative"); + //styles.position = Styles.RELATIVE; + /*index = GroupBase(component.parent.instance).getElementIndex(component.instance as IVisualElement); + numElements = GroupBase(component.parent.instance).numElements; + gap = BasicLayout(component.parent.instance.layout).gap;*/ + + /*if (index<numElements-1 && numElements>1) { + wrapperTagStyles += "padding-bottom:" + gap + "px"; + } + + wrapperTag = "div";*/ + } + } + + // constraints take higher authority + var isHorizontalSet:Boolean; + var isVerticalSet:Boolean; + + // loop through assigned properties + for (var propertyName:String in property) { + value = property[propertyName]; + + if (value===undefined || value==null) { + continue; + } + + + if (verticalPositions.indexOf(propertyName)!=-1 && !isVerticalSet) { + styleValue = getVerticalPositionHTML(component.instance as IVisualElement, styles, styleValue, isBasicLayout); + isVerticalSet = true; + } + else if (horizontalPositions.indexOf(propertyName)!=-1 && !isHorizontalSet) { + styleValue = getHorizontalPositionHTML(component.instance as IVisualElement, styles, styleValue, isBasicLayout); + isHorizontalSet = true; + } + + } + + + if (htmlName) { + + // create code for element type + if (htmlName=="application") { + htmlName = "div"; + + // container div + if (includePreview) { + /*output = "<div style=\"position:absolute;"; + //output += "width:" + (component.instance.width + 40) + "px;"; + output += "width:100%;"; + output += "height:" + (component.instance.height + 40) + "px;"; + output += "background-color:#666666;\">";*/ + output += "<div"; + //output = getNameString(component.instance, output); + output += properties ? " " + properties : " "; + output = getIdentifierAttribute(component.instance, output); + styleValue = styleValue.replace("absolute", "relative"); + styles.position = Styles.ABSOLUTE; + styleValue += "width:" + component.instance.width+ "px;"; + styleValue += "height:" + component.instance.height+ "px;"; + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + styleValue += "margin:0 auto;"; + styleValue += "left:8px;top:14px;"; + styleValue += "overflow:auto;"; + styleValue += "background-color:" + DisplayObjectUtils.getColorInHex(component.instance.getStyle("backgroundColor"), true) + ";"; + //output += properties ? " " : ""; + output += setStyles(component.instance, styleValue); + + + if (showScreenshotBackground) { + var backgroundImageID:String = "backgroundComparisonImage"; + var imageDataFormat:String = "png";//"jpeg"; + var imageData:String = getDataURI(component.instance, imageDataFormat); + var backgroundSnapshot:String = "\n" + tabs + "\t" + "<img "; + backgroundSnapshot += "id=\"" + backgroundImageID +"\""; + backgroundSnapshot += " src=\"" + imageData + "\" "; + + output += backgroundSnapshot; + output += setStyles("#"+backgroundImageID, "position:absolute;opacity:"+backgroundImageAlpha+";top:0px;left:0px;", true); + /* background-image didn't work in FF on mac. didn't test on other browsers + //trace(imageData); + var imageDataStyle:String = "#" + getIdentifierOrName(target) + " {\n"; + //imageDataStyle = "\tbackground-image: url(data:image/jpeg;base64,"+imageData+");"; + imageDataStyle += "\tbackground-repeat: no-repeat;\n"; + imageDataStyle += "\tbackground-image: url(data:image/"+imageDataFormat+";base64,"+imageData+");\n}"; + styles += "\n" + imageDataStyle;*/ + } + + output += contentToken; + //output += "\n </div>\n</div>"; + output += "\n</div>"; + + } + else { + //output = "<div style=\"position: absolute;width:100%;height:100%;background-color:#666666;\">"; + output = "<div"; + output += properties ? " " + properties : " "; + output = getIdentifierAttribute(component.instance, output); + //output = getNameString(component.instance, output); + output += properties ? " " + properties : ""; + styleValue += "width:" + component.instance.width+ "px;"; + styleValue += "height:" + component.instance.height+ "px;"; + styleValue += "border:1px solid black"; + styleValue += "background-color:" + DisplayObjectUtils.getColorInHex(component.instance.getStyle("backgroundColor"), true) + ";"; + //output += properties ? " " : ""; + output += setStyles(component.instance, styleValue); + output += contentToken; + output += "\n</div>"; + } + } + + else if (htmlName=="group" || htmlName=="vgroup") { + htmlName = "div"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<div " + properties; + output = getIdentifierAttribute(component.instance, output); + output += properties ? " " : ""; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + output += setStyles(component.instance, styleValue); + output += contentToken; + output += "\n" + tabs + "</div>"; + output += getWrapperTag(wrapperTag, true); + } + + else if (htmlName=="bordercontainer") { + htmlName = "div"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<div " + properties; + output = getIdentifierAttribute(component.instance, output); + output += properties ? " " : ""; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += getBorderString(component.instance as BorderContainer); + //styleValue += getColorString(component.instance as BorderContainer); + //styles += component.instance as BorderContainer); + + output += setStyles(component.instance, styleValue); + output += contentToken; + output += "\n" + tabs + "</div>"; + output += getWrapperTag(wrapperTag, true); + + } + + else if (htmlName=="hgroup" || htmlName=="tilegroup") { + htmlName = "div"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<div " + properties; + output = getIdentifierAttribute(component.instance, output); + + //styleValue = getSizeString(component.instance as IVisualElement, styleValue); + if (component.name=="HGroup") { + styleValue += "width:" + Math.max(HGroup(component.instance).contentWidth, component.instance.width)+ "px;"; + } + else { + styleValue += "width:" + component.instance.width+ "px;"; + } + + styleValue += "height:" + component.instance.height+ "px;"; + //var verical:String = component.instance.getStyle("verticalAlign"); + var vericalAlign:String = component.instance.verticalAlign; + if (componentName.toLowerCase()=="hgroup" && vericalAlign=="middle") { + styleValue += "line-height:" + component.instance.height + "px;"; + } + + output += properties ? " " : ""; + output += setStyles(component.instance, styleValue); + output += contentToken; + output += "\n" + tabs + "</div>"; + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="button" || htmlName=="togglebutton") { + htmlName = "button"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<input " + properties; + output = getIdentifierAttribute(component.instance, output); + output += " type=\"" + htmlName.toLowerCase() + "\"" ; + output += properties ? " " + properties : ""; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + output += " value=\"" + component.instance.label + "\""; + output += " class=\"buttonSkin\""; + output += setStyles(component.instance, styleValue); + + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="checkbox") { + if (component.instance.label!="") { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<label "; + output = getIdentifierAttribute(component.instance, output, "_Label"); + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + //styleValue += "width:" + (component.instance.width + 6)+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + output += setStyles("#"+getIdentifierOrName(component.instance, true, "_Label"), styleValue); + output += "<input "; + output = getIdentifierAttribute(component.instance, output); + output += " type=\"" + htmlName.toLowerCase() + "\" "; + output += "/>" ; + } + else { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<input " + properties; + output = getIdentifierAttribute(component.instance, output); + output += " type=\"" + htmlName.toLowerCase() + "\" " + properties; + //styleValue = getSizeString(component.instance as IVisualElement, styleValue); + output += setStyles(component.instance, styleValue); + } + + if (component.instance.label!="") { + output += " " + component.instance.label + "</label>"; + } + + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="radiobutton") { + htmlName = "radio"; + if (component.instance.label!="") { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<label "; + output = getIdentifierAttribute(component.instance, output, "_Label"); + //styleValue += "width:" + (component.instance.width + 8)+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + output += setStyles("#"+getIdentifierOrName(component.instance, true, "_Label"), styleValue); + output += "<input type=\"radio\" " ; + output = getIdentifierAttribute(component.instance, output); + //styleValue = getSizeString(component.instance as IVisualElement, styleValue); + output += "/>" ; + } + else { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<input type=\"" + htmlName.toLowerCase() + "\" " + properties; + output = getIdentifierAttribute(component.instance, output); + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + //styleValue = getSizeString(component.instance as IVisualElement, styleValue); + output += setStyles(component.instance, styleValue); + } + + if (component.instance.label!="") { + output += " " + component.instance.label + "</label>"; + } + + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="textinput" || htmlName=="combobox") { + htmlName = "input"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<input "; + output = getIdentifierAttribute(component.instance, output); + output += " type=\"input\" " + properties; + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + styleValue += "padding:0;border:1px solid " + DisplayObjectUtils.getColorInHex(component.instance.getStyle("borderColor"), true) + ";"; + + if (htmlName=="combobox") { + output += " list=\"listdata\""; + //<datalist id="listData"> + //<option value="value 1"> + //<option value="value 2"> + //<option value="value 3"> + //</datalist> + } + output += setStyles(component.instance, styleValue); + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="dropdownlist") { + htmlName = "select"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<select "; + output = getIdentifierAttribute(component.instance, output); + output += " type=\"input\" " + properties; + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + styleValue += "padding:0;border:1px solid " + DisplayObjectUtils.getColorInHex(component.instance.getStyle("borderColor"), true) + ";"; + + output += setStyles(component.instance, styleValue); + output += "</select>"; + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="linkbutton") { + htmlName = "a"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<a " + properties; + output = getIdentifierAttribute(component.instance, output); + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + //styles += getBorderString(component.instance as IStyleClient); + + output += properties ? " " : ""; + output += setStyles(component.instance, styleValue); + output += component.instance.label; + output += "</a>"; + output += getWrapperTag(wrapperTag, true); + } + else if (htmlName=="label") { + htmlName = "label"; + if (useWrapperDivs) { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + } + else { + output = tabs; + } + output += "<label " + properties; + output = getIdentifierAttribute(component.instance, output); + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet, isVerticalSet); + //styleValue += wrapperTagStyles; + styleValue += "color:" + DisplayObjectUtils.getColorInHex(component.instance.getStyle("color"), true) + ";"; + styleValue += "font-weight:" + component.instance.getStyle("fontWeight") + ";"; + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + styleValue += "line-height:" + "1;"; + //styles += getBorderString(component.instance as IStyleClient); + + output += properties ? " " : ""; + // remove wrapperTagStyles since we are trying to not use wrapper tags + //output += setStyles(component.instance, styleValue+wrapperTagStyles); + output += setStyles(component.instance, wrapperTagStyles+styleValue); + output += component.instance.text; + output += "</label>"; + if (useWrapperDivs) { + output += getWrapperTag(wrapperTag, true); + } + } + else if (htmlName=="image") { + htmlName = "img"; + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<img " + properties; + output = getIdentifierAttribute(component.instance, output); + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + output += properties ? " " : ""; + + if (component.instance.source is BitmapData) { + output += " src=\"" + getDataURI(component.instance.source, "jpeg") + "\""; + } + else if (component.instance.source is String) { + output += " src=\"" + component.instance.source + "\""; + } + output += setStyles(component.instance, styleValue); + output += getWrapperTag(wrapperTag, true); + } + + else { + // show placeholder NOT actual component + htmlName = "label"; + if (useWrapperDivs) { + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + } + else { + output = tabs; + } + output += "<label " + properties; + output = getIdentifierAttribute(component.instance, output); + //styleValue += "width:" + component.instance.width+ "px;"; + //styleValue += "height:" + component.instance.height+ "px;"; + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet, isVerticalSet); + //styleValue += wrapperTagStyles; + styleValue += "color:" + DisplayObjectUtils.getColorInHex(component.instance.getStyle("color"), true) + ";"; + styleValue += "font-weight:" + component.instance.getStyle("fontWeight") + ";"; + styleValue += "font-family:" + component.instance.getStyle("fontFamily") + ";"; + styleValue += "font-size:" + component.instance.getStyle("fontSize") + "px;"; + styleValue += "line-height:" + "1;"; + //styles += getBorderString(component.instance as IStyleClient); + + output += properties ? " " : ""; + // remove wrapperTagStyles since we are trying to not use wrapper tags + //output += setStyles(component.instance, styleValue+wrapperTagStyles); + output += setStyles(component.instance, wrapperTagStyles+styleValue); + output += getIdentifierOrName(component.instance); + output += "</label>"; + if (useWrapperDivs) { + output += getWrapperTag(wrapperTag, true); + } + /* + output = tabs + getWrapperTag(wrapperTag, false, wrapperTagStyles); + output += "<" + htmlName.toLowerCase() + " " + properties; + output = getIdentifierAttribute(component.instance, output); + styleValue = getSizeString(component.instance as IVisualElement, styleValue, isHorizontalSet); + output += properties ? " " : ""; + output += setStyles(component.instance, styleValue); + output += getWrapperTag(wrapperTag, true);*/ + } + + + // add children + if (component.children && component.children.length>0) { + //output += ">\n"; + + for (var i:int;i<component.children.length;i++) { + componentChild = component.children[i]; + getAppliedPropertiesFromHistory(iDocument, componentChild); + if (i>0) { + childContent += "\n"; + } + childContent += getHTMLOutputString(iDocument, componentChild, false, tabs + "\t"); + } + + output = output.replace(contentToken, "\n" + childContent); + + } + else { + output = output.replace(contentToken, "\n"); + } + } + else { + output = ""; + } + + return output; + } + + /** + * Get a tag with less than or greater than wrapped around it. + * */ + private function getWrapperTag(wrapperTag:String = "", end:Boolean = false, styles:String = ""):String { + var output:String = ""; + + if (wrapperTag=="") return ""; + + if (end) { + output = "</" + wrapperTag + ">"; + return output; + } + + output += "<" + wrapperTag; + + if (styles) { + output += " style=\"" + styles + "\"" ; + } + + output += ">"; + + return output; + } + + /** + * Get width and height styles + * If explicit width is set then we should use inline-block + * because inline does not respect width and height + * */ + public function getSizeString(instance:IVisualElement, styleValue:String = "", isHorizontalAlignSet:Boolean = false, isVerticalSet:Boolean = false):String { + var hasExplicitSize:Boolean; + var hasBorder:Boolean; + var border:int; + + if (instance is IStyleClient && IStyleClient(instance).getStyle("borderWeight")) { + + } + + if (!isNaN(instance.percentWidth)) { + styleValue += "width:" + instance.percentWidth + "%;"; + } + else if ("explicitWidth" in instance) { + if (Object(instance).explicitWidth!=null && !isNaN(Object(instance).explicitWidth) + || setExplicitSize) { + styleValue += "width:" + instance.width + "px;"; + hasExplicitSize = true; + } + } + else { + //styleValue += "width:" + instance.width + "px;"; + } + + if (!isNaN(instance.percentHeight)) { + styleValue += "height:" + instance.percentHeight + "%;"; + } + else if ("explicitHeight" in instance) { + if (Object(instance).explicitHeight!=null && !isNaN(Object(instance).explicitHeight) + || setExplicitSize) { + styleValue += "height:" + instance.height + "px;"; + hasExplicitSize = true; + } + } + else { + //styleValue += "height:" + instance.height + "px;"; + } + + + // If explicit width is set then we should use inline-block + // because inline does not respect width and height + if (!isHorizontalAlignSet && hasExplicitSize) { + styleValue += "display:" + Styles.INLINE_BLOCK + ";"; + } + + return styleValue; + + } + + /** + * Checks if size is explicitly set + * If explicit width is set then we should use inline-block + * because inline does not respect width and height + * */ + public function hasExplicitSizeSet(instance:IVisualElement):Boolean { + + if ("explicitWidth" in instance && Object(instance).explicitWidth!=null) { + return true; + } + else if ("explicitHeight" in instance && Object(instance).explicitHeight!=null) { + return true; + } + + return false; + } + + /** + * Get the horizontal position string for HTML + * */ + public function getHorizontalPositionHTML(instance:IVisualElement, propertyModel:Styles, stylesValue:String = "", isBasicLayout:Boolean = true):String { + + if (!isBasicLayout) return stylesValue; + // horizontal center trumps left and x properties + if (instance.horizontalCenter!=null) { + stylesValue += "display:block;margin:" + instance.horizontalCenter + " auto;left:0;right:0;"; + //stylesValue = stylesValue.replace("absolute", "relative"); + + propertyModel.display = Styles.BLOCK; + //propertyModel.position = Styles.RELATIVE; + propertyModel.position = Styles.ABSOLUTE; + propertyModel.margin = instance.horizontalCenter + " auto;left:0;right:0;"; + + return stylesValue; + } + else if (instance.left!=null || instance.right!=null) { + stylesValue += instance.left!=null ? "left:" + instance.left + "px;" : ""; + stylesValue += instance.right!=null ? "right:" + instance.right + "px;" : ""; + if (instance.left!=null) propertyModel.left = instance.left + "px"; + if (instance.right!=null) propertyModel.right = instance.right + "px"; + return stylesValue; + } + else { + stylesValue += "left:" + instance.x + "px;"; + propertyModel.left = instance.x + "px;"; + } + + return stylesValue; + } + + + /** + * Get the vertical position string for HTML + * */ + public function getVerticalPositionHTML(instance:IVisualElement, propertyModel:Styles, stylesValue:String = "", isBasicLayout:Boolean = true):String { + + if (!isBasicLayout) return stylesValue; + + if (instance.verticalCenter!=null) { + stylesValue += "display:block;margin:" + instance.verticalCenter + " auto;"; + stylesValue = stylesValue.replace("absolute", "relative"); + + propertyModel.display = Styles.BLOCK; + propertyModel.position = Styles.RELATIVE; + propertyModel.margin = instance.verticalCenter + " auto;"; + + return stylesValue; + } + else if (instance.top!=null || instance.bottom!=null) { + stylesValue += instance.top!=null ? "top:" + instance.top + "px;" : ""; + stylesValue += instance.bottom!=null ? "bottom:" + instance.bottom + "px;" : ""; + if (instance.top!=null) propertyModel.top = instance.top + "px"; + if (instance.bottom!=null) propertyModel.bottom = instance.bottom + "px"; + return stylesValue; + } + else { + stylesValue += "top:" + instance.y + "px;"; + propertyModel.top = instance.y + "px;"; + } + + return stylesValue; + } + + /** + * Get border and background styles of a border container + * */ + public function getBorderString(element:IStyleClient):String { + var value:String = ""; + + if (element.getStyle("backgroundAlpha")!=0) { + value += "background-color:" + DisplayObjectUtils.getColorInHex(element.getStyle("backgroundColor"), true) + ";"; + value += "background-alpha:" + element.getStyle("backgroundAlpha") + ";"; + } + + if (element.getStyle("borderVisible")) { + value += "border-width:" + element.getStyle("borderWeight") + "px;"; + value += "border-style:solid;"; + + if (element.getStyle("borderColor")!==undefined) { + value += "border-color:" + DisplayObjectUtils.getColorInHex(element.getStyle("borderColor"), true) + ";"; + } + } + + if (element.getStyle("color")!==undefined) { + value += "color:" + DisplayObjectUtils.getColorInHex(element.getStyle("color"), true) + ";"; + } + + return value; + } + + /** + * Set styles + * */ + public function setStyles(component:Object, styles:String = "", singleton:Boolean = false):String { + var out:String = ">"; + + if (useInlineStyles) { + return " style=\"" + styles + "\"" + (singleton?"\>":">"); + } + else { + var formatted:String= "\t" + styles.replace(/;/g, ";\n\t"); + + //styles += ";"; + //cssOutput += "#" + getIdentifierOrName(component) + " {\n\n"; + //cssOutput += "" + styles.replace(/;/g, ";\n") + "\n\n} "; + + if (component is String) { + out = component + " {\n"; + } + else { + out = "#" + getIdentifierOrName(component) + " {\n"; + } + out += formatted; + out += "}\n\n"; + + out = out.replace(/\t}/g, "}"); + + cssOutput += out; + } + + return (singleton?"\>":">"); + } + + + /** + * Wrap in style tags + * */ + public function wrapInStyleTags(value:String):String { + var out:String = "<style>\n" + value + "\n</style>"; + return out; + } + + /** + * Gets the ID of the target object + * + * @param name if id is not available then if the name parameter is true then use name + * + * returns id or name + * */ + public function getIdentifierOrName(element:Object, name:Boolean = true, appendID:String = ""):String { + + if (element && "id" in element && element.id) { + return element.id + appendID; + } + else if (element && name && "name" in element && element.name) { + return element.name + appendID; + } + + return ""; + } + + /** + * Get ID from ID or else name attribute + * */ + public function getIdentifierAttribute(instance:Object, value:String = "", appendID:String = ""):String { + + if (instance && "id" in instance && instance.id) { + value += "id=\"" + instance.id + appendID + "\""; + } + + else if (instance && "name" in instance && instance.name) { + value += "id=\"" + instance.name + appendID + "\""; + } + + return value; + } + + /** + * Get name and ID attribute + * */ + public function getIdentifierOrNameAttribute(instance:Object, propertyValue:String = ""):String { + + if (instance && "id" in instance && instance.id) { + propertyValue += "id=\"" + instance.id + "\""; + } + + if (instance && "name" in instance && instance.name) { + propertyValue += "name=\"" + instance.name + "\""; + } + + return propertyValue; + } + + /** + * @inheritDoc + * */ + public function exportXML(document:IDocument, reference:Boolean = false):XML { + return null; + } + + /** + * @inheritDoc + * */ + public function exportJSON(document:IDocument, reference:Boolean = false):JSON { + return null; + } + + /** + * Get data URI from object. + * + * Encoding to JPG took 2000ms in some cases where PNG took 200ms. + * I have not extensively tested this but it seems to be 10x faster + * than JPG. + * */ + public function getDataURI(target:Object, type:String = "png"):String { + var output:String; + + if (type.toLowerCase()=="jpg") { + type = "jpeg"; + } + + output = "data:image/" + type + ";base64," + getBase64ImageData(target, type); + + return output; + } + + /** + * Returns base64 image string. + * + * Encoding to JPG took 2000ms in some cases where PNG took 200ms. + * I have not extensively tested this but it seems to be 10x faster + * than JPG. + * + * Performance: + * get snapshot. time=14 + * encode to png. time=336 // encode to jpg. time=2000 + * encode to base 64. time=35 + * + * Don't trust these numbers. Test it yourself. First runs are always longer than previous. + * + * This function gets called multiple times sometimes. We may be encoding more than we have too. + * But is probably a bug somewhere. + * */ + public function getBase64ImageData(target:Object, type:String = "png", checkCache:Boolean = false):String { + var component:IUIComponent = target as IUIComponent; + var bitmapData:BitmapData; + var byteArray:ByteArray; + var base64Encoder:Base64Encoder; + var base64Encoder2:Base64; + var useEncoder:Boolean; + var rectangle:Rectangle; + var jpegEncoderOptions:JPEGEncoderOptions; + var pngEncoderOptions:PNGEncoderOptions; + var quality:int = 80; + var fastCompression:Boolean = true; + var timeEvents:Boolean = false; + var altBase64:Boolean = true; + var results:String; + + + if (base64BitmapCache[target] && checkCache) { + return base64BitmapCache[target]; + } + + if (timeEvents) { + var time:int = getTimer(); + } + + if (component) { + bitmapData = BitmapUtil.getSnapshot(component); + } + else if (target is DisplayObject) { + bitmapData = DisplayObjectUtils.getBitmapDataSnapshot2(target as DisplayObject); + } + else if (target is BitmapData) { + bitmapData = target as BitmapData; + } + else { + return null; + } + + rectangle = new Rectangle(0, 0, bitmapData.width, bitmapData.height); + + if (timeEvents) { + trace ("get snapshot. time=" + (getTimer()-time)); + time = getTimer(); + } + + if (type=="png") { + + if (useEncoder) { + if (!pngEncoder) { + pngEncoder = new PNGEncoder(); + } + + byteArray = pngEncoder.encode(bitmapData); + } + else { + if (!pngEncoderOptions) { + pngEncoderOptions = new PNGEncoderOptions(fastCompression); + } + + byteArray = bitmapData.encode(rectangle, pngEncoderOptions); + } + } + else if (type=="jpg" || type=="jpeg") { + + if (useEncoder) { + if (!jpegEncoder) { + jpegEncoder = new JPEGEncoder(); + } + + byteArray = jpegEncoder.encode(bitmapData); + } + else { + if (!jpegEncoderOptions) { + jpegEncoderOptions = new JPEGEncoderOptions(quality); + } + + byteArray = bitmapData.encode(rectangle, jpegEncoderOptions); + } + } + else { + // raw bitmap image data + byteArray = bitmapData.getPixels(new Rectangle(0, 0, bitmapData.width, bitmapData.height)); + } + + if (timeEvents) { + trace ("encode to " + type + ". time=" + (getTimer()-time)); + time = getTimer(); + } + + if (!altBase64) { + if (!base64Encoder) { + base64Encoder = new Base64Encoder(); + } + + base64Encoder.encodeBytes(byteArray); + + results = base64Encoder.toString(); + } + else { + results = Base64.encode(byteArray); + } + + //trace(base64.toString()); + + if (timeEvents) { + trace ("encode to base 64. time=" + (getTimer()-time)); + } + + base64BitmapCache[target] = results; + + return results; + } + + public var base64BitmapCache:Dictionary = new Dictionary(true) + } +} \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/flex-radii8/blob/f370bfcf/Radii8Library/src/com/flexcapacitor/utils/InspectorUtils.as ---------------------------------------------------------------------- diff --git a/Radii8Library/src/com/flexcapacitor/utils/InspectorUtils.as b/Radii8Library/src/com/flexcapacitor/utils/InspectorUtils.as new file mode 100644 index 0000000..ae388ec --- /dev/null +++ b/Radii8Library/src/com/flexcapacitor/utils/InspectorUtils.as @@ -0,0 +1,500 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + + + + +package com.flexcapacitor.utils { + import com.flexcapacitor.controller.Radiate; + import com.flexcapacitor.events.InspectorEvent; + import com.flexcapacitor.graphics.LayoutLines; + import com.flexcapacitor.model.VisualElementVO; + + import flash.desktop.Clipboard; + import flash.desktop.ClipboardFormats; + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + import flash.events.IEventDispatcher; + import flash.utils.describeType; + import flash.utils.getQualifiedClassName; + import flash.utils.getQualifiedSuperclassName; + + import mx.collections.ArrayCollection; + import mx.core.IVisualElement; + import mx.core.IVisualElementContainer; + import mx.core.UIComponent; + import mx.managers.ISystemManager; + import mx.utils.NameUtil; + import mx.utils.ObjectUtil; + + public class InspectorUtils { + + + public function InspectorUtils() { + + + } + + /** + * Get unqualified class name of the target object + * */ + public static function getClassName(element:Object):String { + var name:String = NameUtil.getUnqualifiedClassName(element); + return name; + } + + /** + * Get unqualified class name of the document of the target object + * returns null if element is not a UIComponent + * */ + public static function getDocumentName(element:Object):String { + var name:String; + if (element is UIComponent) { + name = NameUtil.getUnqualifiedClassName(UIComponent(element).document); + } + return name; + } + + /** + * Get the type of the value passed in + * */ + public static function getValueType(value:*):String { + var type:String = getQualifiedClassName(value); + + if (type=="int") { + if (typeof value=="number") { + type = "Number"; + } + } + + return type; + } + + /** + * Get package of the target object + * */ + public static function getPackageName(element:Object):String { + var name:String = flash.utils.getQualifiedClassName(element); + if (name && name.indexOf("::")) { + name = name.split("::")[0]; + } + + return name; + } + + /** + * Get super class name of the target object + * */ + public static function getSuperClassName(element:Object):String { + var name:String = flash.utils.getQualifiedSuperclassName(element); + if (name && name.indexOf("::")) { + name = name.split("::")[name.split("::").length-1]; // i'm sure theres a better way to do this + } + + return name; + } + + /** + * Get the package of the super class name of the target + * */ + public static function getSuperClassPackage(element:Object):String { + var name:String = flash.utils.getQualifiedSuperclassName(element); + if (name && name.indexOf("::")) { + name = name.split("::")[0]; + } + + return name; + } + + /** + * Clears outline drawn around target display object + * */ + public static function clearSelection(target:Object, systemManager:ISystemManager, remove:Boolean = false):void { + LayoutLines.getInstance().clear(target, systemManager, remove); + } + + /** + * Draws outline around target display object + * */ + public static function drawSelection(target:Object, systemManager:ISystemManager):void { + throw new Error("CANNOT USE DRAW SELECTION"); + //Radiate.drawSelection(target); + /*if (target is DisplayObject) { + LayoutLines.getInstance().drawLines(target, systemManager); + } + else if (target!=null) { + trace("Is not displayObject", NameUtil.getUnqualifiedClassName(target)); + }*/ + } + + /** + * Copy text to clipboard + * */ + public static function copyToClipboard(text:String, format:String=ClipboardFormats.TEXT_FORMAT):void { + Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, text); + } + + /** + * Returns an array of display objects of type VisualElementVO + * Optionally returns elements + * */ + public static function getElementChildrenArray(displayObject:DisplayObject, getElements:Boolean = false, getSkins:Boolean = true):ArrayCollection { + var displayObject:DisplayObject = DisplayObject(displayObject); + var displayObjectContainer:DisplayObjectContainer; + + var visualElementContainer:IVisualElementContainer; + var visualElement:IVisualElement; + + var visualElementVO:VisualElementVO = new VisualElementVO(); + + var children:ArrayCollection = new ArrayCollection(); + + + // attempt to cast to a specific type and assign in the process + displayObjectContainer = displayObject as DisplayObjectContainer; + visualElementContainer = displayObject as IVisualElementContainer; + visualElement = displayObject as IVisualElement; + + + // gather all the display objects on the current display object + if (displayObjectContainer) { + + for (var bb:int = 0; bb < displayObjectContainer.numChildren; bb++) { + + // visualElementVO = createDisplayObjectVO(displayObjectContainer.getChildAt(bb)); + visualElementVO = VisualElementVO.unmarshall(displayObjectContainer.getChildAt(bb)); + children.addItem(visualElementVO); + } + } + + if (visualElementContainer && getElements) { + + for (var cc:int = 0; cc < visualElementContainer.numElements; cc++) { + //visualElementVO = createDisplayObjectVO(displayObjectContainer.getChildAt(cc)); + visualElementVO = VisualElementVO.unmarshall(DisplayObject(visualElementContainer.getElementAt(cc))); + children.addItem(visualElementVO); + } + } + + return children; + } + + + /** + * Gets the ID of the target object + * returns null if no ID is specified or target is not a UIComponent + * */ + public static function getIdentifier(element:Object):String { + var id:String; + + if (element is UIComponent && UIComponent(element).id) { + id = UIComponent(element).id; + } + return id; + } + + /** + * Get name of target object or null if not available + * */ + public static function getName(element:Object):String { + var name:String; + + if (element.hasOwnProperty("name") && element.name) { + name = element.name; + } + + return name; + } + + /** + * Get qualified class name of the target object + * */ + public static function getQualifiedClassName(element:Object):String { + var name:String = flash.utils.getQualifiedClassName(element); + return name; + } + + /** + * With the given target it returns a regexp pattern to find the exact instance in MXML + * If isScript is true it attempts to returns a pattern to find the exact instance in AS3 + * The MXML pattern will find the instance with that ID. If the instance doesn't have an ID it no worky. + * NOTE: Press CMD+SHIFT+F to and check regular expression in the Find in Files dialog + * */ + public static function getRegExpSearchPattern(target:DisplayObject, isScript:Boolean = false):String { + var id:String = getIdentifier(target); + var className:String = NameUtil.getUnqualifiedClassName(target); + var pattern:String; + var scriptPattern:String; + + if (id == null) { + pattern = className + "(.*)"; + } + else { + pattern = className + "(.*)id\\s?=\\s?[\"|']" + id + "[\"|']"; + scriptPattern = id + ".addEventListener"; + } + + + if (isScript) { + return scriptPattern; + } + + return pattern; + } + + + /** + * Get ancestors of element + * + * @param target + * @param collection + * @param ancestors + * @return + */ + public static function getVisualElementsArray(element:*, items:Array, ancestors:int = 0, topMostDisplayObject:Object = null):Array { + var vo:VisualElementVO; + + // do the worm up the display list + while (element!= null && ancestors>-1) + { + // store display element information + vo = VisualElementVO.unmarshall(element); + + // save reference to display element VO's for tree + if (!items) items = new Array(); + items.push(vo); + + if ("owner" in element) { + element = element.owner as IVisualElement; + } + + ancestors--; + + if (element==topMostDisplayObject) { + ancestors = 0; //bail after the next run through + } + } + + return items; + + } + + /** + * Get the parent of the target that is also a UIComponent + * + * @return + */ + public static function getParentUIComponent(target:DisplayObject):UIComponent { + var found:Boolean; + + // run up the display list + while (target) { + target = target.parent; + + // check if next parent exists + if (!target) { + break; + } + + if (target is UIComponent) { + found = true; + break; + } + + } + + if (found) return UIComponent(target); + return null; + } + + /** + * Get the name of the target parent that is also a UIComponent + * + * @return + */ + public static function getParentUIComponentName(target:DisplayObject):String { + var parent:DisplayObject = getParentUIComponent(target); + var className:String = getClassName(parent); + return className; + } + + + /** + * Get parent document name + * + * @return null if target is not a UIComponent + */ + public static function getParentDocumentName(target:Object):String { + var className:String; + if (target is UIComponent) { + className = getClassName(target.parentDocument); + } + return className; + } + + + /** + * Get parent document name + * + * @return + */ + public static function getClassNameAndPackage(target:Object):Array { + var className:String; + var classPath:String; + + className = getClassName(target); + classPath = getPackageName(target); + + return [className, classPath]; + } + + + /** + * Dispatch target change event + * + * @return + */ + public static function dispatchTargetChangeEvent(target:Object, source:IEventDispatcher):void { + + // let other inspectors know there is a new target selected + var selectionChangeEvent:InspectorEvent = new InspectorEvent(InspectorEvent.CHANGE); + selectionChangeEvent.targetItem = target; + + // store previous targets in a dictionary + + if (source) { + source.dispatchEvent(selectionChangeEvent); + } + } + + + /** + * Change target. Use this instead of dispatchTargetChangeEvent() + * + * TODO: Add a weak reference to the old target in a static array for history type navigation + * + * @return + */ + public static function updateTarget(target:Object, source:IEventDispatcher):void { + // TODO: Add a weak reference to the old target in a static array for history type navigation + + // let other inspectors know there is a new target selected + var selectionChangeEvent:InspectorEvent = new InspectorEvent(InspectorEvent.CHANGE); + selectionChangeEvent.targetItem = target; + + if (source) { + source.dispatchEvent(selectionChangeEvent); + } + } + + /** + * Converts an integer to hexidecimal. + * For example, 16117809 returns "#EEEEEE" or something + * @return + */ + public static function convertIntToHex(item:Object):String { + var hex:String = Number(item).toString(16); + return ("00000" + hex.toUpperCase()).substr(-6); + } + + + /** + * Sets the property on the target. Supports styles. + * We should probably switch to the set property method + * @return + */ + public static function setTargetProperty(target:Object, property:String, value:*, type:String = "String", isPropertyStyle:Object=null):void { + var newAssignedValue:* = TypeUtils.getTypedValue(value, type); + TypeUtils.applyProperty(target, property, newAssignedValue, type, isPropertyStyle); + } + + + /** + * @copy spark.components.gridClasses.GridItemEditor#save(); + */ + public static function setProperty(target:Object, field:String, value:*):Boolean { + + var newData:Object = value; + var property:String = field; + var data:Object = target; + var typeInfo:String = ""; + + for each(var variable:XML in describeType((data).variable)) { + if (property == variable.@name.toString()) { + typeInfo = variable.@type.toString(); + break; + } + } + + if (typeInfo == "String") { + if (!(newData is String)) + newData = newData.toString(); + } + else if (typeInfo == "uint") { + if (!(newData is uint)) + newData = uint(newData); + } + else if (typeInfo == "int") { + if (!(newData is int)) + newData = int(newData); + } + else if (typeInfo == "Number") { + if (!(newData is Number)) + newData = Number(newData); + } + else if (typeInfo == "Boolean") { + if (!(newData is Boolean)) { + var strNewData:String = newData.toString(); + + if (strNewData) { + newData = (strNewData.toLowerCase() == "true") ? true : false; + } + } + } + + if (property && data[property] !== newData) { + data[property] = newData; + } + + return true; + } + + /** + * Returns <code>true</code> if the object reference specified + * is a simple data type. The simple data types include the following: + * <ul> + * <li><code>String</code></li> + * <li><code>Number</code></li> + * <li><code>uint</code></li> + * <li><code>int</code></li> + * <li><code>Boolean</code></li> + * <li><code>Date</code></li> + * <li><code>Array</code></li> + * </ul> + * + * @param value Object inspected. + * + * @return <code>true</code> if the object specified + * is one of the types above; <code>false</code> otherwise. + * */ + public static function isSimple(value:*):Boolean { + return ObjectUtil.isSimple(value); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-radii8/blob/f370bfcf/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentExporter.as ---------------------------------------------------------------------- diff --git a/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentExporter.as b/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentExporter.as new file mode 100644 index 0000000..ff3db5f --- /dev/null +++ b/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentExporter.as @@ -0,0 +1,170 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.flexcapacitor.utils { + import com.flexcapacitor.model.IDocument; + import com.flexcapacitor.model.IDocumentExporter; + import com.flexcapacitor.utils.supportClasses.ComponentDescription; + + import spark.components.Application; + + /** + * Exports a document to MXML + * */ + public class MXMLDocumentExporter extends DocumentExporter implements IDocumentExporter { + + public function MXMLDocumentExporter() { + + } + + /** + * @inheritDoc + * */ + public function export(iDocument:IDocument, target:ComponentDescription = null, reference:Boolean = false):* { + var output:String; + var XML1:XML; + + if (!target) { + target = iDocument.componentDescription; + } + + if (!reference) { + output = getMXMLOutputString(iDocument, target); + } + else { + XML1 = <document />; + XML1.@host = iDocument.host; + XML1.@id = iDocument.id; + XML1.@name = iDocument.name; + XML1.@uid = iDocument.uid; + XML1.@uri = iDocument.uri; + output = XML1.toXMLString(); + } + + return output; + } + + /** + * @inheritDoc + * */ + public function exportXML(document:IDocument, reference:Boolean = false):XML { + return null; + } + + /** + * @inheritDoc + * */ + public function exportJSON(document:IDocument, reference:Boolean = false):JSON { + return null; + } + + + /** + * Gets the formatted MXML output from a component. + * TODO: This should be using XML and namespaces. + * */ + public function getMXMLOutputString(iDocument:IDocument, component:ComponentDescription, addLineBreak:Boolean = false, tabs:String = ""):String { + if (component.instance is Application) { + getAppliedPropertiesFromHistory(iDocument, component); + } + var properties:Object = component.properties; + var styles:Object = component.styles; + var componentChild:ComponentDescription; + var name:String = component.name; + var output:String = ""; + var outputValue:String = ""; + var namespaces:String; + var value:*; + + + for (var propertyName:String in properties) { + value = properties[propertyName]; + if (value===undefined || value==null) { + continue; + } + output += " "; + + // we should be converting objects into tags + if (value is Object) { + outputValue = XMLUtils.getAttributeSafeString(Object(value).toString()); + output += propertyName + "=\"" + outputValue + "\""; + + } + else { + output += propertyName + "=\"" + XMLUtils.getAttributeSafeString(Object(value).toString()) + "\""; + } + } + + for (var styleName:String in styles) { + value = styles[styleName]; + if (value===undefined || value==null) { + continue; + } + output += " "; + output += styleName + "=\"" + XMLUtils.getAttributeSafeString(Object(styles[styleName]).toString()) + "\""; + } + + if (name) { + if (component.instance is Application) { + name = "Application"; + namespaces = " xmlns:fx=\"http://ns.adobe.com/mxml/2009\""; + namespaces += " xmlns:s=\"library://ns.adobe.com/flex/spark\""; + output = namespaces + output; + } + // we are not handling namespaces here - we could use component descriptor + output = tabs + "<s:" + name + output; + + if (component.children && component.children.length>0) { + output += ">\n"; + + for (var i:int;i<component.children.length;i++) { + componentChild = component.children[i]; + // we should get the properties and styles from the + // the component description + getAppliedPropertiesFromHistory(iDocument, componentChild); + output += getMXMLOutputString(iDocument, componentChild, false, tabs + "\t"); + } + + output += tabs + "</s:" + name + ">\n"; + } + else { + output += "/>\n"; + } + } + else { + output = ""; + } + + isValid = XMLUtils.isValidXML(output); + + if (!isValid) { + error = XMLUtils.validationError; + errorMessage = XMLUtils.validationErrorMessage; + } + else { + error = null; + errorMessage = null; + } + + return output; + } + + + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/flex-radii8/blob/f370bfcf/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentImporter.as ---------------------------------------------------------------------- diff --git a/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentImporter.as b/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentImporter.as new file mode 100644 index 0000000..c48992c --- /dev/null +++ b/Radii8Library/src/com/flexcapacitor/utils/MXMLDocumentImporter.as @@ -0,0 +1,143 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + + + +package com.flexcapacitor.utils { + + import com.flexcapacitor.controller.Radiate; + import com.flexcapacitor.model.IDocument; + import com.flexcapacitor.utils.supportClasses.ComponentDefinition; + + import flash.events.EventDispatcher; + import flash.system.ApplicationDomain; + import flash.utils.getTimer; + + import mx.core.IVisualElement; + + + /** + * Import MXML into a IDocument. Basic support of creating components and apply properties and styles. + * */ + public class MXMLDocumentImporter extends EventDispatcher { + + public var document:IDocument; + + /** + * Import the MXML document into the IDocument. + * */ + public function MXMLDocumentImporter(iDocument:IDocument, id:String, mxml:XML, container:IVisualElement) { + document = iDocument; + + var elName:String = mxml.localName(); + var timer:int = getTimer(); + + Radiate.importingDocument = true; + + // TODO this is a special case we check for since + // we should have already created the application by now + // we should handle this case before we get here (pass in the children of the application xml not application itself) + if (elName=="Application") { + Radiate.setAttributesOnComponent(document.instance, mxml); + } + else { + createChildFromNode(mxml, container); + } + + + for each (var childNode:XML in mxml.children()) { + createChildFromNode(childNode, container); + } + + Radiate.importingDocument = false; + + // using importing document flag it goes down from 5 seconds to 1 second + //Radiate.log.info("Time to import: " + (getTimer()-timer)); + + } + + /** + * Create child from node + * */ + private function createChildFromNode(node:XML, parent:Object):IVisualElement { + var elementName:String = node.localName(); + var domain:ApplicationDomain = ApplicationDomain.currentDomain; + var componentDefinition:ComponentDefinition = Radiate.getDynamicComponentType(elementName); + var className:String; + var classType:Class; + var includeChildren:Boolean = true; + var instance:Object; + + if (componentDefinition==null) { + + } + + className =componentDefinition ? componentDefinition.className :null; + classType = componentDefinition ? componentDefinition.classType as Class :null; + + + if (componentDefinition==null && elementName!="RootWrapperNode") { + //message += " Add this class to Radii8LibrarySparkAssets.sparkManifestDefaults or add the library to the project that contains it."; + var message:String = "Could not find definition for " + elementName + ". The document will be missing elements."; + Radiate.log.error(message); + return null; + } + + // classes to look into for decoding XML + // XMLDecoder, SchemaTypeRegistry, SchemaManager, SchemaProcesser + + + // special case for radio button group + /*var object:* = SchemaTypeRegistry.getInstance().getClass(classType); + var object2:* = SchemaTypeRegistry.getInstance().getClass(elementName); + var object3:* = SchemaTypeRegistry.getInstance().getClass(node); + var sm:mx.rpc.xml.SchemaManager = new mx.rpc.xml.SchemaManager(); + + sm.addNamespaces({s:new Namespace("s", "library://ns.adobe.com/flex/spark")}); + var o:Object = sm.unmarshall(node); + + var q:QName = new QName(null, elementName);*/ + //var object2:* = SchemaTypeRegistry.getInstance().registerClass(; + + + if (componentDefinition!=null) { + instance = Radiate.createComponentForAdd(document, componentDefinition, true); + //Radiate.log.info("MXML Importer adding: " + elementName); + + // calling add before setting properties because some + // properties such as borderVisible need to be set after + // the component is added (maybe) + Radiate.addElement(instance, parent); + + Radiate.setAttributesOnComponent(instance, node); + + } + + + if (includeChildren) { + + for each (var childNode:XML in node.children()) { + createChildFromNode(childNode, instance); + } + } + + return instance as IVisualElement; + } + } +} http://git-wip-us.apache.org/repos/asf/flex-radii8/blob/f370bfcf/Radii8Library/src/com/flexcapacitor/utils/MXMLImporterEvent.as ---------------------------------------------------------------------- diff --git a/Radii8Library/src/com/flexcapacitor/utils/MXMLImporterEvent.as b/Radii8Library/src/com/flexcapacitor/utils/MXMLImporterEvent.as new file mode 100644 index 0000000..e4cdc02 --- /dev/null +++ b/Radii8Library/src/com/flexcapacitor/utils/MXMLImporterEvent.as @@ -0,0 +1,37 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to You under the Apache License, Version 2.0 +// (the "License"); you may not use this file except in compliance with +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// +package com.flexcapacitor.utils +{ + import flash.events.Event; + + + public class MXMLImporterEvent extends Event + { + + public static var INITIALIZE:String = "onInitialize"; + public var idString : String; + + public function MXMLImporterEvent(type : String, indata:String, bubbles : Boolean = false, cancelable : Boolean = false) + { + super( type, bubbles, cancelable ); + idString = indata; + } + } + +} \ No newline at end of file