pendingVariables,
boolean forceSync) {
final StringBuffer req = new StringBuffer();
while (!pendingVariables.isEmpty()) {
for (int i = 0; i < pendingVariables.size(); i++) {
if (i > 0) {
if (i % 2 == 0) {
req.append(VAR_RECORD_SEPARATOR);
} else {
req.append(VAR_FIELD_SEPARATOR);
}
}
req.append(pendingVariables.get(i));
}
pendingVariables.clear();
// Append all the busts to this synchronous request
if (forceSync && !pendingVariableBursts.isEmpty()) {
pendingVariables = pendingVariableBursts.firstElement();
pendingVariableBursts.remove(0);
req.append(VAR_BURST_SEPARATOR);
}
}
makeUidlRequest(req.toString(), false, forceSync, false);
}
public void updateVariable(String paintableId, String variableName,
Paintable newValue, boolean immediate) {
String pid = (newValue != null) ? getPid(newValue) : null;
addVariableToQueue(paintableId, variableName, pid, immediate, 'p');
}
public void updateVariable(String paintableId, String variableName,
String newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, newValue, immediate, 's');
}
public void updateVariable(String paintableId, String variableName,
int newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
'i');
}
public void updateVariable(String paintableId, String variableName,
long newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
'l');
}
public void updateVariable(String paintableId, String variableName,
float newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
'f');
}
public void updateVariable(String paintableId, String variableName,
double newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, "" + newValue, immediate,
'd');
}
public void updateVariable(String paintableId, String variableName,
boolean newValue, boolean immediate) {
addVariableToQueue(paintableId, variableName, newValue ? "true"
: "false", immediate, 'b');
}
public void updateVariable(String paintableId, String variableName,
Object[] values, boolean immediate) {
final StringBuffer buf = new StringBuffer();
for (int i = 0; i < values.length; i++) {
if (i > 0) {
buf.append(VAR_ARRAYITEM_SEPARATOR);
}
buf.append(values[i].toString());
}
addVariableToQueue(paintableId, variableName, buf.toString(),
immediate, 'a');
}
/**
* Update generic component features.
*
* Selecting correct implementation
*
*
* The implementation of a component depends on many properties, including
* styles, component features, etc. Sometimes the user changes those
* properties after the component has been created. Calling this method in
* the beginning of your updateFromUIDL -method automatically replaces your
* component with more appropriate if the requested implementation changes.
*
*
* Caption, icon, error messages and description
*
*
* Component can delegate management of caption, icon, error messages and
* description to parent layout. This is optional an should be decided by
* component author
*
*
* Component visibility and disabling
*
* This method will manage component visibility automatically and if
* component is an instanceof FocusWidget, also handle component disabling
* when needed.
*
* @param component
* Widget to be updated, expected to implement an instance of
* Paintable
* @param uidl
* UIDL to be painted
* @param manageCaption
* True if you want to delegate caption, icon, description and
* error message management to parent.
*
* @return Returns true iff no further painting is needed by caller
*/
public boolean updateComponent(Widget component, UIDL uidl,
boolean manageCaption) {
String pid = getPid(component.getElement());
if (pid == null) {
getConsole().error(
"Trying to update an unregistered component: "
+ Util.getSimpleName(component));
return true;
}
ComponentDetail componentDetail = idToPaintableDetail.get(pid);
if (componentDetail == null) {
getConsole().error(
"ComponentDetail not found for "
+ Util.getSimpleName(component) + " with PID "
+ pid + ". This should not happen.");
return true;
}
// If the server request that a cached instance should be used, do
// nothing
if (uidl.getBooleanAttribute("cached")) {
return true;
}
// Visibility
boolean visible = !uidl.getBooleanAttribute("invisible");
boolean wasVisible = component.isVisible();
component.setVisible(visible);
if (wasVisible != visible) {
// Changed invisibile <-> visible
if (wasVisible && manageCaption) {
// Must hide caption when component is hidden
final Container parent = Util.getLayout(component);
if (parent != null) {
parent.updateCaption((Paintable) component, uidl);
}
}
}
if (!visible) {
// component is invisible, delete old size to notify parent, if
// later make visible
componentDetail.setOffsetSize(null);
return true;
}
// Switch to correct implementation if needed
if (!widgetSet.isCorrectImplementation(component, uidl)) {
final Container parent = Util.getLayout(component);
if (parent != null) {
final Widget w = (Widget) widgetSet.createWidget(uidl);
parent.replaceChildComponent(component, w);
unregisterPaintable((Paintable) component);
registerPaintable(uidl.getId(), (Paintable) w);
((Paintable) w).updateFromUIDL(uidl, this);
return true;
}
}
boolean enabled = !uidl.getBooleanAttribute("disabled");
if (component instanceof FocusWidget) {
FocusWidget fw = (FocusWidget) component;
fw.setEnabled(enabled);
if (uidl.hasAttribute("tabindex")) {
fw.setTabIndex(uidl.getIntAttribute("tabindex"));
}
}
StringBuffer styleBuf = new StringBuffer();
final String primaryName = component.getStylePrimaryName();
styleBuf.append(primaryName);
// first disabling and read-only status
if (!enabled) {
styleBuf.append(" ");
styleBuf.append("i-disabled");
}
if (uidl.getBooleanAttribute("readonly")) {
styleBuf.append(" ");
styleBuf.append("i-readonly");
}
// add additional styles as css classes, prefixed with component default
// stylename
if (uidl.hasAttribute("style")) {
final String[] styles = uidl.getStringAttribute("style").split(" ");
for (int i = 0; i < styles.length; i++) {
styleBuf.append(" ");
styleBuf.append(primaryName);
styleBuf.append("-");
styleBuf.append(styles[i]);
styleBuf.append(" ");
styleBuf.append(styles[i]);
}
}
// add modified classname to Fields
if (uidl.hasAttribute("modified") && component instanceof Field) {
styleBuf.append(" ");
styleBuf.append(MODIFIED_CLASSNAME);
}
TooltipInfo tooltipInfo = componentDetail.getTooltipInfo();
if (uidl.hasAttribute("description")) {
tooltipInfo.setTitle(uidl.getStringAttribute("description"));
} else {
tooltipInfo.setTitle(null);
}
// add error classname to components w/ error
if (uidl.hasAttribute("error")) {
styleBuf.append(" ");
styleBuf.append(primaryName);
styleBuf.append(ERROR_CLASSNAME_EXT);
tooltipInfo.setErrorUidl(uidl.getErrors());
} else {
tooltipInfo.setErrorUidl(null);
}
// add required style to required components
if (uidl.hasAttribute("required")) {
styleBuf.append(" ");
styleBuf.append(primaryName);
styleBuf.append(REQUIRED_CLASSNAME_EXT);
}
// Styles + disabled & readonly
component.setStyleName(styleBuf.toString());
// Set captions
if (manageCaption) {
final Container parent = Util.getLayout(component);
if (parent != null) {
parent.updateCaption((Paintable) component, uidl);
}
}
if (configuration.useDebugIdInDOM() && uidl.getId().startsWith("PID_S")) {
DOM.setElementProperty(component.getElement(), "id", uidl.getId()
.substring(5));
}
/*
* updateComponentSize need to be after caption update so caption can be
* taken into account
*/
updateComponentSize(componentDetail, uidl);
return false;
}
private void updateComponentSize(ComponentDetail cd, UIDL uidl) {
String w = uidl.hasAttribute("width") ? uidl
.getStringAttribute("width") : "";
String h = uidl.hasAttribute("height") ? uidl
.getStringAttribute("height") : "";
float relativeWidth = Util.parseRelativeSize(w);
float relativeHeight = Util.parseRelativeSize(h);
// First update maps so they are correct in the setHeight/setWidth calls
if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
// One or both is relative
FloatSize relativeSize = new FloatSize(relativeWidth,
relativeHeight);
if (cd.getRelativeSize() == null && cd.getOffsetSize() != null) {
// The component has changed from absolute size to relative size
relativeSizeChanges.add(cd.getComponent());
}
cd.setRelativeSize(relativeSize);
} else if (relativeHeight < 0.0 && relativeWidth < 0.0) {
if (cd.getRelativeSize() != null) {
// The component has changed from relative size to absolute size
relativeSizeChanges.add(cd.getComponent());
}
cd.setRelativeSize(null);
}
Widget component = (Widget) cd.getComponent();
// Set absolute sizes
if (relativeHeight < 0.0) {
component.setHeight(h);
}
if (relativeWidth < 0.0) {
component.setWidth(w);
}
// Set relative sizes
if (relativeHeight >= 0.0 || relativeWidth >= 0.0) {
// One or both is relative
handleComponentRelativeSize(cd);
}
}
/**
* Traverses recursively child widgets until ContainerResizedListener child
* widget is found. They will delegate it further if needed.
*
* @param container
*/
private boolean runningLayout = false;
public void runDescendentsLayout(HasWidgets container) {
if (runningLayout) {
// getConsole().log(
// "Already running descendents layout. Not running again for "
// + Util.getSimpleName(container));
return;
}
runningLayout = true;
internalRunDescendentsLayout(container);
runningLayout = false;
}
/**
* This will cause re-layouting of all components. Mainly used for
* development. Published to JavaScript.
*/
public void forceLayout() {
Set set = new HashSet();
for (ComponentDetail cd : idToPaintableDetail.values()) {
set.add(cd.getComponent());
}
Util.componentSizeUpdated(set);
}
private void internalRunDescendentsLayout(HasWidgets container) {
// getConsole().log(
// "runDescendentsLayout(" + Util.getSimpleName(container) + ")");
final Iterator childWidgets = container.iterator();
while (childWidgets.hasNext()) {
final Widget child = childWidgets.next();
if (child instanceof Paintable) {
if (handleComponentRelativeSize(child)) {
/*
* Only need to propagate event if "child" has a relative
* size
*/
if (child instanceof ContainerResizedListener) {
((ContainerResizedListener) child).iLayout();
}
if (child instanceof HasWidgets) {
final HasWidgets childContainer = (HasWidgets) child;
internalRunDescendentsLayout(childContainer);
}
}
} else if (child instanceof HasWidgets) {
// propagate over non Paintable HasWidgets
internalRunDescendentsLayout((HasWidgets) child);
}
}
}
/**
* Converts relative sizes into pixel sizes.
*
* @param child
* @return true if the child has a relative size
*/
private boolean handleComponentRelativeSize(ComponentDetail cd) {
if (cd == null) {
return false;
}
boolean debugSizes = false;
FloatSize relativeSize = cd.getRelativeSize();
if (relativeSize == null) {
return false;
}
Widget widget = (Widget) cd.getComponent();
boolean horizontalScrollBar = false;
boolean verticalScrollBar = false;
Container parent = Util.getLayout(widget);
RenderSpace renderSpace;
// Parent-less components (like sub-windows) are relative to browser
// window.
if (parent == null) {
renderSpace = new RenderSpace(Window.getClientWidth(), Window
.getClientHeight());
} else {
renderSpace = parent.getAllocatedSpace(widget);
}
if (relativeSize.getHeight() >= 0) {
if (renderSpace != null) {
if (renderSpace.getScrollbarSize() > 0) {
if (relativeSize.getWidth() > 100) {
horizontalScrollBar = true;
} else if (relativeSize.getWidth() < 0
&& renderSpace.getWidth() > 0) {
int offsetWidth = widget.getOffsetWidth();
int width = renderSpace.getWidth();
if (offsetWidth > width) {
horizontalScrollBar = true;
}
}
}
int height = renderSpace.getHeight();
if (horizontalScrollBar) {
height -= renderSpace.getScrollbarSize();
}
if (validatingLayouts && height <= 0) {
zeroHeightComponents.add(cd.getComponent());
}
height = (int) (height * relativeSize.getHeight() / 100.0);
if (height < 0) {
height = 0;
}
if (debugSizes) {
getConsole()
.log(
"Widget "
+ Util.getSimpleName(widget)
+ "/"
+ getPid(widget.getElement())
+ " relative height "
+ relativeSize.getHeight()
+ "% of "
+ renderSpace.getHeight()
+ "px (reported by "
+ Util.getSimpleName(parent)
+ "/"
+ (parent == null ? "?" : parent
.hashCode()) + ") : "
+ height + "px");
}
widget.setHeight(height + "px");
} else {
widget.setHeight(relativeSize.getHeight() + "%");
ApplicationConnection.getConsole().error(
Util.getLayout(widget).getClass().getName()
+ " did not produce allocatedSpace for "
+ widget.getClass().getName());
}
}
if (relativeSize.getWidth() >= 0) {
if (renderSpace != null) {
int width = renderSpace.getWidth();
if (renderSpace.getScrollbarSize() > 0) {
if (relativeSize.getHeight() > 100) {
verticalScrollBar = true;
} else if (relativeSize.getHeight() < 0
&& renderSpace.getHeight() > 0
&& widget.getOffsetHeight() > renderSpace
.getHeight()) {
verticalScrollBar = true;
}
}
if (verticalScrollBar) {
width -= renderSpace.getScrollbarSize();
}
if (validatingLayouts && width <= 0) {
zeroWidthComponents.add(cd.getComponent());
}
width = (int) (width * relativeSize.getWidth() / 100.0);
if (width < 0) {
width = 0;
}
if (debugSizes) {
getConsole().log(
"Widget " + Util.getSimpleName(widget) + "/"
+ getPid(widget.getElement())
+ " relative width "
+ relativeSize.getWidth() + "% of "
+ renderSpace.getWidth()
+ "px (reported by "
+ Util.getSimpleName(parent) + "/"
+ (parent == null ? "?" : getPid(parent))
+ ") : " + width + "px");
}
widget.setWidth(width + "px");
} else {
widget.setWidth(relativeSize.getWidth() + "%");
ApplicationConnection.getConsole().error(
Util.getLayout(widget).getClass().getName()
+ " did not produce allocatedSpace for "
+ widget.getClass().getName());
}
}
return true;
}
/**
* Converts relative sizes into pixel sizes.
*
* @param child
* @return true if the child has a relative size
*/
public boolean handleComponentRelativeSize(Widget child) {
return handleComponentRelativeSize(idToPaintableDetail.get(getPid(child
.getElement())));
}
public FloatSize getRelativeSize(Widget widget) {
return idToPaintableDetail.get(getPid(widget.getElement()))
.getRelativeSize();
}
/**
* Get either existing or new Paintable for given UIDL.
*
* If corresponding Paintable has been previously painted, return it.
* Otherwise create and register a new Paintable from UIDL. Caller must
* update the returned Paintable from UIDL after it has been connected to
* parent.
*
* @param uidl
* UIDL to create Paintable from.
* @return Either existing or new Paintable corresponding to UIDL.
*/
public Paintable getPaintable(UIDL uidl) {
final String id = uidl.getId();
Paintable w = getPaintable(id);
if (w != null) {
return w;
} else {
w = widgetSet.createWidget(uidl);
registerPaintable(id, w);
return w;
}
}
/**
* Returns a Paintable element by its root element
*
* @param element
* Root element of the paintable
*/
public Paintable getPaintable(Element element) {
return getPaintable(getPid(element));
}
public String getResource(String name) {
return resourcesMap.get(name);
}
/**
* Singleton method to get instance of app's context menu.
*
* @return VContextMenu object
*/
public VContextMenu getContextMenu() {
if (contextMenu == null) {
contextMenu = new VContextMenu();
DOM.setElementProperty(contextMenu.getElement(), "id",
"PID_TOOLKIT_CM");
}
return contextMenu;
}
/**
* Translates custom protocols in UIRL URI's to be recognizable by browser.
* All uri's from UIDL should be routed via this method before giving them
* to browser due URI's in UIDL may contain custom protocols like theme://.
*
* @param toolkitUri
* toolkit URI from uidl
* @return translated URI ready for browser
*/
public String translateToolkitUri(String toolkitUri) {
if (toolkitUri == null) {
return null;
}
if (toolkitUri.startsWith("theme://")) {
final String themeUri = configuration.getThemeUri();
if (themeUri == null) {
console
.error("Theme not set: ThemeResource will not be found. ("
+ toolkitUri + ")");
}
toolkitUri = themeUri + toolkitUri.substring(7);
}
return toolkitUri;
}
public String getThemeUri() {
return configuration.getThemeUri();
}
/**
* Listens for Notification hide event, and redirects. Used for system
* messages, such as session expired.
*
*/
private class NotificationRedirect implements VNotification.EventListener {
String url;
NotificationRedirect(String url) {
this.url = url;
}
public void notificationHidden(HideEvent event) {
redirect(url);
}
}
/* Extended title handling */
/**
* Data showed in tooltips are stored centrilized as it may be needed in
* varios place: caption, layouts, and in owner components themselves.
*
* Updating TooltipInfo is done in updateComponent method.
*
*/
public TooltipInfo getTitleInfo(Paintable titleOwner) {
if (null == titleOwner) {
return null;
}
ComponentDetail pd = idToPaintableDetail.get(getPid(titleOwner));
if (null != pd) {
return pd.getTooltipInfo();
} else {
return null;
}
}
private final VTooltip tooltip = new VTooltip(this);
/**
* Component may want to delegate Tooltip handling to client. Layouts add
* Tooltip (description, errors) to caption, but some components may want
* them to appear one other elements too.
*
* Events wanted by this handler are same as in Tooltip.TOOLTIP_EVENTS
*
* @param event
* @param owner
*/
public void handleTooltipEvent(Event event, Paintable owner) {
tooltip.handleTooltipEvent(event, owner);
}
/**
* Adds PNG-fix conditionally (only for IE6) to the specified IMG -element.
*
* @param el
* the IMG element to fix
*/
public void addPngFix(Element el) {
BrowserInfo b = BrowserInfo.get();
if (b.isIE6()) {
Util.addPngFix(el, getThemeUri()
+ "/../default/common/img/blank.gif");
}
}
/*
* Helper to run layout functions triggered by child components with a
* decent interval.
*/
private final Timer layoutTimer = new Timer() {
private boolean isPending = false;
@Override
public void schedule(int delayMillis) {
if (!isPending) {
super.schedule(delayMillis);
isPending = true;
}
}
@Override
public void run() {
getConsole().log(
"Running re-layout of " + view.getClass().getName());
runDescendentsLayout(view);
isPending = false;
}
};
/**
* Components can call this function to run all layout functions. This is
* usually done, when component knows that its size has changed.
*/
public void requestLayoutPhase() {
layoutTimer.schedule(500);
}
private String windowName = null;
/**
* Reset the name of the current browser-window. This should reflect the
* window-name used in the server, but might be different from the
* window-object target-name on client.
*
* @param stringAttribute
* New name for the window.
*/
public void setWindowName(String newName) {
windowName = newName;
}
public void captionSizeUpdated(Paintable component) {
componentCaptionSizeChanges.add(component);
}
public void analyzeLayouts() {
makeUidlRequest("", true, false, true);
}
public VView getView() {
return view;
}
}