/*
@VaadinApache2LicenseForJavaFiles@
*/
package com.vaadin.ui;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.vaadin.Application;
import com.vaadin.event.ActionManager;
import com.vaadin.event.EventRouter;
import com.vaadin.event.MethodEventSource;
import com.vaadin.event.ShortcutListener;
import com.vaadin.terminal.ErrorMessage;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.Terminal;
import com.vaadin.terminal.gwt.client.ComponentState;
import com.vaadin.terminal.gwt.client.communication.ClientRpc;
import com.vaadin.terminal.gwt.client.communication.ServerRpc;
import com.vaadin.terminal.gwt.server.ClientMethodInvocation;
import com.vaadin.terminal.gwt.server.ComponentSizeValidator;
import com.vaadin.terminal.gwt.server.ResourceReference;
import com.vaadin.terminal.gwt.server.RpcManager;
import com.vaadin.terminal.gwt.server.RpcTarget;
import com.vaadin.terminal.gwt.server.ServerRpcManager;
import com.vaadin.tools.ReflectTools;
/**
* An abstract class that defines default implementation for the
* {@link Component} interface. Basic UI components that are not derived from an
* external component can inherit this class to easily qualify as Vaadin
* components. Most components in Vaadin do just that.
*
* @author Vaadin Ltd.
* @version
* @VERSION@
* @since 3.0
*/
@SuppressWarnings("serial")
public abstract class AbstractComponent implements Component, MethodEventSource {
/* Private members */
/**
* Application specific data object. The component does not use or modify
* this.
*/
private Object applicationData;
/**
* The container this component resides in.
*/
private HasComponents parent = null;
/**
* The EventRouter used for the event model.
*/
private EventRouter eventRouter = null;
/**
* The internal error message of the component.
*/
private ErrorMessage componentError = null;
/**
* Locale of this component.
*/
private Locale locale;
/**
* The component should receive focus (if {@link Focusable}) when attached.
*/
private boolean delayedFocus;
/**
* List of repaint request listeners or null if not listened at all.
*/
private LinkedList
* Gets the component's description, used in tooltips and can be displayed
* directly in certain other components such as forms. The description can
* be used to briefly describe the state of the component to the user. The
* description string may contain certain XML tags:
*
* String
. Caption is the visible
* name of the component. This method will trigger a
* {@link RepaintRequestEvent}.
*
* @param caption
* the new caption String
for the component.
*/
public void setCaption(String caption) {
getState().setCaption(caption);
requestRepaint();
}
/*
* Don't add a JavaDoc comment here, we use the default documentation from
* implemented interface.
*/
public Locale getLocale() {
if (locale != null) {
return locale;
}
if (parent != null) {
return parent.getLocale();
}
final Application app = getApplication();
if (app != null) {
return app.getLocale();
}
return null;
}
/**
* Sets the locale of this component.
*
*
* // Component for which the locale is meaningful
* InlineDateField date = new InlineDateField("Datum");
*
* // German language specified with ISO 639-1 language
* // code and ISO 3166-1 alpha-2 country code.
* date.setLocale(new Locale("de", "DE"));
*
* date.setResolution(DateField.RESOLUTION_DAY);
* layout.addComponent(date);
*
*
*
* @param locale
* the locale to become this component's locale.
*/
public void setLocale(Locale locale) {
this.locale = locale;
// FIXME: Reload value if there is a converter
requestRepaint();
}
/*
* Gets the component's icon resource. Don't add a JavaDoc comment here, we
* use the default documentation from implemented interface.
*/
public Resource getIcon() {
ResourceReference ref = ((ResourceReference) getState().getIcon());
if (ref == null) {
return null;
} else {
return ref.getResource();
}
}
/**
* Sets the component's icon. This method will trigger a
* {@link RepaintRequestEvent}.
*
* @param icon
* the icon to be shown with the component's caption.
*/
public void setIcon(Resource icon) {
if (icon == null) {
getState().setIcon(null);
} else {
getState().setIcon(new ResourceReference(icon));
}
requestRepaint();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component#isEnabled()
*/
public boolean isEnabled() {
return getState().isEnabled();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component#setEnabled(boolean)
*/
public void setEnabled(boolean enabled) {
if (getState().isEnabled() != enabled) {
getState().setEnabled(enabled);
requestRepaint();
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.gwt.client.Connector#isConnectorEnabled()
*/
public boolean isConnectorEnabled() {
if (getParent() == null) {
// No parent -> the component cannot receive updates from the client
return false;
} else {
boolean thisEnabledAndVisible = isEnabled() && isVisible();
if (!thisEnabledAndVisible) {
return false;
}
if (!getParent().isConnectorEnabled()) {
return false;
}
return getParent().isComponentVisible(this);
}
}
/*
* Tests if the component is in the immediate mode. Don't add a JavaDoc
* comment here, we use the default documentation from implemented
* interface.
*/
public boolean isImmediate() {
return getState().isImmediate();
}
/**
* Sets the component's immediate mode to the specified status. This method
* will trigger a {@link RepaintRequestEvent}.
*
* @param immediate
* the boolean value specifying if the component should be in the
* immediate mode after the call.
* @see Component#isImmediate()
*/
public void setImmediate(boolean immediate) {
getState().setImmediate(immediate);
requestRepaint();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component#isVisible()
*/
public boolean isVisible() {
return getState().isVisible();
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component#setVisible(boolean)
*/
public void setVisible(boolean visible) {
if (getState().isVisible() == visible) {
return;
}
getState().setVisible(visible);
requestRepaint();
if (getParent() != null) {
// Must always repaint the parent (at least the hierarchy) when
// visibility of a child component changes.
getParent().requestRepaint();
}
}
/**
*
*
*
*
* Tag
* Description
* Example
*
*
* <b>
* bold
* bold text
*
*
* <i>
* italic
* italic text
*
*
* <u>
* underlined
* underlined text
*
*
* <br>
* linebreak
* N/A
*
*
* <ul>
*
* <li>item1
* <li>item1
* </ul>item list
*
*
*
*
*
* These tags may be nested. *
* * @return component's descriptionString
*/
public String getDescription() {
return getState().getDescription();
}
/**
* Sets the component's description. See {@link #getDescription()} for more
* information on what the description is. This method will trigger a
* {@link RepaintRequestEvent}.
*
* The description is displayed as HTML/XHTML in tooltips or directly in
* certain components so care should be taken to avoid creating the
* possibility for HTML injection and possibly XSS vulnerabilities.
*
* @param description
* the new description string for the component.
*/
public void setDescription(String description) {
getState().setDescription(description);
requestRepaint();
}
/*
* Gets the component's parent component. Don't add a JavaDoc comment here,
* we use the default documentation from implemented interface.
*/
public HasComponents getParent() {
return parent;
}
/**
* Returns the closest ancestor with the given type.
* * To find the Window that contains the component, use {@code Window w = * getParent(Window.class);} *
* * @paramErrorMessage
of the component.
*/
public void setComponentError(ErrorMessage componentError) {
this.componentError = componentError;
fireComponentErrorEvent();
requestRepaint();
}
/*
* Tests if the component is in read-only mode. Don't add a JavaDoc comment
* here, we use the default documentation from implemented interface.
*/
public boolean isReadOnly() {
return getState().isReadOnly();
}
/*
* Sets the component's read-only mode. Don't add a JavaDoc comment here, we
* use the default documentation from implemented interface.
*/
public void setReadOnly(boolean readOnly) {
getState().setReadOnly(readOnly);
requestRepaint();
}
/*
* Gets the parent window of the component. Don't add a JavaDoc comment
* here, we use the default documentation from implemented interface.
*/
public Root getRoot() {
if (parent == null) {
return null;
} else {
return parent.getRoot();
}
}
/*
* Notify the component that it's attached to a window. Don't add a JavaDoc
* comment here, we use the default documentation from implemented
* interface.
*/
public void attach() {
getRoot().componentAttached(this);
requestRepaint();
if (delayedFocus) {
focus();
}
setActionManagerViewer();
}
/*
* Detach the component from application. Don't add a JavaDoc comment here,
* we use the default documentation from implemented interface.
*/
public void detach() {
if (actionManager != null) {
// Remove any existing viewer. Root cast is just to make the
// compiler happy
actionManager.setViewer((Root) null);
}
getRoot().componentDetached(this);
}
/**
* Sets the focus for this component if the component is {@link Focusable}.
*/
protected void focus() {
if (this instanceof Focusable) {
final Application app = getApplication();
if (app != null) {
getRoot().setFocusedComponent((Focusable) this);
delayedFocus = false;
} else {
delayedFocus = true;
}
}
}
/**
* Gets the application object to which the component is attached.
*
* * The method will return {@code null} if the component is not currently * attached to an application. This is often a problem in constructors of * regular components and in the initializers of custom composite * components. A standard workaround is to move the problematic * initialization to {@link #attach()}, as described in the documentation of * the method. *
** This method is not meant to be overridden. Due to CDI requirements we * cannot declare it as final even though it should be final. *
* * @return the parent application of the component ornull
.
* @see #attach()
*/
public Application getApplication() {
if (parent == null) {
return null;
} else {
return parent.getApplication();
}
}
/**
* Build CSS compatible string representation of height.
*
* @return CSS height
*/
private String getCSSHeight() {
if (getHeightUnits() == Unit.PIXELS) {
return ((int) getHeight()) + getHeightUnits().getSymbol();
} else {
return getHeight() + getHeightUnits().getSymbol();
}
}
/**
* Build CSS compatible string representation of width.
*
* @return CSS width
*/
private String getCSSWidth() {
if (getWidthUnits() == Unit.PIXELS) {
return ((int) getWidth()) + getWidthUnits().getSymbol();
} else {
return getWidth() + getWidthUnits().getSymbol();
}
}
/**
* Returns the shared state bean with information to be sent from the server
* to the client.
*
* Subclasses should override this method and set any relevant fields of the
* state returned by super.getState().
*
* @since 7.0
*
* @return updated component shared state
*/
public ComponentState getState() {
if (null == sharedState) {
sharedState = createState();
}
return sharedState;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.ui.Component#updateState()
*/
public void updateState() {
// TODO This logic should be on the client side and the state should
// simply be a data object with "width" and "height".
if (getHeight() >= 0
&& (getHeightUnits() != Unit.PERCENTAGE || ComponentSizeValidator
.parentCanDefineHeight(this))) {
getState().setHeight("" + getCSSHeight());
} else {
getState().setHeight("");
}
if (getWidth() >= 0
&& (getWidthUnits() != Unit.PERCENTAGE || ComponentSizeValidator
.parentCanDefineWidth(this))) {
getState().setWidth("" + getCSSWidth());
} else {
getState().setWidth("");
}
ErrorMessage error = getErrorMessage();
if (null != error) {
getState().setErrorMessage(error.getFormattedHtmlMessage());
} else {
getState().setErrorMessage(null);
}
}
/**
* Creates the shared state bean to be used in server to client
* communication.
* * By default a state object of the defined return type of * {@link #getState()} is created. Subclasses can override this method and * return a new instance of the correct state class but this should rarely * be necessary. *
*
* No configuration of the values of the state should be performed in
* {@link #createState()}.
*
* @since 7.0
*
* @return new shared state object
*/
protected ComponentState createState() {
try {
Method m = getClass().getMethod("getState", (Class[]) null);
Class extends ComponentState> type = (Class extends ComponentState>) m
.getReturnType();
return type.newInstance();
} catch (Exception e) {
getLogger().log(
Level.INFO,
"Error determining state object class for "
+ getClass().getName());
}
// Fall back to ComponentState if detection fails for some reason.
return new ComponentState();
}
/* Documentation copied from interface */
public void requestRepaint() {
// Invisible components (by flag in this particular component) do not
// need repaints
if (!getState().isVisible()) {
return;
}
fireRequestRepaintEvent();
}
/**
* Fires the repaint request event.
*
* @param alreadyNotified
*/
// Notify listeners only once
private void fireRequestRepaintEvent() {
// Notify the listeners
if (repaintRequestListeners != null
&& !repaintRequestListeners.isEmpty()) {
final Object[] listeners = repaintRequestListeners.toArray();
final RepaintRequestEvent event = new RepaintRequestEvent(this);
for (int i = 0; i < listeners.length; i++) {
((RepaintRequestListener) listeners[i]).repaintRequested(event);
}
}
}
/* Documentation copied from interface */
public void addListener(RepaintRequestListener listener) {
if (repaintRequestListeners == null) {
repaintRequestListeners = new LinkedList
* Registers a new listener with the specified activation method to listen
* events generated by this component. If the activation method does not
* have any arguments the event object will not be passed to it when it's
* called.
*
* This method additionally informs the event-api to route events with the
* given eventIdentifier to the components handleEvent function call.
*
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* This method additionally informs the event-api to stop routing events
* with the given eventIdentifier to the components handleEvent function
* call.
*
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* Registers a new listener with the specified activation method to listen
* events generated by this component. If the activation method does not
* have any arguments the event object will not be passed to it when it's
* called.
*
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* Convenience method for registering a new listener with the specified
* activation method to listen events generated by this component. If the
* activation method does not have any arguments the event object will not
* be passed to it when it's called.
*
* This version of
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* Note: Using this method is discouraged because it cannot be checked
* during compilation. Use {@link #addListener(Class, Object, Method)} or
* {@link #addListener(com.vaadin.ui.Component.Listener)} instead.
*
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
*
* Removes one registered listener method. The given method owned by the
* given object will no longer be called when the specified events are
* generated by this component.
*
* This version of
* For more information on the inheritable event mechanism see the
* {@link com.vaadin.event com.vaadin.event package documentation}.
* object
's methods that are
* registered to listen to events of type eventType
generated
* by this component.
*
* object
listens to.
* @param target
* the target object that has registered to listen to events of
* type eventType
with one or more methods.
*
* @since 6.2
*/
protected void removeListener(String eventIdentifier, Class> eventType,
Object target) {
if (eventRouter != null) {
eventRouter.removeListener(eventType, target);
if (!eventRouter.hasListeners(eventType)) {
getState().removeRegisteredEventListener(eventIdentifier);
requestRepaint();
}
}
}
/**
* addListener
gets the name of the activation
* method as a parameter. The actual method is reflected from
* object
, and unless exactly one match is found,
* java.lang.IllegalArgumentException
is thrown.
* object
's methods that are
* registered to listen to events of type eventType
generated
* by this component.
*
* object
listens to.
* @param target
* the target object that has registered to listen to events of
* type eventType
with one or more methods.
*/
public void removeListener(Class> eventType, Object target) {
if (eventRouter != null) {
eventRouter.removeListener(eventType, target);
}
}
/**
* Removes one registered listener method. The given method owned by the
* given object will no longer be called when the specified events are
* generated by this component.
*
* object
listens to.
* @param target
* target object that has registered to listen to events of type
* eventType
with one or more methods.
* @param method
* the method owned by target
that's registered to
* listen to events of type eventType
.
*/
public void removeListener(Class> eventType, Object target, Method method) {
if (eventRouter != null) {
eventRouter.removeListener(eventType, target, method);
}
}
/**
* removeListener
gets the name of the
* activation method as a parameter. The actual method is reflected from
* target
, and unless exactly one match is found,
* java.lang.IllegalArgumentException
is thrown.
* object
listens to.
* @param target
* the target object that has registered to listen to events of
* type eventType
with one or more methods.
* @param methodName
* the name of the method owned by target
that's
* registered to listen to events of type eventType
.
*/
public void removeListener(Class> eventType, Object target,
String methodName) {
if (eventRouter != null) {
eventRouter.removeListener(eventType, target, methodName);
}
}
/**
* Returns all listeners that are registered for the given event type or one
* of its subclasses.
*
* @param eventType
* The type of event to return listeners for.
* @return A collection with all registered listeners. Empty if no listeners
* are found.
*/
public Collection> getListeners(Class> eventType) {
if (eventType.isAssignableFrom(RepaintRequestEvent.class)) {
// RepaintRequestListeners are not stored in eventRouter
if (repaintRequestListeners == null) {
return Collections.EMPTY_LIST;
} else {
return Collections
.unmodifiableCollection(repaintRequestListeners);
}
}
if (eventRouter == null) {
return Collections.EMPTY_LIST;
}
return eventRouter.getListeners(eventType);
}
/**
* Sends the event to all listeners.
*
* @param event
* the Event to be sent to all listeners.
*/
protected void fireEvent(Component.Event event) {
if (eventRouter != null) {
eventRouter.fireEvent(event);
}
}
/* Component event framework */
/*
* Registers a new listener to listen events generated by this component.
* Don't add a JavaDoc comment here, we use the default documentation from
* implemented interface.
*/
public void addListener(Component.Listener listener) {
addListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD);
}
/*
* Removes a previously registered listener from this component. Don't add a
* JavaDoc comment here, we use the default documentation from implemented
* interface.
*/
public void removeListener(Component.Listener listener) {
removeListener(Component.Event.class, listener, COMPONENT_EVENT_METHOD);
}
/**
* Emits the component event. It is transmitted to all registered listeners
* interested in such events.
*/
protected void fireComponentEvent() {
fireEvent(new Component.Event(this));
}
/**
* Emits the component error event. It is transmitted to all registered
* listeners interested in such events.
*/
protected void fireComponentErrorEvent() {
fireEvent(new Component.ErrorEvent(getComponentError(), this));
}
/**
* Sets the data object, that can be used for any application specific data.
* The component does not use or modify this data.
*
* @param data
* the Application specific data.
* @since 3.1
*/
public void setData(Object data) {
applicationData = data;
}
/**
* Gets the application specific data. See {@link #setData(Object)}.
*
* @return the Application specific data set with setData function.
* @since 3.1
*/
public Object getData() {
return applicationData;
}
/* Sizeable and other size related methods */
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#getHeight()
*/
public float getHeight() {
return height;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#getHeightUnits()
*/
public Unit getHeightUnits() {
return heightUnit;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#getWidth()
*/
public float getWidth() {
return width;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#getWidthUnits()
*/
public Unit getWidthUnits() {
return widthUnit;
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setHeight(float, Unit)
*/
public void setHeight(float height, Unit unit) {
if (unit == null) {
throw new IllegalArgumentException("Unit can not be null");
}
this.height = height;
heightUnit = unit;
requestRepaint();
// ComponentSizeValidator.setHeightLocation(this);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setSizeFull()
*/
public void setSizeFull() {
setWidth(100, Unit.PERCENTAGE);
setHeight(100, Unit.PERCENTAGE);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setSizeUndefined()
*/
public void setSizeUndefined() {
setWidth(-1, Unit.PIXELS);
setHeight(-1, Unit.PIXELS);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setWidth(float, Unit)
*/
public void setWidth(float width, Unit unit) {
if (unit == null) {
throw new IllegalArgumentException("Unit can not be null");
}
this.width = width;
widthUnit = unit;
requestRepaint();
// ComponentSizeValidator.setWidthLocation(this);
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setWidth(java.lang.String)
*/
public void setWidth(String width) {
Size size = parseStringSize(width);
if (size != null) {
setWidth(size.getSize(), size.getUnit());
} else {
setWidth(-1, Unit.PIXELS);
}
}
/*
* (non-Javadoc)
*
* @see com.vaadin.terminal.Sizeable#setHeight(java.lang.String)
*/
public void setHeight(String height) {
Size size = parseStringSize(height);
if (size != null) {
setHeight(size.getSize(), size.getUnit());
} else {
setHeight(-1, Unit.PIXELS);
}
}
/*
* Returns array with size in index 0 unit in index 1. Null or empty string
* will produce {-1,Unit#PIXELS}
*/
private static Size parseStringSize(String s) {
if (s == null) {
return null;
}
s = s.trim();
if ("".equals(s)) {
return null;
}
float size = 0;
Unit unit = null;
Matcher matcher = sizePattern.matcher(s);
if (matcher.find()) {
size = Float.parseFloat(matcher.group(1));
if (size < 0) {
size = -1;
unit = Unit.PIXELS;
} else {
String symbol = matcher.group(3);
unit = Unit.getUnitFromSymbol(symbol);
}
} else {
throw new IllegalArgumentException("Invalid size argument: \"" + s
+ "\" (should match " + sizePattern.pattern() + ")");
}
return new Size(size, unit);
}
private static class Size implements Serializable {
float size;
Unit unit;
public Size(float size, Unit unit) {
this.size = size;
this.unit = unit;
}
public float getSize() {
return size;
}
public Unit getUnit() {
return unit;
}
}
public interface ComponentErrorEvent extends Terminal.ErrorEvent {
}
public interface ComponentErrorHandler extends Serializable {
/**
* Handle the component error
*
* @param event
* @return True if the error has been handled False, otherwise
*/
public boolean handleComponentError(ComponentErrorEvent event);
}
/**
* Gets the error handler for the component.
*
* The error handler is dispatched whenever there is an error processing the
* data coming from the client.
*
* @return
*/
public ComponentErrorHandler getErrorHandler() {
return errorHandler;
}
/**
* Sets the error handler for the component.
*
* The error handler is dispatched whenever there is an error processing the
* data coming from the client.
*
* If the error handler is not set, the application error handler is used to
* handle the exception.
*
* @param errorHandler
* AbstractField specific error handler
*/
public void setErrorHandler(ComponentErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}
/**
* Handle the component error event.
*
* @param error
* Error event to handle
* @return True if the error has been handled False, otherwise. If the error
* haven't been handled by this component, it will be handled in the
* application error handler.
*/
public boolean handleError(ComponentErrorEvent error) {
if (errorHandler != null) {
return errorHandler.handleComponentError(error);
}
return false;
}
/*
* Actions
*/
/**
* Gets the {@link ActionManager} used to manage the
* {@link ShortcutListener}s added to this {@link Field}.
*
* @return the ActionManager in use
*/
protected ActionManager getActionManager() {
if (actionManager == null) {
actionManager = new ActionManager();
setActionManagerViewer();
}
return actionManager;
}
/**
* Set a viewer for the action manager to be the parent sub window (if the
* component is in a window) or the root (otherwise). This is still a
* simplification of the real case as this should be handled by the parent
* VOverlay (on the client side) if the component is inside an VOverlay
* component.
*/
private void setActionManagerViewer() {
if (actionManager != null && getRoot() != null) {
// Attached and has action manager
Window w = findAncestor(Window.class);
if (w != null) {
actionManager.setViewer(w);
} else {
actionManager.setViewer(getRoot());
}
}
}
public void addShortcutListener(ShortcutListener shortcut) {
getActionManager().addAction(shortcut);
}
public void removeShortcutListener(ShortcutListener shortcut) {
if (actionManager != null) {
actionManager.removeAction(shortcut);
}
}
/**
* Registers an RPC interface implementation for this component.
*
* A component can listen to multiple RPC interfaces, and subclasses can
* register additional implementations.
*
* @since 7.0
*
* @param implementation
* RPC interface implementation
* @param rpcInterfaceType
* RPC interface class for which the implementation should be
* registered
*/
protected