diff options
22 files changed, 2469 insertions, 17 deletions
diff --git a/GWT development mode for vaadin.launch b/GWT development mode for vaadin.launch new file mode 100644 index 0000000000..5195348bf5 --- /dev/null +++ b/GWT development mode for vaadin.launch @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication"> +<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS"> +<listEntry value="/vaadin"/> +</listAttribute> +<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES"> +<listEntry value="4"/> +</listAttribute> +<listAttribute key="org.eclipse.jdt.launching.CLASSPATH"> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="/Users/jouni/.ivy2/cache/com.google.gwt/gwt-dev/jars/gwt-dev-2.4.0.jar" path="3" type="2"/> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry externalArchive="/Users/jouni/.ivy2/cache/com.google.gwt/gwt-user/jars/gwt-user-2.4.0.jar" path="3" type="2"/> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="vaadin"/> </runtimeClasspathEntry> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/vaadin/src" path="3" type="2"/> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/vaadin/tests/testbench" path="3" type="2"/> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/vaadin/tests/client-side" path="3" type="2"/> "/> +<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/vaadin/tests/server-side" path="3" type="2"/> "/> +</listAttribute> +<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/> +<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/> +<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-noserver -war WebContent/VAADIN/widgetsets com.vaadin.terminal.gwt.DefaultWidgetSet -startupUrl http://localhost:8080/vaadin -bindAddress 0.0.0.0"/> +<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="vaadin"/> +<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx512M -XX:MaxPermSize=256M"/> +<stringAttribute key="org.eclipse.jdt.launching.WORKING_DIRECTORY" value="/Users/jouni/Documents/Work/Dev/Vaadin/vaadin"/> +</launchConfiguration> diff --git a/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css b/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css new file mode 100644 index 0000000000..bbfad98582 --- /dev/null +++ b/WebContent/VAADIN/themes/base/boxlayout/boxlayout.css @@ -0,0 +1,169 @@ +/* +TODO +- separate styles to proper places +- decide a good class name structure for core layouts (e.g. 'v-layout', 'v-vertical', 'v-grid' etc.) +- use !important in carefully selected places to prevent accidental layout breakage by custom theming (e.g. alignments should be forced) + +*/ + +.v-boxlayout.v-margin-top {padding-top: 12px;} +.v-boxlayout.v-margin-right {padding-right: 12px;} +.v-boxlayout.v-margin-bottom {padding-bottom: 12px;} +.v-boxlayout.v-margin-left {padding-left: 12px;} + +.v-spacing { + width: 6px; + height: 6px; +} + +.v-boxlayout { + display: inline-block; +} + +.v-boxlayout.v-horizontal { + white-space: nowrap !important; +} + +.v-boxlayout > .v-expand { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + height: 100%; +} + +.v-slot, +.v-spacing { + display: inline-block; + white-space: normal; + vertical-align: top; +} + +/* Clear any floats inside the slot, to prevent unwanted collapsing */ +.v-slot:after { + content: ""; + display: inline-block; + clear: both; + width: 0; + height: 0; + overflow: hidden; +} + +.v-vertical > .v-slot, +.v-vertical > .v-expand > .v-slot { + display: block; + clear: both; +} + +.v-horizontal > .v-slot, +.v-horizontal > .v-expand > .v-slot { + height: 100%; +} + +.v-vertical > .v-spacing, +.v-vertical > .v-expand > .v-spacing { + width: 0; + display: block; + clear: both; +} + +.v-horizontal > .v-spacing, +.v-horizontal > .v-expand > .v-spacing { + height: 0; +} + +.v-align-middle:before, +.v-align-bottom:before, +.v-expand > .v-align-middle:before, +.v-expand > .v-align-bottom:before { + content: ""; + display: inline-block; + height: 100%; + vertical-align: middle; + width: 0; +} + +.v-align-middle, +.v-align-bottom { + white-space: nowrap; +} + +.v-align-middle > .v-connector, +.v-align-bottom > .v-connector { + display: inline-block; + /* TODO this is a bit tricky, since it will override component defaults in some cases */ + white-space: normal; +} + +.v-align-middle > .v-connector { + vertical-align: middle; +} + +.v-align-bottom > .v-connector { + vertical-align: bottom; +} + +.v-align-center { + text-align: center; +} + +.v-align-center > .v-connector { + margin-left: auto; + margin-right: auto; +} + +.v-align-right { + text-align: right; +} + +.v-align-right > .v-connector { + margin-left: auto; +} + +.v-has-caption { + display: inline-block; +} + +.v-caption { + display: inline-block; /* Force default width to zero */ + overflow: visible; + vertical-align: middle; +} + +.v-caption-on-left, +.v-caption-on-right { + white-space: nowrap; +} + +.v-caption-on-top > .v-caption, +.v-caption-on-bottom > .v-caption { + display: block; +} + +.v-caption-on-left > .v-caption { + padding-right: .5em; +} + +.v-caption-on-right > .v-caption { + padding-left: .5em; +} + +.v-caption-on-left > .v-connector, +.v-caption-on-right > .v-connector { + display: inline-block; + vertical-align: middle; + /* TODO this is a bit tricky, since it will override component defaults in some cases */ + white-space: normal; +} + +.v-has-caption.v-has-width > .v-connector { + width: 100% !important; +} + +.v-has-caption.v-has-height > .v-connector { + height: 100% !important; +} + +.v-errorindicator { + vertical-align: middle; +}
\ No newline at end of file diff --git a/WebContent/VAADIN/themes/base/button/button.css b/WebContent/VAADIN/themes/base/button/button.css index 2e14d59a90..8424a3e59f 100644 --- a/WebContent/VAADIN/themes/base/button/button.css +++ b/WebContent/VAADIN/themes/base/button/button.css @@ -4,7 +4,7 @@ .v-button { display: inline-block; zoom: 1; - text-align: center; + text-align: center !important; text-decoration: none; border: 2px outset #ddd; background: #eee; @@ -84,7 +84,7 @@ * NativeButton styles (html button element) * -------------------------------------- */ .v-nativebutton { - text-align: center; + text-align: center !important; cursor: pointer; white-space: nowrap; margin: 0; diff --git a/WebContent/VAADIN/themes/base/paintable/paintable.css b/WebContent/VAADIN/themes/base/paintable/paintable.css index 36a3fe3455..9d700fd4ee 100644 --- a/WebContent/VAADIN/themes/base/paintable/paintable.css +++ b/WebContent/VAADIN/themes/base/paintable/paintable.css @@ -1,4 +1,7 @@ .v-connector { - box-sizing: border-box; + -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -}
\ No newline at end of file + box-sizing: border-box; + text-align: left; + display: inline-block; +} diff --git a/WebContent/VAADIN/themes/reindeer/layouts/layouts.css b/WebContent/VAADIN/themes/reindeer/layouts/layouts.css index 528d4b9174..650247bade 100644 --- a/WebContent/VAADIN/themes/reindeer/layouts/layouts.css +++ b/WebContent/VAADIN/themes/reindeer/layouts/layouts.css @@ -1,21 +1,25 @@ .v-orderedlayout-margin-top, .v-horizontallayout-margin-top, -.v-verticallayout-margin-top { +.v-verticallayout-margin-top, +.v-boxlayout.v-margin-top { padding-top: 18px; } .v-orderedlayout-margin-right, .v-horizontallayout-margin-right, -.v-verticallayout-margin-right { +.v-verticallayout-margin-right, +.v-boxlayout.v-margin-right { padding-right: 18px; } .v-orderedlayout-margin-bottom, .v-horizontallayout-margin-bottom, -.v-verticallayout-margin-bottom { +.v-verticallayout-margin-bottom, +.v-boxlayout.v-margin-bottom { padding-bottom: 18px; } .v-orderedlayout-margin-left, .v-horizontallayout-margin-left, -.v-verticallayout-margin-left { +.v-verticallayout-margin-left, +.v-boxlayout.v-margin-left { padding-left: 18px; } .v-orderedlayout-spacing-on, @@ -24,6 +28,10 @@ padding-top: 7px; padding-left: 6px; } +.v-spacing { + height: 7px; + width: 6px; +} /* Different for historical reasons: previously was inherited directly from Base theme */ /* TODO unify these values in version 7 */ .v-gridlayout-margin-top { diff --git a/WebContent/VAADIN/themes/runo/orderedlayout/orderedlayout.css b/WebContent/VAADIN/themes/runo/orderedlayout/orderedlayout.css index 306292d843..f70e285729 100644 --- a/WebContent/VAADIN/themes/runo/orderedlayout/orderedlayout.css +++ b/WebContent/VAADIN/themes/runo/orderedlayout/orderedlayout.css @@ -1,7 +1,8 @@ .v-orderedlayout-margin-top, .v-horizontallayout-margin-top, .v-verticallayout-margin-top, -.v-csslayout-margin-top { +.v-csslayout-margin-top, +.v-boxlayout.v-margin-top { padding-top: 15px; } .v-orderedlayout-margin-right, @@ -13,7 +14,8 @@ .v-orderedlayout-margin-bottom, .v-horizontallayout-margin-bottom, .v-verticallayout-margin-bottom, -.v-csslayout-margin-bottom { +.v-csslayout-margin-bottom, +.v-boxlayout.v-margin-bottom { padding-bottom: 15px; } .v-orderedlayout-margin-left, @@ -28,6 +30,10 @@ padding-top: 8px; padding-left: 8px; } +.v-spacing { + width: 8px; + height: 8px; +} .v-verticallayout-darker, .v-horizontallayout-darker, .v-gridlayout-darker, diff --git a/WebContent/VAADIN/themes/tests-components/styles.css b/WebContent/VAADIN/themes/tests-components/styles.css index c38f32f132..45a96b6d16 100644 --- a/WebContent/VAADIN/themes/tests-components/styles.css +++ b/WebContent/VAADIN/themes/tests-components/styles.css @@ -36,4 +36,21 @@ .v-table-row-tables-test-cell-style-red-row, .v-table-cell-content-tables-test-cell-style-red-row { background: #f00; +} + +.v-boxlayout.test { + border: 1px solid #ddd; +} + +.v-boxlayout.test .target { + outline: 2px dashed blue; +} + +.fieldset { + padding: .5em 1em; + -webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 1px 2px rgba(0,0,0,.2); + box-shadow: inset 0 1px 2px rgba(0,0,0,.2); + border-radius: .5em; + background: rgba(0,0,0,.02); }
\ No newline at end of file diff --git a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml index f65b4c51e7..518f91df7d 100644 --- a/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml +++ b/src/com/vaadin/terminal/gwt/DefaultWidgetSet.gwt.xml @@ -16,6 +16,14 @@ name="com.vaadin.terminal.gwt.DefaultWidgetSetBrowserSpecificOverrides" /> <source path="client" /> + + <!-- TODO only for development --> + <replace-with class="com.vaadin.terminal.gwt.client.ui.VerticalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.orderedlayout.VerticalLayoutConnector" /> + </replace-with> + <replace-with class="com.vaadin.terminal.gwt.client.ui.HorizontalBoxLayoutConnector"> + <when-type-is class="com.vaadin.terminal.gwt.client.ui.orderedlayout.HorizontalLayoutConnector" /> + </replace-with> <!-- Use own Scheduler implementation to be able to track if commands are running --> diff --git a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java index d847d49e6f..a4df6c6cc4 100644 --- a/src/com/vaadin/terminal/gwt/client/ComponentLocator.java +++ b/src/com/vaadin/terminal/gwt/client/ComponentLocator.java @@ -13,6 +13,7 @@ import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ui.SubPartAware; +import com.vaadin.terminal.gwt.client.ui.VBoxLayout; import com.vaadin.terminal.gwt.client.ui.gridlayout.VGridLayout; import com.vaadin.terminal.gwt.client.ui.orderedlayout.VMeasuringOrderedLayout; import com.vaadin.terminal.gwt.client.ui.root.VRoot; @@ -483,6 +484,14 @@ public class ComponentLocator { // is always 0 which indicates the widget in the active tab widgetPosition = 0; } + if ("VVerticalLayout".equals(widgetClassName) + || "VHorizontalLayout".equals(widgetClassName)) { + widgetClassName = "VBoxLayout"; + } + if (w instanceof VBoxLayout + && "ChildComponentContainer".equals(widgetClassName)) { + widgetClassName = "VBoxLayout$Slot"; + } /* * The new grid and ordered layotus do not contain diff --git a/src/com/vaadin/terminal/gwt/client/LayoutManager.java b/src/com/vaadin/terminal/gwt/client/LayoutManager.java index b182429902..a40a2e841f 100644 --- a/src/com/vaadin/terminal/gwt/client/LayoutManager.java +++ b/src/com/vaadin/terminal/gwt/client/LayoutManager.java @@ -18,6 +18,7 @@ import com.google.gwt.user.client.Timer; import com.vaadin.terminal.gwt.client.MeasuredSize.MeasureResult; import com.vaadin.terminal.gwt.client.ui.ManagedLayout; import com.vaadin.terminal.gwt.client.ui.PostLayoutListener; +import com.vaadin.terminal.gwt.client.ui.PreLayoutListener; import com.vaadin.terminal.gwt.client.ui.SimpleManagedLayout; import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent; import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener; @@ -130,7 +131,7 @@ public class LayoutManager { return; } measuredSize.removeDependent(owner.getConnectorId()); - stopMeasuringIfUnecessary(element); + stopMeasuringIfUnnecessary(element); } public boolean isLayoutRunning() { @@ -152,7 +153,7 @@ public class LayoutManager { } } - private void layoutLater() { + public void layoutLater() { if (!layoutPending) { layoutPending = true; layoutTimer.schedule(100); @@ -176,10 +177,21 @@ public class LayoutManager { private void doLayout() { VConsole.log("Starting layout phase"); + Duration totalDuration = new Duration(); + + int preLayoutStart = totalDuration.elapsedMillis(); + for (ComponentConnector connector : connection.getConnectorMap() + .getComponentConnectors()) { + if (connector instanceof PreLayoutListener) { + ((PreLayoutListener) connector).preLayout(); + } + } + VConsole.log("Invoke pre layout listeners in " + + (totalDuration.elapsedMillis() - preLayoutStart) + " ms"); + Map<ManagedLayout, Integer> layoutCounts = new HashMap<ManagedLayout, Integer>(); int passes = 0; - Duration totalDuration = new Duration(); for (ManagedLayout layout : needsHorizontalLayout) { currentDependencyTree.setNeedsHorizontalLayout(layout, true); @@ -637,6 +649,14 @@ public class LayoutManager { return getMeasuredSize(element, nullSize).getMarginLeft(); } + public int getMarginWidth(Element element) { + return getMeasuredSize(element, nullSize).getMarginWidth(); + } + + public int getMarginHeight(Element element) { + return getMeasuredSize(element, nullSize).getMarginHeight(); + } + public void reportOuterHeight(ComponentConnector component, int outerHeight) { MeasuredSize measuredSize = getMeasuredSize(component); if (isLayoutRunning()) { @@ -713,12 +733,12 @@ public class LayoutManager { listeners.remove(listener); if (listeners.isEmpty()) { elementResizeListeners.remove(element); - stopMeasuringIfUnecessary(element); + stopMeasuringIfUnnecessary(element); } } } - private void stopMeasuringIfUnecessary(Element element) { + private void stopMeasuringIfUnnecessary(Element element) { if (!needsMeasure(element)) { measuredNonConnectorElements.remove(element); setMeasuredSize(element, null); @@ -737,4 +757,5 @@ public class LayoutManager { public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } + } diff --git a/src/com/vaadin/terminal/gwt/client/ServerConnector.java b/src/com/vaadin/terminal/gwt/client/ServerConnector.java index 559238b512..f179b29054 100644 --- a/src/com/vaadin/terminal/gwt/client/ServerConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ServerConnector.java @@ -91,6 +91,15 @@ public interface ServerConnector extends Connector { public HandlerRegistration addStateChangeHandler(StateChangeHandler handler); /** + * Removes the specified StateChangeHandler from this connector. The handler + * will no longer be notified of the state is updated by the server. + * + * @param handler + * The handler that should be removed. + */ + public void removeStateChangeHandler(StateChangeHandler handler); + + /** * Sends the given event to all registered handlers. * * @param event diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java new file mode 100644 index 0000000000..c58241c608 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractBoxLayoutConnector.java @@ -0,0 +1,593 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.ComponentConnector; +import com.vaadin.terminal.gwt.client.ConnectorHierarchyChangeEvent; +import com.vaadin.terminal.gwt.client.Paintable; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.Util; +import com.vaadin.terminal.gwt.client.ValueMap; +import com.vaadin.terminal.gwt.client.communication.RpcProxy; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent; +import com.vaadin.terminal.gwt.client.communication.StateChangeEvent.StateChangeHandler; +import com.vaadin.terminal.gwt.client.ui.VBoxLayout.CaptionPosition; +import com.vaadin.terminal.gwt.client.ui.VBoxLayout.Slot; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeEvent; +import com.vaadin.terminal.gwt.client.ui.layout.ElementResizeListener; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutServerRPC; +import com.vaadin.terminal.gwt.client.ui.orderedlayout.AbstractOrderedLayoutState; + +public abstract class AbstractBoxLayoutConnector extends + AbstractLayoutConnector implements Paintable, PreLayoutListener, + PostLayoutListener { + + AbstractOrderedLayoutServerRPC rpc; + + private LayoutClickEventHandler clickEventHandler = new LayoutClickEventHandler( + this) { + + @Override + protected ComponentConnector getChildComponent(Element element) { + return Util.getConnectorForElement(getConnection(), getWidget(), + element); + } + + @Override + protected LayoutClickRPC getLayoutClickRPC() { + return rpc; + }; + + }; + + @Override + public void init() { + rpc = RpcProxy.create(AbstractOrderedLayoutServerRPC.class, this); + getWidget().setLayoutManager(getLayoutManager()); + } + + @Override + public AbstractOrderedLayoutState getState() { + return (AbstractOrderedLayoutState) super.getState(); + } + + @Override + protected Widget createWidget() { + return GWT.create(VBoxLayout.class); + } + + @Override + public VBoxLayout getWidget() { + return (VBoxLayout) super.getWidget(); + } + + /** + * For bookkeeping. Used to determine if extra calculations are needed for + * horizontal layout. + */ + private HashSet<ComponentConnector> hasVerticalAlignment = new HashSet<ComponentConnector>(); + + /** + * For bookkeeping. Used to determine if extra calculations are needed for + * horizontal layout. + */ + private HashSet<ComponentConnector> hasRelativeHeight = new HashSet<ComponentConnector>(); + + /** + * For bookkeeping. Used to determine if extra calculations are needed for + * horizontal layout. + */ + private HashSet<ComponentConnector> hasExpandRatio = new HashSet<ComponentConnector>(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + private HashSet<Element> needsMeasure = new HashSet<Element>(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + private HashMap<Element, Integer> childElementHeight = new HashMap<Element, Integer>(); + + /** + * For bookkeeping. Used in extra calculations for horizontal layout. + */ + private HashMap<Element, Integer> childCaptionElementHeight = new HashMap<Element, Integer>(); + + // For debugging + private static int resizeCount = 0; + + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + if (!isRealUpdate(uidl)) { + return; + } + + clickEventHandler.handleEventHandlerRegistration(); + + VBoxLayout layout = getWidget(); + + ValueMap expandRatios = uidl.getMapAttribute("expandRatios"); + ValueMap alignments = uidl.getMapAttribute("alignments"); + + for (ComponentConnector child : getChildren()) { + Slot slot = layout.getSlot(child.getWidget()); + String pid = child.getConnectorId(); + + AlignmentInfo alignment; + if (alignments.containsKey(pid)) { + alignment = new AlignmentInfo(alignments.getInt(pid)); + if (alignment.isVerticalCenter() || alignment.isBottom()) { + hasVerticalAlignment.add(child); + } else { + hasVerticalAlignment.remove(child); + } + } else { + alignment = AlignmentInfo.TOP_LEFT; + hasVerticalAlignment.remove(child); + } + slot.setAlignment(alignment); + + double expandRatio; + // TODO discuss the layout specs, is this what we want: distribute + // extra space equally if no expand ratios are specified inside a + // layout with specified size + if (expandRatios.getKeySet().size() == 0 + && ((!getWidget().vertical && !isUndefinedHeight()) || !isUndefinedWidth())) { + expandRatio = 1; + hasExpandRatio.add(child); + } else if (expandRatios.containsKey(pid) + && expandRatios.getRawNumber(pid) > 0) { + expandRatio = expandRatios.getRawNumber(pid); + hasExpandRatio.add(child); + } else { + expandRatio = -1; + hasExpandRatio.remove(child); + getLayoutManager().addElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + if (slot.hasCaption()) { + getLayoutManager() + .addElementResizeListener(slot.getCaptionElement(), + slotCaptionResizeListener); + } + } + slot.setExpandRatio(expandRatio); + + if (slot.getSpacingElement() != null) { + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + } + + if (needsExpand()) { + updateExpand(); + } else { + getWidget().clearExpand(); + } + + } + + public void updateCaption(ComponentConnector child) { + Slot slot = getWidget().getSlot(child.getWidget()); + + String caption = child.getState().getCaption(); + String iconUrl = child.getState().getIcon() != null ? child.getState() + .getIcon().getURL() : null; + List<String> styles = child.getState().getStyles(); + String error = child.getState().getErrorMessage(); + boolean required = false; + if (child instanceof AbstractFieldConnector) { + required = ((AbstractFieldConnector) child).isRequired(); + } + boolean enabled = child.getState().isEnabled(); + // TODO Description is handled from somewhere else? + + slot.setCaption(caption, iconUrl, styles, error, required, enabled); + + slot.setRelativeWidth(child.isRelativeWidth()); + slot.setRelativeHeight(child.isRelativeHeight()); + + if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); + getLayoutManager().addElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } + } else { + getLayoutManager().removeElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (!slot.hasCaption()) { + childCaptionElementHeight.remove(child.getWidget().getElement()); + } + } + + @Override + public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { + super.onConnectorHierarchyChange(event); + + List<ComponentConnector> previousChildren = event.getOldChildren(); + int currentIndex = 0; + VBoxLayout layout = getWidget(); + + for (ComponentConnector child : getChildren()) { + Widget childWidget = child.getWidget(); + Slot slot = layout.getSlot(childWidget); + if (slot.getParent() != layout) { + child.addStateChangeHandler(childStateChangeHandler); + } + layout.addOrMoveSlot(slot, currentIndex++); + } + + for (ComponentConnector child : previousChildren) { + if (child.getParent() != this) { + Slot slot = layout.getSlot(child.getWidget()); + hasVerticalAlignment.remove(child); + hasRelativeHeight.remove(child); + hasExpandRatio.remove(child); + needsMeasure.remove(child.getWidget().getElement()); + childElementHeight.remove(child.getWidget().getElement()); + childCaptionElementHeight + .remove(child.getWidget().getElement()); + getLayoutManager().removeElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + if (slot.hasCaption()) { + getLayoutManager() + .removeElementResizeListener( + slot.getCaptionElement(), + slotCaptionResizeListener); + } + if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + child.removeStateChangeHandler(childStateChangeHandler); + layout.removeSlot(child.getWidget()); + } + } + + if (needsExpand()) { + getWidget().updateExpand(); + } else { + getWidget().clearExpand(); + } + + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().setMargin(new VMarginInfo(getState().getMarginsBitmask())); + getWidget().setSpacing(getState().isSpacing()); + + if (needsFixedHeight()) { + setLayoutHeightListener(true); + } else { + setLayoutHeightListener(false); + } + + } + + StateChangeHandler childStateChangeHandler = new StateChangeHandler() { + public void onStateChanged(StateChangeEvent stateChangeEvent) { + ComponentConnector child = (ComponentConnector) stateChangeEvent + .getConnector(); + + // We need to update the slot size if the component size is changed + // to relative + Slot slot = getWidget().getSlot(child.getWidget()); + slot.setRelativeWidth(child.isRelativeWidth()); + slot.setRelativeHeight(child.isRelativeHeight()); + + // For relative sized widgets, we need to set the caption offset + if (slot.hasCaption()) { + CaptionPosition pos = slot.getCaptionPosition(); + if (child.isRelativeHeight() + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } else if (child.isRelativeWidth() + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(slot.getCaptionElement()); + } + } + + // TODO 'needsExpand' might return false during the first render, + // since updateFromUidl is called last + + // If the slot has caption, we need to listen for it's size changes + // in order to update the padding/margin offset for relative sized + // components + if ((child.isRelativeHeight() || needsFixedHeight()) + && slot.hasCaption()) { + getLayoutManager().addElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + } else if (!needsExpand()) { + // TODO recheck if removing the listener here breaks anything. + // Should be cleaned up. + // getLayoutManager().removeElementResizeListener( + // slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (slot.getSpacingElement() != null && needsExpand()) { + // Spacing is on + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } else if (slot.getSpacingElement() != null) { + getLayoutManager().addElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + if (child.isRelativeHeight()) { + hasRelativeHeight.add(child); + needsMeasure.remove(child.getWidget().getElement()); + } else { + hasRelativeHeight.remove(child); + needsMeasure.add(child.getWidget().getElement()); + } + + if (needsFixedHeight()) { + getLayoutManager().addElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + } else if (!needsExpand()) { + getLayoutManager().removeElementResizeListener( + child.getWidget().getElement(), + childComponentResizeListener); + } + + if (needsFixedHeight()) { + setLayoutHeightListener(true); + } else { + setLayoutHeightListener(false); + } + + } + }; + + private boolean needsFixedHeight() { + if (!getWidget().vertical + && isUndefinedHeight() + && (hasRelativeHeight.size() > 0 || hasVerticalAlignment.size() > 0)) { + return true; + } + return false; + } + + private boolean needsExpand() { + boolean canApplyExpand = (getWidget().vertical && !isUndefinedHeight()) + || !isUndefinedWidth(); + return hasExpandRatio.size() > 0 && canApplyExpand; + } + + public void preLayout() { + resizeCount = 0; + if (needsFixedHeight()) { + getWidget().clearHeight(); + getLayoutManager().setNeedsMeasure(this); + } + } + + public void postLayout() { + if (needsFixedHeight()) { + // Re-measure all elements that are available + for (Element el : needsMeasure) { + childElementHeight.put(el, getLayoutManager() + .getOuterHeight(el)); + + // Element captionElement = el.getParentElement() + // .getFirstChildElement().cast(); + // if (captionElement.getClassName().contains("v-caption")) { + // childCaptionElementHeight.put(el, getLayoutManager() + // .getOuterHeight(captionElement)); + // } + } + + String h = getWidget().getElement().getStyle().getHeight(); + if (h == null || h.equals("")) { + int height = getLayoutManager().getOuterHeight( + getWidget().getElement()) + - getLayoutManager().getMarginHeight( + getWidget().getElement()); + // int height = getMaxHeight(); + getWidget().getElement().getStyle().setHeight(height, Unit.PX); + } + } + // System.err.println("Element resize listeners fired for " + + // resizeCount + // + " times"); + } + + private ElementResizeListener layoutResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + updateLayoutHeight(); + if (needsExpand() && (isUndefinedHeight() || isUndefinedWidth())) { + updateExpand(); + } + } + }; + + private ElementResizeListener slotCaptionResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + + Element captionElement = (Element) e.getElement().cast(); + + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) captionElement.getParentElement().cast()); + + Element widgetElement = captionElement.getParentElement() + .getLastChild().cast(); + if (pos == CaptionPosition.BOTTOM || pos == CaptionPosition.RIGHT) { + widgetElement = captionElement.getParentElement() + .getFirstChildElement().cast(); + } + + if (captionElement == widgetElement) { + // Caption element already detached + getLayoutManager().removeElementResizeListener(captionElement, + slotCaptionResizeListener); + return; + } + + String widgetWidth = widgetElement.getStyle().getWidth(); + String widgetHeight = widgetElement.getStyle().getHeight(); + + if (widgetHeight.endsWith("%") + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + getWidget().updateCaptionOffset(captionElement); + } else if (widgetWidth.endsWith("%") + && (pos == CaptionPosition.LEFT || pos == CaptionPosition.RIGHT)) { + getWidget().updateCaptionOffset(captionElement); + } + + int h = getLayoutManager().getOuterHeight(captionElement) + - getLayoutManager().getMarginHeight(captionElement); + childCaptionElementHeight.put(widgetElement, h); + + updateLayoutHeight(); + + if (needsExpand()) { + updateExpand(); + } + } + }; + + private ElementResizeListener childComponentResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + int h = getLayoutManager().getOuterHeight(e.getElement()); + childElementHeight.put((Element) e.getElement().cast(), h); + updateLayoutHeight(); + + if (needsExpand()) { + updateExpand(); + } + } + }; + + private ElementResizeListener spacingResizeListener = new ElementResizeListener() { + public void onElementResize(ElementResizeEvent e) { + resizeCount++; + if (needsExpand()) { + updateExpand(); + } + } + }; + + private void updateLayoutHeight() { + if (needsFixedHeight() && childElementHeight.size() > 0) { + int h = getMaxHeight(); + h += getLayoutManager().getBorderHeight(getWidget().getElement()) + + getLayoutManager().getPaddingHeight( + getWidget().getElement()); + getWidget().getElement().getStyle().setHeight(h, Unit.PX); + getLayoutManager().setNeedsMeasure(this); + } + } + + private void updateExpand() { + // System.out.println("All sizes: " + // + childElementHeight.values().toString() + " - Caption sizes: " + // + childCaptionElementHeight.values().toString()); + getWidget().updateExpand(); + } + + private int getMaxHeight() { + int highestNonRelative = -1; + int highestRelative = -1; + for (Element el : childElementHeight.keySet()) { + // TODO would be more efficient to measure the slot element if both + // caption and child widget elements need to be measured. Keeping + // track of what to measure is the most difficult part of this + // layout. + CaptionPosition pos = getWidget().getCaptionPositionFromElement( + (Element) el.getParentElement().cast()); + if (needsMeasure.contains(el)) { + int h = childElementHeight.get(el); + String sHeight = el.getStyle().getHeight(); + // Only add the caption size to the height of the slot if + // coption position is top or bottom + if (childCaptionElementHeight.containsKey(el) + && (sHeight == null || !sHeight.endsWith("%")) + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + h += childCaptionElementHeight.get(el); + } + if (h > highestNonRelative) { + highestNonRelative = h; + } + } else { + int h = childElementHeight.get(el); + if (childCaptionElementHeight.containsKey(el) + && (pos == CaptionPosition.TOP || pos == CaptionPosition.BOTTOM)) { + h += childCaptionElementHeight.get(el); + } + if (h > highestRelative) { + highestRelative = h; + } + } + } + return highestNonRelative > -1 ? highestNonRelative : highestRelative; + } + + @Override + public void onUnregister() { + // Cleanup all ElementResizeListeners + + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), layoutResizeListener); + + for (int i = 0; i < getWidget().getWidgetCount(); i++) { + // TODO unsafe + Slot slot = (Slot) getWidget().getWidget(i); + + if (slot.hasCaption()) { + getLayoutManager().removeElementResizeListener( + slot.getCaptionElement(), slotCaptionResizeListener); + } + + if (slot.getSpacingElement() != null) { + getLayoutManager().removeElementResizeListener( + slot.getSpacingElement(), spacingResizeListener); + } + + getLayoutManager() + .removeElementResizeListener(slot.getWidget().getElement(), + childComponentResizeListener); + + } + + super.onUnregister(); + } + + private void setLayoutHeightListener(boolean add) { + if (add) { + getLayoutManager().addElementResizeListener( + getWidget().getElement(), layoutResizeListener); + } else { + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), layoutResizeListener); + if (!needsExpand()) { + childElementHeight.clear(); + childCaptionElementHeight.clear(); + } + } + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java index ef74228ae8..e205723e64 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java +++ b/src/com/vaadin/terminal/gwt/client/ui/AbstractConnector.java @@ -163,6 +163,10 @@ public abstract class AbstractConnector implements ServerConnector, .addHandler(StateChangeEvent.TYPE, handler); } + public void removeStateChangeHandler(StateChangeHandler handler) { + ensureHandlerManager().removeHandler(StateChangeEvent.TYPE, handler); + } + public void onStateChanged(StateChangeEvent stateChangeEvent) { if (debugLogging) { VConsole.log("State change event for " diff --git a/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java new file mode 100644 index 0000000000..20d9ca9d30 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/HorizontalBoxLayoutConnector.java @@ -0,0 +1,26 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.HorizontalLayout; + +@Connect(value = HorizontalLayout.class, loadStyle = LoadStyle.EAGER) +public class HorizontalBoxLayoutConnector extends AbstractBoxLayoutConnector { + + @Override + public void init() { + super.init(); + getWidget().setVertical(false); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // TODO remove when Vaadin style name handling is improved so that it + // won't override extra client side style names + getWidget().setVertical(false); + super.updateFromUIDL(uidl, client); + getWidget().setVertical(false); + } + +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/PreLayoutListener.java b/src/com/vaadin/terminal/gwt/client/ui/PreLayoutListener.java new file mode 100644 index 0000000000..9a087bb46d --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/PreLayoutListener.java @@ -0,0 +1,8 @@ +/* +@VaadinApache2LicenseForJavaFiles@ + */ +package com.vaadin.terminal.gwt.client.ui; + +public interface PreLayoutListener { + public void preLayout(); +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java new file mode 100644 index 0000000000..540ad58c2b --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VBoxLayout.java @@ -0,0 +1,705 @@ +package com.vaadin.terminal.gwt.client.ui; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.dom.client.Node; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.terminal.gwt.client.LayoutManager; + +public class VBoxLayout extends FlowPanel { + + private static final String ALIGN_CLASS_PREFIX = "v-align-"; + + protected boolean spacing = false; + + protected boolean vertical = true; + + protected boolean definedHeight = false; + + private Map<Widget, Slot> widgetToSlot = new HashMap<Widget, Slot>(); + + private LayoutManager layoutManager; + + public VBoxLayout() { + setStylePrimaryName("v-boxlayout"); + setVertical(true); + } + + public void setVertical(boolean isVertical) { + vertical = isVertical; + if (vertical) { + addStyleName("v-vertical"); + removeStyleName("v-horizontal"); + } else { + addStyleName("v-horizontal"); + removeStyleName("v-vertical"); + } + } + + public void addOrMoveSlot(Slot slot, int index) { + if (slot.getParent() == this) { + int currentIndex = getWidgetIndex(slot); + if (index == currentIndex) { + return; + } + } + insert(slot, index); + } + + @Override + protected void insert(Widget child, Element container, int beforeIndex, + boolean domInsert) { + // Validate index; adjust if the widget is already a child of this + // panel. + beforeIndex = adjustIndex(child, beforeIndex); + + // Detach new child. + child.removeFromParent(); + + // Logical attach. + getChildren().insert(child, beforeIndex); + + // Physical attach. + container = expandWrapper != null ? expandWrapper : getElement(); + if (domInsert) { + DOM.insertChild(container, child.getElement(), + spacing ? beforeIndex * 2 : beforeIndex); + } else { + DOM.appendChild(container, child.getElement()); + } + + // Adopt. + adopt(child); + } + + public Slot removeSlot(Widget widget) { + Slot slot = getSlot(widget); + remove(slot); + widgetToSlot.remove(widget); + return slot; + } + + public Slot getSlot(Widget widget) { + Slot slot = widgetToSlot.get(widget); + if (slot == null) { + slot = new Slot(widget); + widgetToSlot.put(widget, slot); + } + return slot; + } + + public enum CaptionPosition { + TOP, RIGHT, BOTTOM, LEFT + } + + protected class Slot extends SimplePanel { + + private Element spacer; + + private Element captionWrap; + private Element caption; + private Element captionText; + private Icon icon; + private Element errorIcon; + private Element requiredIcon; + + // Caption is placed after component unless there is some part which + // moves it above. + private CaptionPosition captionPosition = CaptionPosition.RIGHT; + + private AlignmentInfo alignment; + private double expandRatio = -1; + + public Slot(Widget widget) { + setWidget(widget); + setStylePrimaryName("v-slot"); + } + + public AlignmentInfo getAlignment() { + return alignment; + } + + public void setAlignment(AlignmentInfo alignment) { + this.alignment = alignment; + + if (alignment.isHorizontalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "center"); + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + } else if (alignment.isRight()) { + addStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "right"); + removeStyleName(ALIGN_CLASS_PREFIX + "center"); + } + if (alignment.isVerticalCenter()) { + addStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } else if (alignment.isBottom()) { + addStyleName(ALIGN_CLASS_PREFIX + "bottom"); + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + } else { + removeStyleName(ALIGN_CLASS_PREFIX + "middle"); + removeStyleName(ALIGN_CLASS_PREFIX + "bottom"); + } + } + + public void setExpandRatio(double expandRatio) { + this.expandRatio = expandRatio; + } + + public double getExpandRatio() { + return expandRatio; + } + + public void setSpacing(boolean spacing) { + if (spacing && spacer == null) { + spacer = DOM.createDiv(); + spacer.addClassName("v-spacing"); + getElement().getParentElement().insertBefore(spacer, + getElement()); + } else if (!spacing && spacer != null) { + spacer.removeFromParent(); + spacer = null; + } + } + + public Element getSpacingElement() { + return spacer; + } + + protected int getSpacingSize(boolean vertical) { + if (spacer == null) { + return 0; + } + + if (layoutManager != null) { + if (vertical) { + return layoutManager.getOuterHeight(spacer); + } else { + return layoutManager.getOuterWidth(spacer); + } + } + // TODO place for optimization (in expense of theme + // flexibility): only measure one of the elements and cache the + // value + return vertical ? spacer.getOffsetHeight() : spacer + .getOffsetWidth(); + // } + } + + public void setCaptionPosition(CaptionPosition captionPosition) { + if (caption == null) { + return; + } + + captionWrap.removeClassName("v-caption-on-" + + this.captionPosition.name().toLowerCase()); + + this.captionPosition = captionPosition; + if (captionPosition == CaptionPosition.BOTTOM + || captionPosition == CaptionPosition.RIGHT) { + captionWrap.appendChild(caption); + } else { + captionWrap.insertFirst(caption); + } + + captionWrap.addClassName("v-caption-on-" + + captionPosition.name().toLowerCase()); + } + + public CaptionPosition getCaptionPosition() { + return captionPosition; + } + + // TODO refactor VCaption and use that instead: creates a tight coupling + // between this layout and Vaadin, but it's already coupled + public void setCaption(String captionText, String iconUrl, + List<String> styles, String error, boolean required, + boolean enabled) { + + // TODO place for optimization: check if any of these have changed + // since last time, and only run those changes + + // Caption wrappers + if (captionText != null || iconUrl != null || error != null + || required) { + if (caption == null) { + caption = DOM.createDiv(); + captionWrap = DOM.createDiv(); + captionWrap.addClassName("v-connector"); + captionWrap.addClassName("v-has-caption"); + getElement().appendChild(captionWrap); + captionWrap.appendChild(getWidget().getElement()); + } + } else if (caption != null) { + getElement().appendChild(getWidget().getElement()); + captionWrap.removeFromParent(); + caption = null; + captionWrap = null; + } + + // Caption text + if (captionText != null) { + if (this.captionText == null) { + this.captionText = DOM.createSpan(); + this.captionText.addClassName("v-captiontext"); + caption.appendChild(this.captionText); + } + if (captionText.equals("")) { + this.captionText.setInnerHTML(" "); + } else { + this.captionText.setInnerText(captionText); + } + } else if (this.captionText != null) { + this.captionText.removeFromParent(); + this.captionText = null; + } + + // Icon + if (iconUrl != null) { + if (icon == null) { + icon = new Icon(); + // icon = DOM.createImg(); + // icon.setClassName("v-icon"); + caption.insertFirst(icon.getElement()); + } + // icon.setAttribute("src", iconUrl); + icon.setUri(iconUrl); + } else if (icon != null) { + icon.getElement().removeFromParent(); + icon = null; + } + + // Required + if (required) { + if (requiredIcon == null) { + requiredIcon = DOM.createSpan(); + // TODO decide something better + requiredIcon.setInnerHTML("*"); + requiredIcon.setClassName("v-required-field-indicator"); + } + caption.appendChild(requiredIcon); + } else if (requiredIcon != null) { + requiredIcon.removeFromParent(); + requiredIcon = null; + } + + // Error + if (error != null) { + if (errorIcon == null) { + errorIcon = DOM.createSpan(); + errorIcon.setClassName("v-errorindicator"); + } + caption.appendChild(errorIcon); + } else if (errorIcon != null) { + errorIcon.removeFromParent(); + errorIcon = null; + } + + if (caption != null) { + // Styles + caption.setClassName("v-caption"); + + if (styles != null) { + for (String style : styles) { + caption.addClassName("v-caption-" + style); + } + } + + if (enabled) { + caption.removeClassName("v-disabled"); + } else { + caption.addClassName("v-disabled"); + } + + // Caption position + if (captionText != null || iconUrl != null) { + setCaptionPosition(CaptionPosition.TOP); + } else { + setCaptionPosition(CaptionPosition.RIGHT); + } + } + + // TODO theme flexibility: add extra styles to captionWrap as well? + + } + + public boolean hasCaption() { + return caption != null; + } + + public Element getCaptionElement() { + return caption; + } + + public void setRelativeWidth(boolean relativeWidth) { + updateRelativeSize(relativeWidth, "width"); + } + + public void setRelativeHeight(boolean relativeHeight) { + updateRelativeSize(relativeHeight, "height"); + } + + private void updateRelativeSize(boolean isRelativeSize, String direction) { + if (isRelativeSize && hasCaption()) { + captionWrap.getStyle().setProperty( + direction, + getWidget().getElement().getStyle() + .getProperty(direction)); + captionWrap.addClassName("v-has-" + direction); + } else if (hasCaption()) { + captionWrap.getStyle().clearHeight(); + captionWrap.removeClassName("v-has-" + direction); + captionWrap.getStyle().clearPaddingTop(); + captionWrap.getStyle().clearPaddingRight(); + captionWrap.getStyle().clearPaddingBottom(); + captionWrap.getStyle().clearPaddingLeft(); + caption.getStyle().clearMarginTop(); + caption.getStyle().clearMarginRight(); + caption.getStyle().clearMarginBottom(); + caption.getStyle().clearMarginLeft(); + } + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + if (DOM.eventGetType(event) == Event.ONLOAD + && icon.getElement() == DOM.eventGetTarget(event)) { + if (layoutManager != null) { + layoutManager.layoutLater(); + } else { + updateCaptionOffset(caption); + } + } + } + + @Override + protected void onDetach() { + if (spacer != null) { + spacer.removeFromParent(); + } + super.onDetach(); + } + + } + + protected class Icon extends UIObject { + public static final String CLASSNAME = "v-icon"; + private String myUrl; + + public Icon() { + setElement(DOM.createImg()); + DOM.setElementProperty(getElement(), "alt", ""); + setStyleName(CLASSNAME); + } + + public void setUri(String url) { + if (!url.equals(myUrl)) { + /* + * Start sinking onload events, widgets responsibility to react. + * We must do this BEFORE we set src as IE fires the event + * immediately if the image is found in cache (#2592). + */ + sinkEvents(Event.ONLOAD); + + DOM.setElementProperty(getElement(), "src", url); + myUrl = url; + } + } + + } + + void setLayoutManager(LayoutManager manager) { + layoutManager = manager; + } + + private static final RegExp captionPositionRegexp = RegExp + .compile("v-caption-on-(\\S+)"); + + CaptionPosition getCaptionPositionFromElement(Element captionWrap) { + // Get caption position from the classname + MatchResult matcher = captionPositionRegexp.exec(captionWrap + .getClassName()); + if (matcher == null || matcher.getGroupCount() < 2) { + return CaptionPosition.TOP; + } + String captionClass = matcher.getGroup(1); + CaptionPosition captionPosition = CaptionPosition.valueOf( + CaptionPosition.class, captionClass.toUpperCase()); + return captionPosition; + } + + void updateCaptionOffset(Element caption) { + + Element captionWrap = caption.getParentElement().cast(); + + Style captionWrapStyle = captionWrap.getStyle(); + captionWrapStyle.clearPaddingTop(); + captionWrapStyle.clearPaddingRight(); + captionWrapStyle.clearPaddingBottom(); + captionWrapStyle.clearPaddingLeft(); + + Style captionStyle = caption.getStyle(); + captionStyle.clearMarginTop(); + captionStyle.clearMarginRight(); + captionStyle.clearMarginBottom(); + captionStyle.clearMarginLeft(); + + // Get caption position from the classname + CaptionPosition captionPosition = getCaptionPositionFromElement(captionWrap); + + if (captionPosition == CaptionPosition.LEFT + || captionPosition == CaptionPosition.RIGHT) { + int captionWidth; + if (layoutManager != null) { + captionWidth = layoutManager.getOuterWidth(caption) + - layoutManager.getMarginWidth(caption); + } else { + captionWidth = caption.getOffsetWidth(); + } + if (captionWidth > 0) { + if (captionPosition == CaptionPosition.LEFT) { + captionWrapStyle.setPaddingLeft(captionWidth, Unit.PX); + captionStyle.setMarginLeft(-captionWidth, Unit.PX); + } else { + captionWrapStyle.setPaddingRight(captionWidth, Unit.PX); + captionStyle.setMarginRight(-captionWidth, Unit.PX); + } + } + } + if (captionPosition == CaptionPosition.TOP + || captionPosition == CaptionPosition.BOTTOM) { + int captionHeight; + if (layoutManager != null) { + captionHeight = layoutManager.getOuterHeight(caption) + - layoutManager.getMarginHeight(caption); + } else { + captionHeight = caption.getOffsetHeight(); + } + if (captionHeight > 0) { + if (captionPosition == CaptionPosition.TOP) { + captionWrapStyle.setPaddingTop(captionHeight, Unit.PX); + captionStyle.setMarginTop(-captionHeight, Unit.PX); + } else { + captionWrapStyle.setPaddingBottom(captionHeight, Unit.PX); + captionStyle.setMarginBottom(-captionHeight, Unit.PX); + } + } + } + } + + private void toggleStyleName(String name, boolean enabled) { + if (enabled) { + addStyleName(name); + } else { + removeStyleName(name); + } + } + + void setMargin(VMarginInfo marginInfo) { + toggleStyleName("v-margin-top", marginInfo.hasTop()); + toggleStyleName("v-margin-right", marginInfo.hasRight()); + toggleStyleName("v-margin-bottom", marginInfo.hasBottom()); + toggleStyleName("v-margin-left", marginInfo.hasLeft()); + } + + protected void setSpacing(boolean spacingEnabled) { + spacing = spacingEnabled; + for (Slot slot : widgetToSlot.values()) { + if (getWidgetIndex(slot) > 0) { + slot.setSpacing(spacingEnabled); + } + } + } + + private void recalculateExpands() { + double total = 0; + for (Slot slot : widgetToSlot.values()) { + if (slot.getExpandRatio() > -1) { + total += slot.getExpandRatio(); + } else { + if (vertical) { + slot.getElement().getStyle().clearHeight(); + } else { + slot.getElement().getStyle().clearWidth(); + } + } + } + for (Slot slot : widgetToSlot.values()) { + if (slot.getExpandRatio() > -1) { + if (vertical) { + slot.setHeight((100 * (slot.getExpandRatio() / total)) + + "%"); + } else { + slot.setWidth((100 * (slot.getExpandRatio() / total)) + "%"); + } + } + } + } + + private Element expandWrapper; + + void clearExpand() { + if (expandWrapper != null) { + for (; expandWrapper.getChildCount() > 0;) { + Element el = expandWrapper.getChild(0).cast(); + getElement().appendChild(el); + if (vertical) { + el.getStyle().clearHeight(); + el.getStyle().clearMarginTop(); + } else { + el.getStyle().clearWidth(); + el.getStyle().clearMarginLeft(); + } + } + expandWrapper.removeFromParent(); + expandWrapper = null; + } + } + + public void updateExpand() { + boolean isExpanding = false; + for (Widget slot : getChildren()) { + if (((Slot) slot).getExpandRatio() > -1) { + isExpanding = true; + } else { + if (vertical) { + slot.getElement().getStyle().clearHeight(); + } else { + slot.getElement().getStyle().clearWidth(); + } + } + slot.getElement().getStyle().clearMarginLeft(); + slot.getElement().getStyle().clearMarginTop(); + } + + if (isExpanding) { + if (expandWrapper == null) { + expandWrapper = DOM.createDiv(); + expandWrapper.setClassName("v-expand"); + for (; getElement().getChildCount() > 0;) { + Node el = getElement().getChild(0); + expandWrapper.appendChild(el); + } + getElement().appendChild(expandWrapper); + } + + int totalSize = 0; + for (Widget w : getChildren()) { + Slot slot = (Slot) w; + if (slot.getExpandRatio() == -1) { + if (layoutManager != null) { + // TODO check caption position + if (vertical) { + totalSize += layoutManager.getOuterHeight(slot + .getWidget().getElement()) + - layoutManager.getMarginHeight(slot + .getWidget().getElement()); + if (slot.hasCaption()) { + totalSize += layoutManager.getOuterHeight(slot + .getCaptionElement()) + - layoutManager.getMarginHeight(slot + .getCaptionElement()); + } + } else { + int max = -1; + max = layoutManager.getOuterWidth(slot.getWidget() + .getElement()) + - layoutManager.getMarginWidth(slot + .getWidget().getElement()); + if (slot.hasCaption()) { + int max2 = layoutManager.getOuterWidth(slot + .getCaptionElement()) + - layoutManager.getMarginWidth(slot + .getCaptionElement()); + max = Math.max(max, max2); + } + totalSize += max; + } + } else { + totalSize += vertical ? slot.getOffsetHeight() : slot + .getOffsetWidth(); + } + } + // TODO fails in Opera, always returns 0 + totalSize += slot.getSpacingSize(vertical); + } + + // When we set the margin to the first child, we don't need + // overflow:hidden in the layout root element, since the wrapper + // would otherwise be placed outside of the layout root element + // and block events on elements below it. + if (vertical) { + expandWrapper.getStyle().setPaddingTop(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginTop(-totalSize, Unit.PX); + } else { + expandWrapper.getStyle().setPaddingLeft(totalSize, Unit.PX); + expandWrapper.getFirstChildElement().getStyle() + .setMarginLeft(-totalSize, Unit.PX); + } + + recalculateExpands(); + } + } + + public void recalculateLayoutHeight() { + // Only needed if a horizontal layout is undefined high, and contains + // relative height children or vertical alignments + if (vertical || definedHeight) { + return; + } + + boolean hasRelativeHeightChildren = false; + boolean hasVAlign = false; + + for (Widget slot : getChildren()) { + Widget widget = ((Slot) slot).getWidget(); + String h = widget.getElement().getStyle().getHeight(); + if (h != null && h.indexOf("%") > -1) { + hasRelativeHeightChildren = true; + } + AlignmentInfo a = ((Slot) slot).getAlignment(); + if (a != null && (a.isVerticalCenter() || a.isBottom())) { + hasVAlign = true; + } + } + + if (hasRelativeHeightChildren || hasVAlign) { + int newHeight; + if (layoutManager != null) { + newHeight = layoutManager.getOuterHeight(getElement()) + - layoutManager.getMarginHeight(getElement()); + } else { + newHeight = getElement().getOffsetHeight(); + } + VBoxLayout.this.getElement().getStyle() + .setHeight(newHeight, Unit.PX); + } + + } + + void clearHeight() { + getElement().getStyle().clearHeight(); + } + + @Override + public void setHeight(String height) { + super.setHeight(height); + definedHeight = (height != null && !"".equals(height)); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java new file mode 100644 index 0000000000..369afb9e2f --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/VerticalBoxLayoutConnector.java @@ -0,0 +1,26 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.vaadin.terminal.gwt.client.ApplicationConnection; +import com.vaadin.terminal.gwt.client.UIDL; +import com.vaadin.terminal.gwt.client.ui.Connect.LoadStyle; +import com.vaadin.ui.VerticalLayout; + +@Connect(value = VerticalLayout.class, loadStyle = LoadStyle.EAGER) +public class VerticalBoxLayoutConnector extends AbstractBoxLayoutConnector { + + @Override + public void init() { + super.init(); + getWidget().setVertical(true); + } + + @Override + public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + // TODO fix when Vaadin style name handling is improved so that it won't + // override extra client side style names + getWidget().setVertical(true); + super.updateFromUIDL(uidl, client); + getWidget().setVertical(true); + } + +} diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java new file mode 100644 index 0000000000..e52979267a --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/BoxLayoutTest.java @@ -0,0 +1,437 @@ +package com.vaadin.tests.components.orderedlayout; + +import java.util.ArrayList; +import java.util.Arrays; + +import com.vaadin.annotations.Theme; +import com.vaadin.data.Property.ValueChangeEvent; +import com.vaadin.data.Property.ValueChangeListener; +import com.vaadin.event.LayoutEvents.LayoutClickEvent; +import com.vaadin.event.LayoutEvents.LayoutClickListener; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.UserError; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.AbstractComponent; +import com.vaadin.ui.AbstractField; +import com.vaadin.ui.AbstractOrderedLayout; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; +import com.vaadin.ui.CheckBox; +import com.vaadin.ui.Component; +import com.vaadin.ui.GridLayout; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.Label; +import com.vaadin.ui.Label.ContentMode; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; +import com.vaadin.ui.themes.Reindeer; + +@Theme("tests-components") +public class BoxLayoutTest extends AbstractTestRoot { + + protected AbstractOrderedLayout view; + + protected AbstractOrderedLayout l; + + protected AbstractComponent target; + + protected NativeSelect componentWidth; + protected NativeSelect componentHeight; + protected NativeSelect componentCaption; + protected NativeSelect componentIcon; + protected TextField componentDescription; + protected CheckBox componentError; + protected CheckBox componentRequired; + + protected NativeSelect align; + protected CheckBox expand; + + @Override + protected void setup(WrappedRequest request) { + + view = new VerticalLayout(); + view.setSizeFull(); + view.setMargin(true); + view.setSpacing(true); + + view.addComponent(createControls(false)); + view.addComponent(createTestLayout(false)); + view.setExpandRatio(view.getComponent(1), 1); + + setContent(view); + getApplication().setRootPreserved(true); + } + + protected AbstractOrderedLayout createControls(boolean horizontal) { + VerticalLayout root = new VerticalLayout(); + root.setSpacing(true); + + // First row + HorizontalLayout header = new HorizontalLayout(); + header.setSpacing(true); + root.addComponent(header); + + Label title = new Label("BoxLayout Test"); + title.addStyleName(Reindeer.LABEL_H1); + header.addComponent(title); + + final CheckBox vertical = new CheckBox("Vertical", !horizontal); + vertical.setImmediate(true); + vertical.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + view.removeAllComponents(); + + view.addComponent(createControls(!vertical.getValue() + .booleanValue())); + view.addComponent(createTestLayout(!vertical.getValue() + .booleanValue())); + + view.setExpandRatio(view.getComponent(1), 1); + + } + }); + header.addComponent(vertical); + + Button addComponent = new Button("Add Component", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + GridLayout grid = new GridLayout(2, 2); + Button grow = new Button("Grow Me", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + if (event.getButton().getWidth() == -1) { + event.getButton().setHeight("50px"); + event.getButton().setWidth("200px"); + } else { + event.getButton() + .setSizeUndefined(); + } + } + }); + grid.addComponent(new Label("Grid cell 1")); + grid.addComponent(new Label("Grid cell 2")); + grid.addComponent(grow); + grid.addComponent(new Label("Grid cell 4")); + // l.addComponent(grid); + l.addComponent(new TextField("Some field")); + } + }); + header.addComponent(addComponent); + + Button removeComponent = new Button("Remove Component", + new Button.ClickListener() { + public void buttonClick(ClickEvent event) { + Component last = l.getComponent(l.getComponentCount() - 1); + l.removeComponent(last); + } + }); + header.addComponent(removeComponent); + + // Second row + HorizontalLayout controls = new HorizontalLayout(); + controls.setSpacing(true); + root.addComponent(controls); + + // Layout controls + HorizontalLayout layout = new HorizontalLayout(); + layout.addStyleName("fieldset"); + layout.setSpacing(true); + controls.addComponent(layout); + layout.addComponent(new Label("Layout")); + + ArrayList<String> sizes = new ArrayList<String>(); + sizes.addAll(Arrays.asList("100px", "30em", "100%")); + + final NativeSelect width = new NativeSelect(null, sizes); + width.setImmediate(true); + width.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (width.getValue() != null) { + l.setWidth(width.getValue().toString()); + } else { + l.setWidth(null); + } + } + }); + layout.addComponent(width); + layout.addComponent(new Label("×", ContentMode.XHTML)); + final NativeSelect height = new NativeSelect(null, sizes); + height.setImmediate(true); + height.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (height.getValue() != null) { + l.setHeight(height.getValue().toString()); + } else { + l.setHeight(null); + } + } + }); + layout.addComponent(height); + + final CheckBox margin = new CheckBox("Margin", false); + margin.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + l.setMargin(margin.getValue().booleanValue()); + } + }); + margin.setImmediate(true); + layout.addComponent(margin); + layout.addComponent(margin); + + final CheckBox spacing = new CheckBox("Spacing", false); + spacing.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + l.setSpacing(spacing.getValue().booleanValue()); + } + }); + spacing.setImmediate(true); + layout.addComponent(spacing); + + // Cell controls + HorizontalLayout cell = new HorizontalLayout(); + cell.addStyleName("fieldset"); + cell.setSpacing(true); + controls.addComponent(cell); + cell.addComponent(new Label("Cell")); + + ArrayList<Alignment> alignments = new ArrayList<Alignment>(); + alignments.addAll(Arrays.asList(Alignment.TOP_LEFT, + Alignment.MIDDLE_LEFT, Alignment.BOTTOM_LEFT, + Alignment.TOP_CENTER, Alignment.MIDDLE_CENTER, + Alignment.BOTTOM_CENTER, Alignment.TOP_RIGHT, + Alignment.MIDDLE_RIGHT, Alignment.BOTTOM_RIGHT)); + + align = new NativeSelect(null, alignments); + for (Alignment a : alignments) { + align.setItemCaption(a, + a.getVerticalAlignment() + "-" + a.getHorizontalAlignment()); + } + align.setImmediate(true); + align.setEnabled(false); + align.setNullSelectionAllowed(false); + align.select(Alignment.TOP_LEFT); + align.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target == null) { + return; + } + l.setComponentAlignment(target, ((Alignment) align.getValue())); + } + }); + cell.addComponent(align); + + expand = new CheckBox("Expand"); + expand.setImmediate(true); + expand.setEnabled(false); + expand.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target != null) { + l.setExpandRatio(target, expand.getValue() ? 1 : 0); + } + } + }); + cell.addComponent(expand); + + // Component controls + HorizontalLayout component = new HorizontalLayout(); + component.addStyleName("fieldset"); + component.setSpacing(true); + root.addComponent(component); + component.addComponent(new Label("Component")); + + sizes = new ArrayList<String>(); + sizes.addAll(Arrays.asList("50px", "200px", "10em", "50%", "100%")); + + componentWidth = new NativeSelect(null, sizes); + componentWidth.setImmediate(true); + componentWidth.setEnabled(false); + componentWidth.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target == null) { + return; + } + if (componentWidth.getValue() != null) { + target.setWidth(componentWidth.getValue().toString()); + } else { + target.setWidth(null); + } + } + }); + component.addComponent(componentWidth); + component.addComponent(new Label("×", ContentMode.XHTML)); + + componentHeight = new NativeSelect(null, sizes); + componentHeight.setImmediate(true); + componentHeight.setEnabled(false); + componentHeight.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentHeight.getValue() != null) { + target.setHeight(componentHeight.getValue().toString()); + } else { + target.setHeight(null); + } + } + }); + component.addComponent(componentHeight); + + componentCaption = new NativeSelect("Caption", Arrays.asList("Short", + "Slightly Longer Caption")); + componentCaption.setImmediate(true); + componentCaption.setEnabled(false); + componentCaption.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentCaption.getValue() != null) { + target.setCaption(componentCaption.getValue().toString()); + } else { + target.setCaption(null); + } + } + }); + component.addComponent(componentCaption); + + componentIcon = new NativeSelect("Icon", Arrays.asList( + "../runo/icons/16/folder.png", "../runo/icons/32/document.png")); + componentIcon.setImmediate(true); + componentIcon.setEnabled(false); + componentIcon.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (componentIcon.getValue() != null) { + target.setIcon(new ThemeResource(componentIcon.getValue() + .toString())); + } else { + target.setIcon(null); + } + } + }); + component.addComponent(componentIcon); + + componentDescription = new TextField("Description"); + componentDescription.setImmediate(true); + componentDescription.setEnabled(false); + componentDescription.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + target.setDescription(componentDescription.getValue()); + } + }); + component.addComponent(componentDescription); + + componentError = new CheckBox("Error"); + componentError.setImmediate(true); + componentError.setEnabled(false); + componentError.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target != null) { + target.setComponentError(componentError.getValue() ? new UserError( + "Error message") : null); + } + } + }); + component.addComponent(componentError); + + componentRequired = new CheckBox("Required"); + componentRequired.setImmediate(true); + componentRequired.setEnabled(false); + componentRequired.addListener(new ValueChangeListener() { + public void valueChange(ValueChangeEvent event) { + if (target != null && target instanceof AbstractField) { + ((AbstractField<?>) target).setRequired(componentRequired + .getValue()); + } + } + }); + component.addComponent(componentRequired); + + return root; + } + + protected AbstractOrderedLayout createTestLayout(boolean horizontal) { + l = horizontal ? new HorizontalLayout() : new VerticalLayout(); + l.setSizeUndefined(); + l.addStyleName("test"); + + Label label = new Label("Component 1"); + l.addComponent(label); + l.addComponent(new Button("Component 2")); + + l.addListener(new LayoutClickListener() { + public void layoutClick(LayoutClickEvent event) { + if (event.getChildComponent() == null + || target == event.getChildComponent()) { + if (target != null) { + target.removeStyleName("target"); + } + target = null; + } else if (target != event.getChildComponent()) { + if (target != null) { + target.removeStyleName("target"); + } + target = (AbstractComponent) event.getChildComponent(); + target.addStyleName("target"); + } + componentWidth.setEnabled(target != null); + componentHeight.setEnabled(target != null); + componentCaption.setEnabled(target != null); + componentIcon.setEnabled(target != null); + componentDescription.setEnabled(target != null); + componentError.setEnabled(target != null); + componentRequired.setEnabled(target != null + && target instanceof AbstractField); + align.setEnabled(target != null); + expand.setEnabled(target != null); + if (target != null) { + if (target.getWidth() > -1) { + componentWidth.select(new Float(target.getWidth()) + .intValue() + + target.getWidthUnits().getSymbol()); + } else { + componentWidth.select(null); + } + if (target.getHeight() > -1) { + componentHeight.select(new Float(target.getHeight()) + .intValue() + + target.getHeightUnits().getSymbol()); + } else { + componentHeight.select(null); + } + + align.select(l.getComponentAlignment(target)); + expand.setValue(new Boolean(l.getExpandRatio(target) > 0)); + + componentCaption.select(target.getCaption()); + if (target.getIcon() != null) { + componentIcon.select(((ThemeResource) target.getIcon()) + .getResourceId()); + } else { + componentIcon.select(null); + } + componentDescription.setValue(target.getDescription()); + componentError.setValue(target.getComponentError() != null); + if (target instanceof AbstractField) { + componentRequired.setValue(((AbstractField<?>) target) + .isRequired()); + } + } + } + }); + + target = null; + + return l; + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +}
\ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java index 172e808070..a4a7098f52 100644 --- a/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/OrderedLayoutCases.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import com.vaadin.annotations.Theme; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.Property.ValueChangeListener; import com.vaadin.terminal.WrappedRequest; @@ -20,6 +21,7 @@ import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.NativeSelect; import com.vaadin.ui.VerticalLayout; +@Theme("tests-components") public class OrderedLayoutCases extends AbstractTestRoot { private static final String[] dimensionValues = { "-1px", "5px", "350px", "800px", "100%", "50%" }; diff --git a/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java b/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java new file mode 100644 index 0000000000..973bd63d76 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/components/orderedlayout/VaadinTunesLayout.java @@ -0,0 +1,341 @@ +package com.vaadin.tests.components.orderedlayout; + +import com.vaadin.annotations.Theme; +import com.vaadin.terminal.Sizeable; +import com.vaadin.terminal.ThemeResource; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Alignment; +import com.vaadin.ui.Button; +import com.vaadin.ui.ComboBox; +import com.vaadin.ui.Embedded; +import com.vaadin.ui.HorizontalLayout; +import com.vaadin.ui.HorizontalSplitPanel; +import com.vaadin.ui.Label; +import com.vaadin.ui.NativeButton; +import com.vaadin.ui.NativeSelect; +import com.vaadin.ui.Slider; +import com.vaadin.ui.Table; +import com.vaadin.ui.VerticalLayout; + +@Theme("tests-components") +public class VaadinTunesLayout extends AbstractTestRoot { + + @Override + public void setup(WrappedRequest request) { + + /* + * We'll build the whole UI here, since the application will not contain + * any logic. Otherwise it would be more practical to separate parts of + * the UI into different classes and methods. + */ + + // Main (browser) window, needed in all Vaadin applications + VerticalLayout rootLayout = new VerticalLayout(); + // final Window root = new Window("VaadinTunes", rootLayout); + + /* + * We'll attach the window to the browser view already here, so we won't + * forget it later. + */ + setContent(rootLayout); + + // root.showNotification( + // "This is an example of how you can do layouts in Vaadin.<br/>It is not a working sound player.", + // Notification.TYPE_HUMANIZED_MESSAGE); + + // Our root window contains one VerticalLayout, let's make + // sure it's 100% sized, and remove unwanted margins + rootLayout.setSizeFull(); + rootLayout.setMargin(false); + + // Top area, containing playback and volume controls, play status, view + // modes and search + HorizontalLayout top = new HorizontalLayout(); + top.setWidth("100%"); + top.setMargin(false, true, false, true); // Enable horizontal margins + top.setSpacing(true); + + // Let's attach that one straight away too + rootLayout.addComponent(top); + + // Create the placeholders for all the components in the top area + HorizontalLayout playback = new HorizontalLayout(); + HorizontalLayout volume = new HorizontalLayout(); + HorizontalLayout status = new HorizontalLayout(); + HorizontalLayout viewmodes = new HorizontalLayout(); + ComboBox search = new ComboBox(); + + // Add the components and align them properly + top.addComponent(playback); + top.addComponent(volume); + top.addComponent(status); + top.addComponent(viewmodes); + top.addComponent(search); + top.setComponentAlignment(playback, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(volume, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(status, Alignment.MIDDLE_CENTER); + top.setComponentAlignment(viewmodes, Alignment.MIDDLE_LEFT); + top.setComponentAlignment(search, Alignment.MIDDLE_LEFT); + + /* + * We want our status area to expand if the user resizes the root + * window, and we want it to accommodate as much space as there is + * available. All other components in the top layout should stay fixed + * sized, so we don't need to specify any expand ratios for them (they + * will automatically revert to zero after the following line). + */ + top.setExpandRatio(status, 1.0F); + + // Playback controls + Button prev = new NativeButton("Previous"); + Button play = new NativeButton("Play/pause"); + Button next = new NativeButton("Next"); + playback.addComponent(prev); + playback.addComponent(play); + playback.addComponent(next); + // Set spacing between the buttons + playback.setSpacing(true); + + // Volume controls + Button mute = new NativeButton("mute"); + Slider vol = new Slider(); + vol.setOrientation(Slider.ORIENTATION_HORIZONTAL); + vol.setWidth("100px"); + Button max = new NativeButton("max"); + volume.addComponent(mute); + volume.addComponent(vol); + volume.addComponent(max); + + // Status area + status.setWidth("80%"); + status.setSpacing(true); + + Button toggleVisualization = new NativeButton("Mode"); + Label timeFromStart = new Label("0:00"); + + // We'll need another layout to show currently playing track and + // progress + VerticalLayout trackDetails = new VerticalLayout(); + trackDetails.setWidth("100%"); + Label track = new Label("Track Name"); + Label album = new Label("Album Name - Artist"); + track.setWidth(null); + album.setWidth(null); + Slider progress = new Slider(); + progress.setOrientation(Slider.ORIENTATION_HORIZONTAL); + progress.setWidth("100%"); + trackDetails.addComponent(track); + trackDetails.addComponent(album); + trackDetails.addComponent(progress); + trackDetails.setComponentAlignment(track, Alignment.TOP_CENTER); + trackDetails.setComponentAlignment(album, Alignment.TOP_CENTER); + + Label timeToEnd = new Label("-4:46"); + Button jumpToTrack = new NativeButton("Show"); + + // Place all components to the status layout and align them properly + status.addComponent(toggleVisualization); + status.setComponentAlignment(toggleVisualization, Alignment.MIDDLE_LEFT); + status.addComponent(timeFromStart); + status.setComponentAlignment(timeFromStart, Alignment.BOTTOM_LEFT); + status.addComponent(trackDetails); + status.addComponent(timeToEnd); + status.setComponentAlignment(timeToEnd, Alignment.BOTTOM_LEFT); + status.addComponent(jumpToTrack); + status.setComponentAlignment(jumpToTrack, Alignment.MIDDLE_LEFT); + + // Then remember to specify the expand ratio + status.setExpandRatio(trackDetails, 1.0F); + + // View mode buttons + Button viewAsTable = new NativeButton("Table"); + Button viewAsGrid = new NativeButton("Grid"); + Button coverflow = new NativeButton("Coverflow"); + viewmodes.addComponent(viewAsTable); + viewmodes.addComponent(viewAsGrid); + viewmodes.addComponent(coverflow); + + /* + * That covers the top bar. Now let's move on to the sidebar and track + * listing + */ + + // We'll need one splitpanel to separate the sidebar and track listing + HorizontalSplitPanel bottom = new HorizontalSplitPanel(); + rootLayout.addComponent(bottom); + + // The splitpanel is by default 100% x 100%, but we'll need to adjust + // our main window layout to accomodate the height + rootLayout.setExpandRatio(bottom, 1.0F); + + // Give the sidebar less space than the listing + bottom.setSplitPosition(200, Sizeable.UNITS_PIXELS); + + // Let's add some content to the sidebar + // First, we need a layout to but all components in + VerticalLayout sidebar = new VerticalLayout(); + sidebar.setSizeFull(); + bottom.setFirstComponent(sidebar); + + /* + * Then we need some labels and buttons, and an album cover image The + * labels and buttons go into their own vertical layout, since we want + * the 'sidebar' layout to be expanding (cover image in the bottom). + * VerticalLayout is by default 100% wide. + */ + VerticalLayout selections = new VerticalLayout(); + Label library = new Label("Library"); + Button music = new NativeButton("Music"); + music.setWidth("100%"); + + Label store = new Label("Store"); + Button vaadinTunesStore = new NativeButton("VaadinTunes Store"); + vaadinTunesStore.setWidth("100%"); + Button purchased = new NativeButton("Purchased"); + purchased.setWidth("100%"); + + Label playlists = new Label("Playlists"); + Button genius = new NativeButton("Geniues"); + genius.setWidth("100%"); + Button recent = new NativeButton("Recently Added"); + recent.setWidth("100%"); + + // Lets add them to the 'selections' layout + selections.addComponent(library); + selections.addComponent(music); + selections.addComponent(store); + selections.addComponent(vaadinTunesStore); + selections.addComponent(purchased); + selections.addComponent(playlists); + selections.addComponent(genius); + selections.addComponent(recent); + + // Then add the selections to the sidebar, and set it expanding + sidebar.addComponent(selections); + sidebar.setExpandRatio(selections, 1.0F); + + // Then comes the cover artwork (we'll add the actual image in the + // themeing section) + Embedded cover = new Embedded("Currently Playing"); + sidebar.addComponent(cover); + + /* + * And lastly, we need the track listing table It should fill the whole + * left side of our bottom layout + */ + Table listing = new Table(); + listing.setSizeFull(); + listing.setSelectable(true); + bottom.setSecondComponent(listing); + + // Add the table headers + listing.addContainerProperty("Name", String.class, ""); + listing.addContainerProperty("Time", String.class, "0:00"); + listing.addContainerProperty("Artist", String.class, ""); + listing.addContainerProperty("Album", String.class, ""); + listing.addContainerProperty("Genre", String.class, ""); + listing.addContainerProperty("Rating", NativeSelect.class, + new NativeSelect()); + + // Lets populate the table with random data + String[] tracks = new String[] { "Red Flag", "Millstone", + "Not The Sun", "Breath", "Here We Are", "Deep Heaven", + "Her Voice Resides", "Natural Tan", "End It All", "Kings", + "Daylight Slaving", "Mad Man", "Resolve", "Teargas", + "African Air", "Passing Bird" }; + String[] times = new String[] { "4:12", "6:03", "5:43", "4:32", "3:42", + "4:45", "2:56", "9:34", "2:10", "3:44", "5:49", "6:30", "5:18", + "7:42", "3:13", "2:52" }; + String[] artists = new String[] { "Billy Talent", "Brand New", + "Breaking Benjamin", "Becoming The Archetype", + "Bullet For My Valentine", "Chasing Victory", "Chimaira", + "Danko Jones", "Deadlock", "Deftones", "From Autumn To Ashes", + "Haste The Day", "Four Year Strong", "In Flames", "Kemopetrol", + "John Legend" }; + String[] albums = new String[] { "Once Again", "The Caitiff Choir", + "The Devil And God", "Light Grenades", "Dicthonomy", + "Back In Black", "Dreamer", "Come Clarity", "Year Zero", + "Frames", "Fortress", "Phobia", "The Poison", "Manifesto", + "White Pony", "The Big Dirty" }; + String[] genres = new String[] { "Rock", "Metal", "Hardcore", "Indie", + "Pop", "Alternative", "Blues", "Jazz", "Hip Hop", + "Electronica", "Punk", "Hard Rock", "Dance", "R'n'B", "Gospel", + "Country" }; + for (int i = 0; i < 1000; i++) { + NativeSelect s = new NativeSelect(); + s.addItem("1 star"); + s.addItem("2 stars"); + s.addItem("3 stars"); + s.addItem("4 stars"); + s.addItem("5 stars"); + s.select(i % 5 + " stars"); + final int index = i % 16; + listing.addItem(new Object[] { tracks[index], times[index], + artists[index], albums[index], genres[index], s }, i); + } + + // We'll align the track time column to right as well + listing.setColumnAlignment("Time", Table.ALIGN_RIGHT); + + // TODO the footer + + // Now what's left to do? Themeing of course. + // setTheme("vaadintunes"); + + /* + * Let's give a namespace to our application window. This way, if + * someone uses the same theme for different applications, we don't get + * unwanted style conflicts. + */ + // root.setStyleName("tTunes"); + + top.setStyleName("top"); + top.setHeight("75px"); // Same as the background image height + + playback.setStyleName("playback"); + playback.setMargin(false, true, false, false); // Add right-side margin + play.setStyleName("play"); + next.setStyleName("next"); + prev.setStyleName("prev"); + playback.setComponentAlignment(prev, Alignment.MIDDLE_LEFT); + playback.setComponentAlignment(next, Alignment.MIDDLE_LEFT); + + volume.setStyleName("volume"); + mute.setStyleName("mute"); + max.setStyleName("max"); + vol.setWidth("78px"); + + status.setStyleName("status"); + status.setMargin(true); + status.setHeight("46px"); // Height of the background image + + toggleVisualization.setStyleName("toggle-vis"); + jumpToTrack.setStyleName("jump"); + + viewAsTable.setStyleName("viewmode-table"); + viewAsGrid.setStyleName("viewmode-grid"); + coverflow.setStyleName("viewmode-coverflow"); + + sidebar.setStyleName("sidebar"); + + music.setStyleName("selected"); + + cover.setSource(new ThemeResource("images/album-cover.jpg")); + // Because this is an image, it will retain it's aspect ratio + cover.setWidth("100%"); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +}
\ No newline at end of file diff --git a/tests/testbench/com/vaadin/tests/components/table/TextFieldRelativeWidth.java b/tests/testbench/com/vaadin/tests/components/table/TextFieldRelativeWidth.java index 865a50c5f7..f3a92d410c 100644 --- a/tests/testbench/com/vaadin/tests/components/table/TextFieldRelativeWidth.java +++ b/tests/testbench/com/vaadin/tests/components/table/TextFieldRelativeWidth.java @@ -27,8 +27,7 @@ public class TextFieldRelativeWidth extends TestBase { public class EditTable extends Table implements Button.ClickListener { - private Button addButton = new Button("Add new row", - (Button.ClickListener) this); + private Button addButton = new Button("Add new row", this); private String inputPrompt; diff --git a/tests/testbench/com/vaadin/tests/themes/LiferayThemeTest.java b/tests/testbench/com/vaadin/tests/themes/LiferayThemeTest.java new file mode 100644 index 0000000000..fa15d88799 --- /dev/null +++ b/tests/testbench/com/vaadin/tests/themes/LiferayThemeTest.java @@ -0,0 +1,37 @@ +package com.vaadin.tests.themes; + +import com.vaadin.annotations.Theme; +import com.vaadin.terminal.WrappedRequest; +import com.vaadin.tests.components.AbstractTestRoot; +import com.vaadin.ui.Label; +import com.vaadin.ui.Panel; +import com.vaadin.ui.themes.LiferayTheme; + +@Theme("liferay") +public class LiferayThemeTest extends AbstractTestRoot { + + @Override + protected void setup(WrappedRequest request) { + Panel p = new Panel("Panel"); + addComponent(p); + p.addComponent(new Label("Panel content")); + + p = new Panel("Light Panel"); + p.addStyleName(LiferayTheme.PANEL_LIGHT); + addComponent(p); + p.addComponent(new Label("Panel content")); + } + + @Override + protected String getTestDescription() { + // TODO Auto-generated method stub + return null; + } + + @Override + protected Integer getTicketNumber() { + // TODO Auto-generated method stub + return null; + } + +} |