blob: 5b8bf9ed59e988b98fa5ca0901c8f10fd7a7132e [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 bxf.ui.inspectors {
import bxf.ui.utils.LocalString;
import flash.events.Event;
import mx.containers.HBox;
import mx.containers.VBox;
import mx.controls.Label;
import mx.controls.Spacer;
import mx.core.UIComponent;
import mx.events.PropertyChangeEvent;
import mx.utils.ObjectProxy;
public class DynamicPropertyEditorBase extends VBox implements IPropertiesEditor{
public static const pxSuffix:String = LocalString("$$$/stage/PropertyEditor/PxSuffix/=px");
public static const percentSuffix:String = LocalString("$$$/stage/PropertyEditor/percentSuffix/=%");
public static const secondsSuffix:String = LocalString("$$$/stage/PropertyEditor/secondsSuffix/=s");
private var mProps:ObjectProxy = new ObjectProxy(new Object());
private const tmpWid:int = 190;
private var mNeedToUpdateWidths:Boolean = false;
private var mEditorLayout:XML;
protected var mIcons:Object = new Object();
private var mPropertyEditors:Object = null; // associative array; created once and held onto
public static const MODELEDITED_EVENT:String = "modelEdited";
public static const MODELCHANGED_EVENT:String = "modelChanged";
public static const PROPERTY_ACTIVE_EVENT:String = "propActive";
public static const PROPERTY_INACTIVE_EVENT:String = "propInactive";
private var mLayoutItems:Array = [];
[Bindable]
private var mMaxLabelWidth:int = 0;
public function DynamicPropertyEditorBase(inLayout:XML):void {
mEditorLayout = inLayout;
properties.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
setStyle("verticalGap", 3);
setStyle("top", 6);
setStyle("left", 10);
}
public function reset():void
{
mProps.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
mProps = new ObjectProxy(new Object());
mProps.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChange);
}
public function get properties():Object {
return mProps;
}
public function SetIcon(inKey:String, inIcon:Class):void
{
mIcons[inKey] = inIcon;
}
public function hasEditorForProperty(inPropertyName:String):Boolean
{
if (mPropertyEditors)
{
return mPropertyEditors[inPropertyName] != null;
}
return false;
}
// These props are common to both regular and flash components. Each also overrides this to add more
protected function doGetPropertyEditor(editorEntry:XML):IPropertyEditor {
var editor:IPropertyEditor = null;
if (editorEntry != null){
var editorType:String = editorEntry.@type;
var labelZString:String = editorEntry.@label;
var localizedLabel:String = (labelZString != null) ? LocalString(labelZString) : "";
var primaryPropName:String = editorEntry.property[0].@name;
var tipZString:String = editorEntry.@tooltip;
var tipString:String = (tipZString != null) ? LocalString(tipZString) : "";
var sectionSpacerStr:String = editorEntry.@sectionSpacer;
var sectionSpacer:Boolean = sectionSpacerStr != null && sectionSpacerStr == "yes";
var editorStyle:String = (editorEntry.@style != null) ? editorEntry.@style : "";
switch (editorType) {
case "hotnumber":
var minStr:String = editorEntry.property[0].@minValue;
var minValue:Number = (minStr != null && minStr.length > 0) ? Number(minStr) : Number.NaN;
var maxStr:String = editorEntry.property[0].@maxValue;
var maxValue:Number = (maxStr != null && maxStr.length > 0) ? Number(maxStr) : Number.NaN;
var decStr:String = editorEntry.@decimals;
var decimals:uint = (decStr != null && decStr.length > 0) ? uint(decStr) : 0;
var maxCharStr:String = editorEntry.@maxChars;
var maxChars:int = (maxCharStr != null && maxCharStr.length > 0) ? int(maxCharStr) : 0;
var enforceStr:String = editorEntry.@enforcePrecision;
var enforcePrecision:Boolean = enforceStr != null && enforceStr == "yes";
var incrStr:String = editorEntry.@increment;
var increment:Number = (incrStr != null && incrStr.length > 0) ? Number(incrStr) : 1;
var suffix:String = (editorEntry.@suffix != null) ? editorEntry.@suffix : "";
var convertToPercentStr:String = editorEntry.property[0].@convertToPercent;
var convertToPercent:Boolean = convertToPercentStr != null && convertToPercentStr == "yes";
editor = new HotNumericPropertyEditor(localizedLabel, primaryPropName, suffix, minValue, maxValue, convertToPercent, decimals, increment, enforcePrecision, maxChars);
break;
case "hotnumberunit":
editor = new HotNumericWithUnitsEditor(localizedLabel, primaryPropName, editorEntry);
break;
case "color":
editor = new ColorPropertyEditor(localizedLabel, primaryPropName);
break;
case "checkbox":
if (editorEntry.@labelSide == "left" || editorEntry.@trickMode == "hangingIndent") {
var sectionLabelZStr:String = editorEntry.@sectionLabel;
var sectionLabel:String = (sectionLabelZStr != null)? LocalString(sectionLabelZStr) : "";
editor = new CheckboxPropertyEditor(localizedLabel, primaryPropName, editorEntry.@trickMode == "hangingIndent", sectionLabel);
} else {
editor = new SimpleCheckboxPropertyEditor(localizedLabel, primaryPropName);
}
break;
case "string":
var stringWidth:String = editorEntry.@width;
if (stringWidth != null)
editor = new StringPropertyEditor(localizedLabel, primaryPropName, Number(stringWidth));
else
editor = new StringPropertyEditor(localizedLabel, primaryPropName);
break;
case "stringButton":
var cmdString:String = editorEntry.@cmd;
var cmdVal:Number = (cmdString != null && cmdString.length > 0) ? Number(cmdString) : 0;
editor = new StringButtonPropertyEditor(localizedLabel, primaryPropName, cmdVal, tipString);
break;
case "pictureButton":
var pixCmdString:String = editorEntry.@cmd;
var pixCmdVal:Number = (pixCmdString != null && pixCmdString.length > 0) ? Number(pixCmdString) : 0;
editor = new PictureButtonPropertyEditor(pixCmdVal, editorStyle, tipString);
break;
case "combo":
var displayValues:Array = new Array();
var map:Object = new Object();
for each (var choice:XML in editorEntry.choice) {
var userString:String = choice.@display;
var valueString:String = choice.@value;
displayValues.push(userString);
if (valueString.length > 0)
map[userString] = valueString;
}
editor = new ComboBoxPropertyEditor(localizedLabel, primaryPropName, displayValues, map);
break;
case "toggleButton":
var iconClassStr:String = editorEntry.@iconClass;
var iconClass:Class = iconClassStr ? mIcons[iconClassStr] : null;
var falseStr:String = editorEntry.property[0].@falseValue;
var trueStr:String = editorEntry.property[0].@trueValue;
var buttonWidth:Number = editorEntry.@width;
var commitStr:String = editorEntry.property[0].@commit;
var commit:Boolean = commitStr != null && commitStr == "yes";
editor = new ToggleButtonPropertyEditor(localizedLabel, iconClass, primaryPropName, falseStr, trueStr, commit, editorStyle, buttonWidth);
break;
case "multiIconButton":
var icons:Array = [];
var values:Array = [];
for each (var button:XML in editorEntry.button) {
icons.push(mIcons[button.@icon]);
values.push(button.@value);
}
editor = new MultiIconButtonSelector(localizedLabel, primaryPropName, icons, values, editorStyle);
break;
case "fontPicker":
editor = new FontPropertyEditor(localizedLabel, primaryPropName);
break;
}
if (editor && sectionSpacer == true && (editor is IHUDLayoutElement)) {
(editor as IHUDLayoutElement).sectionSpacer = sectionSpacer;
}
}
return editor;
}
private function updateAvailablePropertyEditors():void {
// really, it's "init", but maybe one day it will be "update"
if (mPropertyEditors == null) {
mPropertyEditors = new Object();
for each (var editorEntry:XML in mEditorLayout.row.editor) {
var editor:IPropertyEditor = doGetPropertyEditor(editorEntry);
for each (var propEntry:XML in editorEntry.property) {
var propName:String = propEntry.@name;
mPropertyEditors[propName] = editor;
}
if (editor != null) {
editor.addEventListener(PropertyEditEvent.VALUE_EDITED, handleEditingChange, false, 0.0, true);
editor.addEventListener(PropertyEditEvent.VALUE_CHANGED, handlePropChanged, false, 0.0, true);
editor.addEventListener(PropertyEditEvent.VALUE_ACTIVE, handlePropActive, false, 0.0, true);
editor.addEventListener(PropertyEditEvent.VALUE_INACTIVE, handlePropInactive, false, 0.0, true);
}
}
}
}
private function onPropertyChange(evt:PropertyChangeEvent):void {
updateAvailablePropertyEditors();
var asIPE:IPropertyEditor = mPropertyEditors[evt.property];
if (null != asIPE) {
if (evt.newValue is Array)
{
var strArray:Array = [];
for each (var i:* in evt.newValue)
strArray.push(String(i));
asIPE.setMultiValue(strArray, String(evt.property));
}
else
asIPE.setValueAsString(String(evt.newValue), String(evt.property));
}
}
// enables live update
protected function handleEditingChange(editEvt:PropertyEditEvent):void {
var propId:String = String(editEvt.property);
var propVal:String = String(editEvt.newValue);
// is reusing mx.events.PropertyChangeEvent clever or dumb? Don't know yet.
var evt:PropertyChangeEvent = new PropertyChangeEvent(MODELEDITED_EVENT, false, false, null, propId, properties[propId], propVal);
properties[propId] = propVal;
dispatchEvent(evt);
}
protected function handlePropChanged(editEvt:PropertyEditEvent):void {
var propId:String = String(editEvt.property);
var propVal:String = String(editEvt.newValue);
// is reusing mx.events.PropertyChangeEvent clever or dumb? Don't know yet.
var evt:PropertyChangeEvent = new PropertyChangeEvent(MODELCHANGED_EVENT, false, false, null, propId, properties[propId], propVal);
properties[propId] = propVal;
dispatchEvent(evt);
}
protected function handlePropActive(editEvt:PropertyEditEvent):void {
var propId:String = String(editEvt.property);
// is reusing mx.events.PropertyChangeEvent clever or dumb? Probably dumb here. FIXME
var evt:PropertyChangeEvent = new PropertyChangeEvent(PROPERTY_ACTIVE_EVENT, false, false, null, propId, null,null);
dispatchEvent(evt);
}
protected function handlePropInactive(editEvt:PropertyEditEvent):void {
var propId:String = String(editEvt.property);
// is reusing mx.events.PropertyChangeEvent clever or dumb? Probably dumb here. FIXME
var evt:PropertyChangeEvent = new PropertyChangeEvent(PROPERTY_INACTIVE_EVENT, false, false, null, propId, null,null);
dispatchEvent(evt);
}
public function rebuildUI():void {
/*
* re-layout properties based on what we have now
*/
// Remove any existing children
for (var i:uint = numChildren; --i >= 0; ) {
removeChildAt(i);
}
updateAvailablePropertyEditors();
mLayoutItems=[];
/*
* For now, we go with the known properties only, keep track of what we've handled and then (do what with?) properties
* the object has that we don't know how to handle. [note: at this instant the "keep track..."
* part is untrue. But it was my intent....
*/
for each (var row:XML in mEditorLayout.row) {
var currRowBox:HBox = null;
var rowLabel:Label = null;
var colNum:int = 0;
var styleStr:String = row.@style;
var labelStr:String = row.@label;
if (labelStr.length)
labelStr = LocalString(labelStr);
var test:XMLList = row.editor;
if (labelStr.length && row.editor[0] == null)
{
rowLabel = new Label();
rowLabel.text = labelStr;
addChild(rowLabel);
}
for each (var editorEntry:XML in row.editor) {
var usesEditor:Boolean = false;
// usesEditor = mProps[editorEntry.property[0].@name != null;
for each (var affectedProperty:String in editorEntry.property.@name) {
if (mProps[affectedProperty] != null) {
usesEditor = true;
break;
}
}
if (usesEditor) {
// The selected object has this property.
var propName:String = editorEntry.property[0].@name; // HACK
var propEditor:UIComponent = mPropertyEditors[propName];
if (propEditor != null) {
if (null == currRowBox) {
if (labelStr.length)
{
rowLabel = new Label();
rowLabel.text = labelStr;
addChild(rowLabel);
}
currRowBox = new HBox;
addChild(currRowBox);
if (styleStr.length)
currRowBox.styleName = styleStr;
currRowBox.setStyle("verticalAlign", "middle");
}
currRowBox.addChild(propEditor);
var asHUDLayoutElem:IHUDLayoutElement = propEditor as IHUDLayoutElement;
if (asHUDLayoutElem && colNum == 0) {
mLayoutItems.push(asHUDLayoutElem);
}
colNum++;
} else {
// This is a known property. We should have had an editor
trace("Internal error: unknown \"known\" property");
}
}
}
for each (var spacer:XML in row.spacer) {
var s:Spacer = new Spacer();
s.height = spacer.@height;
addChild(new HBox().addChild(s));
}
}
// The property editor changed, we need to alert parties that care.
validateNow();
var propEditEvent:Event = new Event("PropertyEditorChanged", true, false);
this.dispatchEvent(propEditEvent);
mNeedToUpdateWidths = true;
callLater(updateWidths);
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (mNeedToUpdateWidths)
updateWidths();
}
private function updateWidths():void {
if (this.parent == null || this.parent.parent == null)
return;
mNeedToUpdateWidths = false;
var maxLblWid:int = 0;
var hudElem:IHUDLayoutElement;
for each (hudElem in mLayoutItems) {
var lblWid:int = hudElem.getLabelWidth();
if (lblWid > maxLblWid)
maxLblWid = lblWid;
}
if (maxLblWid != mMaxLabelWidth)
mMaxLabelWidth = maxLblWid;
//**teb - TODO; use data binding for this!
for each (hudElem in mLayoutItems) {
hudElem.maxSiblingLabelWid = mMaxLabelWidth;
}
}
}
}