/* * Copyright (c) 2009-2010 Florian Nuecke * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package de.mightypirates.asul.components { import de.mightypirates.asul.AsulFactory; import de.mightypirates.asul.interfaces.IText; import de.mightypirates.utils.Definition; import de.mightypirates.utils.events.LECUtil; import de.mightypirates.utils.fpEquals; import de.mightypirates.utils.string.parseDefinitions; import de.mightypirates.validation.validateBoolean; import de.mightypirates.validation.validateInt; import de.mightypirates.validation.validateNumber; import de.mightypirates.validation.validateString; import de.mightypirates.validation.validateUInt; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.StageQuality; import flash.events.Event; import flash.events.TextEvent; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFieldType; import flash.text.TextFormat; /** * A text box, either to be used for input or just as a label. * *

* This element uses a TextField. The following options can be * set:
* TODO write list *

* *

* Note that adding or removing event handlers to this class checks for the * event type, and if it is a type associated with the text field it is added * to the textfield instead of this instance, allowing seamless event * interaction with the text field in use. The events redirected are:
* TextEvent.LINK, TextEvent.TEXT_INPUT, Event.CHANGE and Event.SCROLL. *

* * @author fnuecke */ public class Text extends Box implements IText { // ---------------------------------------------------------------------- // // Tag name // ---------------------------------------------------------------------- // /** * The tag name of this ASUL object. All ASUL objects must define this * constant, to allow registration with the AsulFactory class. */ public static const tagname:String = "text"; /** * For access through instances. */ override public function get tagname():String { return Text.tagname; } // ---------------------------------------------------------------------- // // Variables // ---------------------------------------------------------------------- // /** * The textfield used for displaying the text. */ private var textField_:TextField; /** * Original HTML text */ private var orgText_:String = ""; /** * Rasterized text, if rasterize is set to true. */ private var buffer_:Bitmap; /** * The text currently displayed in the buffer. Used to check if repainting * is necessary. */ private var bufferText_:String; /** * Used to not trigger buffer update when batch setting attributes. */ private var allowUpdate_:Boolean = false; // ---------------------------------------------------------------------- // // Construction // ---------------------------------------------------------------------- // /** * Creates a new textfield. * * @param data * the xml data to parse for generating this element. * @param factory * the AsulFactory which spawned this object. Used for creating * child nodes and accessing some properties, e.g. the * localizer. */ public function Text(data:XML, factory:AsulFactory) { super(data, factory); init(data); } private function init(data:XML):void { textField_ = new TextField(); textField_.defaultTextFormat = new TextFormat(); // Default to non selectable. textField_.selectable = false; // Default to not input. textField_.type = TextFieldType.DYNAMIC; initAttribute(data, "autosize"); initAttribute(data, "align"); initAttribute(data, "bold"); initAttribute(data, "color"); initAttribute(data, "font"); initAttribute(data, "input"); initAttribute(data, "italic"); initAttribute(data, "maxchars"); initAttribute(data, "multiline"); initAttribute(data, "password"); initAttribute(data, "restrict"); initAttribute(data, "selectable"); initAttribute(data, "size"); initAttribute(data, "spacing"); initAttribute(data, "underline"); initAttribute(data, "wordwrap"); // Initial text value. initAttribute(data, "value"); addChild(textField_); // Rasterize? Can be used when rotating text elements and not embedding // the font. initAttribute(data, "rasterize"); allowUpdate_ = true; updateSizeAndBuffer(true); } /** * @inheritDoc */ override public function dispose():void { factory_ && factory_.disposeDisplayObject(buffer_, true); super.dispose(); textField_ = null; buffer_ = null; } // ---------------------------------------------------------------------- // // Accessors // ---------------------------------------------------------------------- // /** * @inheritDoc */ override public function setAttribute(name:String, value:*):void { if (textField_) { var fmt:TextFormat = textField_.defaultTextFormat; switch(name) { case "align": fmt.align = validateString(value, fmt.align); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "autosize": textField_.autoSize = validateString(value, textField_.autoSize); allowUpdate_ && updateSizeAndBuffer(true); return; case "bold": fmt.bold = validateBoolean(value, Boolean(fmt.bold)); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "color": fmt.color = validateUInt(value, uint(fmt.color)); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "font": fmt.font = validateString(value, fmt.font); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "input": textField_.type = validateBoolean(value, textField_.type == TextFieldType.INPUT) ? TextFieldType.INPUT : TextFieldType.DYNAMIC; allowUpdate_ && updateSizeAndBuffer(true); return; case "italic": fmt.italic = validateBoolean(value, Boolean(fmt.italic)); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "maxchars": textField_.maxChars = validateInt(value, textField_.maxChars); allowUpdate_ && updateSizeAndBuffer(true); return; case "multiline": textField_.multiline = validateBoolean(value, textField_.multiline); allowUpdate_ && updateSizeAndBuffer(true); return; case "password": textField_.displayAsPassword = validateBoolean(value, textField_.displayAsPassword); allowUpdate_ && updateSizeAndBuffer(true); return; case "rasterize": if (validateBoolean(value, false)) { if (!buffer_) { buffer_ = new Bitmap(); } if (contains(textField_)) { removeChild(textField_); } addChild(buffer_); lec_.add(this, Event.ADDED_TO_STAGE, handleAddedToStage, LECUtil.AUTO_REMOVE); } else { if (buffer_) { if (buffer_.bitmapData) { buffer_.bitmapData.dispose(); } if (contains(buffer_)) { removeChild(buffer_); } } buffer_ = null; addChild(textField_); lec_.remove(this); } bufferText_ = null; allowUpdate_ && updateSizeAndBuffer(true); return; case "restrict": textField_.restrict = validateString(value, textField_.restrict); allowUpdate_ && updateSizeAndBuffer(true); return; case "selectable": textField_.selectable = validateBoolean(value, textField_.selectable); allowUpdate_ && updateSizeAndBuffer(true); return; case "size": fmt.size = validateNumber(value, Number(fmt.size), 0); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "spacing": fmt.letterSpacing = validateNumber(value, Number(fmt.letterSpacing)); textField_.defaultTextFormat = fmt; htmlText = orgText_; allowUpdate_ && updateSizeAndBuffer(true); return; case "underline": fmt.underline = validateBoolean(value, Boolean(fmt.underline)); allowUpdate_ && updateSizeAndBuffer(true); return; case "value": parseText(validateString(value, text)); return; case "wordwrap": textField_.wordWrap = validateBoolean(value, textField_.wordWrap); allowUpdate_ && updateSizeAndBuffer(true); return; } } super.setAttribute(name, value); } /** * @inheritDoc */ public function get htmlText():String { return textField_.htmlText; } /** * @private */ public function set htmlText(newText:String):void { if (newText == textField_.htmlText) { return; } textField_.htmlText = newText; orgText_ = newText; allowUpdate_ && updateSizeAndBuffer(); } /** * @inheritDoc */ public function get text():String { return textField_.text; } /** * @private */ public function set text(newText:String):void { if (newText == textField_.text) { return; } textField_.text = newText; orgText_ = newText; allowUpdate_ && updateSizeAndBuffer(); } /** * @inheritDoc */ public function parseText(newText:String):void { // Check new input. var parsed:Array = parseDefinitions(newText); if (parsed.length > 0) { var command:String = Definition(parsed[0]).name; var args:Array = Definition(parsed[0]).args; if (command == "localize" && args.length > 0 && factory_.localizer) { // OK, check for default, then register. if (args.length > 1) { htmlText = args[1]; } factory_.localizer.registerObject(this, "htmlText", args[0]); } else { // Invalid. htmlText = newText; } } else { // Failed parsing, use input directly. htmlText = newText; } } /** * @inheritDoc */ public function grabFocus():void { if (stage) { stage.focus = textField_; } } // ---------------------------------------------------------------------- // // Textbox Size // ---------------------------------------------------------------------- // /** * @inheritDoc */ override public function set maxWidth(value:Number):void { if (fpEquals(value, maxWidth)) { return; } super.maxWidth = value; allowUpdate_ && updateSizeAndBuffer(); } /** * @inheritDoc */ override protected function handleSizeChange():Boolean { if (textField_ && textField_.autoSize == TextFieldAutoSize.NONE) { textField_.width = (background_.width - padding_[0] - padding_[2]) / scaleX; textField_.height = (background_.height - padding_[1] - padding_[3]) / scaleX; } return false; } // ---------------------------------------------------------------------- // // Events // ---------------------------------------------------------------------- // /** * @inheritDoc */ override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void { if (isTextFieldEvent(type)) { textField_.addEventListener(type, listener, useCapture, priority, useWeakReference); } else { super.addEventListener(type, listener, useCapture, priority, useWeakReference); } } /** * @inheritDoc */ override public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void { if (isTextFieldEvent(type)) { textField_.removeEventListener(type, listener, useCapture); } else { super.removeEventListener(type, listener, useCapture); } } /** * Utility function to determine if an event is an event type used by the * text field. * * @param eventType * the type to check. * @return true if the event is dispatched by the text field, * else false. */ private function isTextFieldEvent(eventType:String):Boolean { return eventType == TextEvent.LINK || eventType == TextEvent.TEXT_INPUT || eventType == Event.CHANGE || eventType == Event.SCROLL; } /** * Called when autosize is on and either the maximum width or the text * changes. */ private function updateSizeAndBuffer(force:Boolean = false):void { if (!textField_) { return; } // Disable rotation for proper sizes. var rot:Number = rotation; // Autosizing. if (textField_.autoSize != TextFieldAutoSize.NONE) { rotation = 0; // First, figure out the width actually needed / used, by allowing // the maxwidth. textField_.width = maxWidth; // Then condense it to the used width plus some buffer. textField_.width = textField_.textWidth + 5; super.width = textField_.width + padding_[0] + padding_[2]; // Finally, adjust the height. super.height = textField_.height + padding_[1] + padding_[3]; } // Buffering? if (buffer_) { if (int(textField_.width) != 0 && int(textField_.height) != 0) { rotation = 0; // Got a size, see if it differs from the current one, or if // there isn't even a current buffer. if (!buffer_.bitmapData || buffer_.bitmapData.width != int(textField_.width) || buffer_.bitmapData.height != int(textField_.height)) { if (buffer_.bitmapData) { // Have an old buffer, clear it. buffer_.bitmapData.dispose(); } // Create new buffer and paint. buffer_.bitmapData = new BitmapData( int(textField_.width), int(textField_.height), true, 0); buffer_.smoothing = true; // Need repaint because size changed. drawBuffer(); } else if (force || bufferText_ != orgText_) { // Need repaint because text changed (or forced). drawBuffer(); } } else if (buffer_.bitmapData) { // No size but bitmapdata, clear it. buffer_.bitmapData.dispose(); buffer_.bitmapData = null; } } // Restore rotation. rotation = rot; } /** * Render the text into the buffer. */ private function drawBuffer():void { buffer_.bitmapData.fillRect(buffer_.bitmapData.rect, 0); if (stage) { var q:String = stage.quality; stage.quality = StageQuality.BEST; buffer_.bitmapData.draw(textField_); stage.quality = q; } else { buffer_.bitmapData.draw(textField_); } bufferText_ = orgText_; } /** * Redraw buffer when added to stage (for quality). */ private function handleAddedToStage(event:Event):void { drawBuffer(); } } }