/*******************************************************************************
* CogTool Copyright Notice and Distribution Terms
* CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* CogTool is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CogTool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CogTool; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* CogTool makes use of several third-party components, with the
* following notices:
*
* Eclipse SWT version 3.448
* Eclipse GEF Draw2D version 3.2.1
*
* Unless otherwise indicated, all Content made available by the Eclipse
* Foundation is provided to you under the terms and conditions of the Eclipse
* Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this
* Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
*
* CLISP version 2.38
*
* Copyright (c) Sam Steingold, Bruno Haible 2001-2006
* This software is distributed under the terms of the FSF Gnu Public License.
* See COPYRIGHT file in clisp installation folder for more information.
*
* ACT-R 6.0
*
* Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere &
* John R Anderson.
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* Apache Jakarta Commons-Lang 2.1
*
* This product contains software developed by the Apache Software Foundation
* (http://www.apache.org/)
*
* jopt-simple version 1.0
*
* Copyright (c) 2004-2013 Paul R. Holser, Jr.
*
* 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.
*
* Mozilla XULRunner 1.9.0.5
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/.
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The J2SE(TM) Java Runtime Environment version 5.0
*
* Copyright 2009 Sun Microsystems, Inc., 4150
* Network Circle, Santa Clara, California 95054, U.S.A. All
* rights reserved. U.S.
* See the LICENSE file in the jre folder for more information.
******************************************************************************/
package edu.cmu.cs.hcii.cogtool.model;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.EventObject;
import java.util.HashSet;
import java.util.Set;
import edu.cmu.cs.hcii.cogtool.util.NullSafe;
import edu.cmu.cs.hcii.cogtool.util.ObjectLoader;
import edu.cmu.cs.hcii.cogtool.util.ObjectSaver;
/**
* Use this as the basic implementation of a Widget.
* Subclasses will be used in Widget to "render" and provide "Cognitive"
* information.
*
* The idea is that the Widget class has exactly one implementing object.
* The object gets passed a SHAPE which is used to determine the rendering.
*
* @author alexeiser
*/
public class Widget extends ATransitionSource implements IWidget
{
public static final int edu_cmu_cs_hcii_cogtool_model_Widget_version = 0;
public static final String titleVAR = "title";
public static final String auxTextVAR = "auxText";
public static final String levelVAR = "level";
public static final String widgetImageVAR = "widgetImage";
public static final String renderSkinVAR = "renderWidget";
public static final String shapeVAR = "shape";
public static final String widgetTypeVAR = "widgetType";
public static final String parentGroupVAR = "parentGroup";
public static final String parentEltGroupsVAR = "parentEltGroups";
/*
* Class Objects
*/
/**
* The display title for this widget.
* Used for helping users remember what this widget does.
*/
protected String title = "";
/**
* Additional text for a widget
*/
protected String auxText = "";
/**
* Use enumerated types to handle the kind of widget
* The type is not so necessary any more since I have switched to using
* subclasses. Technically, the type on the subclass should match what
* the class type is.
*/
protected WidgetType widgetType;
/**
* Use an enumerated type to define what kind of shape should be displayed
* All aspects of the "viewable" object will be stored in the shape
* The size, the location, the fill etc etc etc. are all shape properties.
*
* A widget simply contains the shape.
* Current possible shapes are
* Square - rectangle
* Ellipse
* Line
* These are set using the TYPE aspect of the shape.
*/
protected AShape shape;
protected byte[] widgetImage;
// TODO: It would be helpful, perhaps, to have a default value
// for a Widget's renderSkin value set in the Design along with
// which skin to use, so that the user did not have to "set"
// every created Widget to render its skin.
protected boolean renderSkin = false;
/**
* The visible level of this widget.
* This is used to control what order the widgets are visible.
*/
protected int level;
/**
* Parent group; currently used for menu headers and menu items
*/
protected SimpleWidgetGroup parentGroup;
protected Set<FrameElementGroup> parentEltGroups =
new HashSet<FrameElementGroup>();
public final static int DEFAULT_HEIGHT = 100;
public final static int DEFAULT_WIDTH = 100;
public final static int DEFAULT_Y = 0;
public final static int DEFAULT_X = 0;
private static ObjectSaver.IDataSaver<Widget> SAVER =
new ObjectSaver.ADataSaver<Widget>() {
@Override
public int getVersion()
{
return edu_cmu_cs_hcii_cogtool_model_Widget_version;
}
@Override
public void saveData(Widget v, ObjectSaver saver)
throws java.io.IOException
{
saver.saveString(v.title, titleVAR);
saver.saveString(v.auxText, auxTextVAR);
saver.saveInt(v.level, levelVAR);
saver.saveBoolean(v.renderSkin, renderSkinVAR);
saver.saveObject(v.widgetImage, widgetImageVAR);
saver.saveObject(v.shape, shapeVAR);
saver.saveObject(v.widgetType, widgetTypeVAR);
if (saver.getPurpose() == IWidget.COPY_TRANSITION_SOURCE_ONLY) {
if (v.isSaveFilteringRequired()) {
saver.saveObject(saver.filterObject(v.parentGroup),
parentGroupVAR);
}
else {
saver.saveObject(v.parentGroup, parentGroupVAR);
}
saver.saveObject(saver.filterObject(v.parentEltGroups),
parentEltGroupsVAR);
}
else {
saver.saveObject(v.parentGroup, parentGroupVAR);
saver.saveObject(v.parentEltGroups, parentEltGroupsVAR);
}
}
};
public static void registerSaver()
{
ObjectSaver.registerSaver(Widget.class.getName(), SAVER);
}
public static class WidgetLoader extends ObjectLoader.AObjectLoader<Widget>
{
@Override
public Widget createObject()
{
return new Widget();
}
@Override
public void set(Widget target, String variable, Object value)
{
if (variable != null) {
if (variable.equals(titleVAR)) {
target.title = (String) value;
}
else if (variable.equals(auxTextVAR)) {
target.auxText = (String) value;
}
else if (variable.equals(shapeVAR)) {
target.shape = (AShape) value;
}
else if (variable.equals(widgetTypeVAR)) {
target.widgetType = (WidgetType) value;
}
else if (variable.equals(widgetImageVAR)) {
target.widgetImage = (byte[]) value;
}
else if (variable.equals(parentGroupVAR)) {
target.parentGroup = (SimpleWidgetGroup) value;
}
}
}
@Override
public void set(Widget target, String variable, int value)
{
if (variable != null) {
if (variable.equals(levelVAR)) {
target.level = value;
}
}
}
@Override
public void set(ObjectLoader l,
Widget target, String variable, boolean value)
{
if (variable != null) {
if (variable.equals(renderSkinVAR)) {
target.renderSkin = value;
// We no longer have to set the skin type for designs
// saved by beta-18 or before. We now default the
// design's skin type to WireFrame. Files saved by
// more recent versions (including trunk versions) have
// a specific skin type. Those saved before beta-20 that
// specified SkinType.None will use WireFrame instead and
// simply leave all renderSkin values "false" (since they
// saved no renderSkin values).
}
}
}
@Override
public Collection<?> createCollection(Widget target,
String variable,
int size)
{
if (variable != null) {
if (variable.equals(parentEltGroupsVAR)) {
return target.parentEltGroups;
}
}
return null;
}
}
public static class WidgetChange extends EventObject
{
public static final int SHAPE = 1;
public static final int TYPE = 2;
public static final int TITLE = 3;
public static final int IMAGE = 4;
public static final int NAME = 6;
public static final int GROUP = 7;
public static final int AUXILIARY = 8;
protected int type;
public boolean isAdd;
public WidgetChange(IWidget widget, int changeType)
{
this(widget, changeType, false);
}
public WidgetChange(IWidget widget, int changeType, boolean add)
{
super(widget);
type = changeType;
isAdd = add;
}
public int getChangeType()
{
return type;
}
}
public static class WidgetLevelComparator implements Comparator<IWidget>
{
/**
* No state, so only one instance is ever needed.
*/
public static WidgetLevelComparator ONLY =
new WidgetLevelComparator();
protected WidgetLevelComparator() { }
/**
* This comparator may be used to sort widgets in ascending level order
*/
public int compare(IWidget w1, IWidget w2)
{
SimpleWidgetGroup g1 = w1.getParentGroup();
SimpleWidgetGroup g2 = w2.getParentGroup();
if ((g1 == g2) && (g1 != null)) {
return g1.indexOf(w1) - g2.indexOf(w2);
}
// if w1.level < w2.level returns negative
// if w1.level = w2.level returns 0
// if w1.level > w2.level returns positive
return w1.getLevel() - w2.getLevel();
}
}
private static ObjectLoader.IObjectLoader<Widget> LOADER =
new WidgetLoader();
public static ObjectLoader.IObjectLoader<Widget> getImportLoader()
{
return LOADER;
}
public static ObjectLoader.IObjectLoader<Widget> getImportLoader2()
{
return LOADER;
}
public static void registerLoader()
{
ObjectLoader.registerLoader(Widget.class.getName(),
edu_cmu_cs_hcii_cogtool_model_Widget_version,
LOADER);
}
/**
* Default constructor of a Widget for use by persistence loading.
* Creates a square widget at (0,0) with size (100,100)
* the widget type is set to Button.
* Set the owner IMMEDIATELY after creation.
*
* This creates a rectangle shaped button widget
*
* In general this default constructor should not be used, other than
* by the loaders.
*/
protected Widget()
{
this(null, WidgetType.Button);
}
/**
* Constructor for a widget takes a bounds and a Widget type
*
* Set the owner IMMEDIATELY after creation.
*
* creates a rectangle shaped t type widget
*
* @param bounds Rectangle
* @param t WidgetType
*/
public Widget(DoubleRectangle bounds, WidgetType t)
{
super();
// Checks null case
setWidgetType(t);
// check if object is visible.
// if its visible, then
// must be after set type
if (bounds != null) {
double w = bounds.getWidth();
double h = bounds.getHeight();
if (w < 1.0 || h < 1.0) {
bounds = new DoubleRectangle(bounds.getX(),
bounds.getY(),
(w < 1.0 ? 1.0 : w),
(h < 1.0 ? 1.0 : h));
}
setShape(new ShapeRectangle(bounds));
} else {
setShape(new ShapeRectangle(Widget.DEFAULT_X,
Widget.DEFAULT_Y,
Widget.DEFAULT_WIDTH,
Widget.DEFAULT_HEIGHT));
}
}
/**
* Test if this Transition source can accept the given AAction.
*
* @param action the AAction to test
* @return true iff the source can accept the given AAction
* @author mlh
*/
public boolean canAccept(AAction action)
{
// Essentially, any screen widget except Noninteractive
// can accept any action because it might have the focus
// (perhaps through tabbing support to be added later!)
return getWidgetType() != WidgetType.Noninteractive;
}
/**
* Returns the title of the Widget.
*
* @return the Widget's title
*/
public String getTitle()
{
return title;
}
/**
* Sets the title of this widget
* The title is displayed in the UI
*
* @param newtitle The new title to display
*/
public void setTitle(String newtitle)
{
// The value of title should never be null.
if (newtitle == null) {
newtitle = "";
}
if (! title.equals(newtitle)) {
title = newtitle;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.TITLE));
}
}
/**
* Sets the name of this widget
* The name is used to help designers recognize what this widget does.
*
* @param newName The new name which this widget will be known by
* @throws IllegalArgumentException if newName is null
*/
@Override
public void setName(String newName)
{
// The value of name should never be null.
if (newName == null) {
throw new IllegalArgumentException("Cannot set name with null identifier!");
}
if (name != newName) {
super.setName(newName);
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.NAME));
}
}
/**
* This returns the widget's background image.
* The resulting image can be null.
*/
public byte[] getImage()
{
return widgetImage;
}
/**
* This sets the widget's background image.
* To control if this image is stretched, centered or tiled, use an
* attribute. A null image is an acceptable value.
*/
public void setImage(byte[] i)
{
if (widgetImage != i) {
widgetImage = i;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.IMAGE));
}
}
/**
* Indicate whether the widget is rendered using the Design's skin.
*/
public boolean isRendered()
{
if (parentGroup != null) {
Boolean render =
(Boolean) parentGroup.getAttribute(WidgetAttributes.IS_RENDERED_ATTR);
return render.booleanValue();
}
return renderSkin;
}
/**
* Set whether the widget is rendered using the Design's skin.
*/
public void setRendered(boolean render)
{
if (parentGroup != null) {
parentGroup.setAttribute(WidgetAttributes.IS_RENDERED_ATTR,
render ? Boolean.TRUE
: Boolean.FALSE);
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.TYPE));
}
else if (renderSkin != render) {
renderSkin = render;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.TYPE));
}
}
@Override
public Object getAttribute(String attr)
{
if (WidgetAttributes.IS_RENDERED_ATTR.equals(attr)) {
return Boolean.valueOf(isRendered());
}
return super.getAttribute(attr);
}
@Override
public void setAttribute(String attr, Object value)
{
if (WidgetAttributes.IS_RENDERED_ATTR.equals(attr)) {
setRendered(((Boolean) value).booleanValue());
}
super.setAttribute(attr, value);
}
/**
* Set the type of this widget
*
* @param t WidgetType
*/
public void setWidgetType(WidgetType t)
{
// The value of type should never be null.
if (t == null) {
throw new IllegalArgumentException
("Cannot set the WidgetType with null identifier!");
}
if (widgetType != t) {
widgetType = t;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.TYPE));
}
}
public WidgetType getWidgetType()
{
return widgetType;
}
/**
* Return the Shape of the object;
* Ensure that if you modify the shape that you call set shape
* to update people who depend on this object.
*/
public AShape getShape()
{
return shape;
}
/**
* Set the shape for this object.
* Ensure that the passed shape is not null;
* otherwise, throw invalid argument exception
*/
public void setShape(AShape s)
{
if (s == null) {
throw new IllegalArgumentException
("Cannot set the shape with a null identifier!");
}
if (! s.equals(shape)) {
shape = s;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.SHAPE));
}
}
public int getLevel()
{
return level;
}
public void setLevel(int newLevel)
{
level = newLevel;
}
public void moveElement(double deltaX, double deltaY)
{
if ((deltaX != 0.0) || (deltaY != 0.0)) {
DoublePoint origin = shape.getOrigin();
setWidgetOrigin(origin.x + deltaX, origin.y + deltaY);
}
}
public void setWidgetOrigin(double x, double y)
{
shape.setOrigin(x, y);
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.SHAPE));
}
public void setWidgetSize(double newWidth, double newHeight)
{
shape.setSize(newWidth, newHeight);
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.SHAPE));
}
public void setShapeType(ShapeType newtype)
{
AShape newShape = ShapeType.getShape(newtype, getEltBounds());
setShape(newShape);
}
public SimpleWidgetGroup getParentGroup()
{
return parentGroup;
}
public FrameElement getRootElement()
{
if (parentGroup != null) {
return parentGroup;
}
return this;
}
public FrameElement getRemoteLabelOwner()
{
// A remote label cannot also be a remote label owner!
FrameElement owner =
(FrameElement)
getAttribute(WidgetAttributes.REMOTE_LABEL_OWNER_ATTR);
if (owner != null) {
return null;
}
// Must be of WidgetType Button, PullDownList, Graffiti,
// or TextBox (whether IS_STANDARD or IS_CUSTOM) or an IWidget
// of WidgetType Radio, Check, or ListBoxItem if IS_STANDARD
// (in which case, the label belongs to the parent SimpleWidgetGroup)
WidgetType widgetType = getWidgetType();
if ((widgetType == WidgetType.Button) ||
(widgetType == WidgetType.PullDownList) ||
(widgetType == WidgetType.Graffiti) ||
(widgetType == WidgetType.TextBox))
{
return this;
}
SimpleWidgetGroup parentGroup = getParentGroup();
if ((parentGroup != null) &&
((widgetType == WidgetType.Radio) ||
(widgetType == WidgetType.Check) ||
(widgetType == WidgetType.ListBoxItem)))
{
return parentGroup;
}
return null;
}
public DoubleRectangle getEltBounds()
{
return getShape().getBounds();
}
public void setParentGroup(SimpleWidgetGroup newParentGroup)
{
parentGroup = newParentGroup;
}
/**
* Returns true if is a standard widget, false if a custom widget
*/
public boolean isStandard()
{
// For some reason, the default value for the IS_STANDARD_ATTR
// was set to IS_STANDARD and not IS_CUSTOM.
// Since it was possible to create standard widgets before attributes
// existed, using the default either way is not going to work.
// This method assumes that it is ok to specify that the widget types
// that do *not* have subclasses may always be IS_STANDARD.
// TODO: If we ever change the default value for the attribute
// definition, we will need to change the following logic.
if (NullSafe.equals(getAttribute(WidgetAttributes.IS_STANDARD_ATTR),
WidgetAttributes.IS_STANDARD))
{
WidgetType widgetType = getWidgetType();
return (widgetType != WidgetType.Menu) &&
(widgetType != WidgetType.ContextMenu) &&
(widgetType != WidgetType.MenuItem) &&
(widgetType != WidgetType.PullDownList) &&
(widgetType != WidgetType.PullDownItem) &&
(widgetType != WidgetType.Radio) &&
(widgetType != WidgetType.ListBoxItem);
}
return false;
}
@Override
public String getNameLabel()
{
String label = title;
if ("".equals(label)) {
label += name;
}
else {
label += " (" + name + ")";
}
return label;
}
@Override
public String getLabel()
{
String label = title;
if ("".equals(label)) {
label = name;
}
return label;
}
/**
* XXX: Widget.toString needs to be improved.
*/
@Override
public String toString()
{
return "Widget " + getName() + " " + shape;
}
/**
* Return that this object (and its subclasses) are WIDGET.
*/
public TransitionSourceType getTransitionSourceType()
{
return TransitionSourceType.Widget;
}
/**
* Checks if 2 objects are identical
*/
public static boolean isIdentical(Widget l, Widget r)
{
return
l.name.equals(r.name) &&
l.title.equals(r.title) &&
(l.level == r.level) &&
l.widgetType.equals(r.widgetType) &&
l.shape.equals(r.shape) &&
((l.widgetImage == r.widgetImage) || ((l.widgetImage != null) &&
Arrays.equals(l.widgetImage, r.widgetImage))) &&
l.transitions.equals(r.transitions) ;
}
public boolean isIdentical(IWidget other)
{
return name.equals(other.getName()) &&
title.equals(other.getTitle()) &&
widgetType.equals(other.getWidgetType()) &&
shape.equals(other.getShape()) &&
renderSkin == other.isRendered();
}
public boolean sameLocation(IWidget other)
{
return (other != null) &&
(shape != null) &&
shape.sameBounds(other.getShape());
}
public boolean sameName(IWidget r)
{
if ((r != null) && (name != null)) {
return name.equals(r.getName());
}
else {
return false;
}
}
/**
* Does not manage the duplication of the parent group or
* any of the frame element groups associated with the fromWidget.
* These issues are left to the caller.
*/
protected void copyState(Widget fromWidget,
Frame.IFrameDuplicator duplicator)
{
AShape fromShape = fromWidget.getShape();
AShape shapeCopy =
ShapeType.getShape(fromShape.getShapeType(),
new DoubleRectangle(fromShape.getBounds()));
setName(fromWidget.getName());
setTitle(fromWidget.getTitle());
setAuxiliaryText(fromWidget.getAuxiliaryText());
setLevel(fromWidget.getLevel());
setImage(fromWidget.getImage());
setShape(shapeCopy);
setWidgetType(fromWidget.getWidgetType());
if (getParentGroup() == null) {
setRendered(fromWidget.isRendered());
}
duplicateTransitions(fromWidget, duplicator);
copyAttributes(fromWidget);
}
/**
* Create a "deep" copy of this widget.
* <p>
* It is the responsibility of the caller to "place" the copy
* (usually by adding it to an Frame).
*
* @param duplicator the manager of duplicate Frame instances
* @return the widget copy
* @author mlh
*/
public IWidget duplicate(Frame.IFrameDuplicator duplicator)
{
Widget widgetCopy = new Widget();
widgetCopy.copyState(this, duplicator);
return widgetCopy;
}
/**
* Indicate whether or not the parent widget group should be filtered.
* TODO: This is a hack to handle cut/copy/paste for IMenuHeaders;
* currently, all other uses of parent IWidgetGroups keep all of their
* children!
*/
protected boolean isSaveFilteringRequired()
{
return false;
}
/**
* Adds this element to the given association
*/
public void addToEltGroup(FrameElementGroup eltGroup)
{
parentEltGroups.add(eltGroup);
}
/**
* Removes this element from the given association
*/
public void removeFromEltGroup(FrameElementGroup eltGroup)
{
parentEltGroups.remove(eltGroup);
}
public Set<FrameElementGroup> getEltGroups()
{
return parentEltGroups;
}
/**
* Get the additional text for the element.
*/
public String getAuxiliaryText()
{
return auxText;
}
/**
* Set the additional text for the element
*/
public void setAuxiliaryText(String newAuxText)
{
if (newAuxText == null) {
throw new IllegalArgumentException("New aux text cannot be null");
}
if (! auxText.equals(newAuxText)) {
auxText = newAuxText;
raiseAlert(new Widget.WidgetChange(this, Widget.WidgetChange.AUXILIARY));
}
}
public String getTextualCue()
{
StringBuilder concat = new StringBuilder();
FrameElement remoteLabelOwner = getRemoteLabelOwner();
if (remoteLabelOwner == this) {
// At this point, we know we are a valid remote label owner.
IWidget remoteLabel =
(IWidget) getAttribute(WidgetAttributes.REMOTE_LABEL_ATTR);
if (remoteLabel != null) {
TextualCueBuilder.append(remoteLabel.getTextualCue(), concat);
}
}
TextualCueBuilder.append(getTitle(), concat);
TextualCueBuilder.append(getAuxiliaryText(), concat);
return concat.toString();
}
}