/* * 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.
*
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();
}
}
}