Java tutorial
/* * Copyright 2009 IT Mill Oy * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.vaadin.henrik.drawer.widgetset.client.ui; import java.util.Iterator; import java.util.Set; import com.google.gwt.animation.client.Animation; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.DeferredCommand; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.Container; import com.vaadin.terminal.gwt.client.Paintable; import com.vaadin.terminal.gwt.client.RenderSpace; import com.vaadin.terminal.gwt.client.UIDL; import com.vaadin.terminal.gwt.client.Util; import com.vaadin.terminal.gwt.client.VCaption; /** * @author Henrik Paul / IT Mill Ltd */ public class VDrawer extends Composite implements Paintable, Container, HasWidgets { private class DrawerAnimation extends Animation { private final boolean expand; private DrawerAnimation(final boolean expand) { this.expand = expand; } @Override protected void onUpdate(final double progress) { final double multiplier = expand ? progress : 1 - progress; if (multiplier == 0) { setDrawerVisible(expand); } drawer.setHeight((drawerHeight * multiplier) + "px"); Util.notifyParentOfSizeChange(VDrawer.this, false); } /** * Ease-out interpolation only, instead of the default of both. */ @Override protected double interpolate(final double progress) { // see http://stackoverflow.com/questions/196173/#196209 return 1.5 * progress - 0.5 * Math.pow(progress, 3); // return 2 * progress - Math.pow(progress, 2); // return 1.2 * progress - 0.2 * Math.pow(progress, 6); } } private class CaptionPanel extends Composite { public CaptionPanel(final Widget icon, final Widget header) { final HorizontalPanel root = new HorizontalPanel(); initWidget(root); setStylePrimaryName(CAPTION_CLASSNAME); root.setWidth("100%"); root.add(icon); root.add(header); root.setCellWidth(header, "100%"); } public HandlerRegistration addClickHandler(final ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } } private class CaptionIcon extends Widget { public CaptionIcon() { final Element root = DOM.createDiv(); root.setInnerHTML(" "); setElement(root); setExpanded(false); } public void setExpanded(final boolean isExpanded) { if (isExpanded) { getElement().setClassName(CAPTION_ICON_EXPANDED_CLASSNAME); } else { getElement().setClassName(CAPTION_ICON_COLLAPSED_CLASSNAME); } } } /** Set the tagname used to statically resolve widget from UIDL. */ public static final String TAGNAME = "drawer"; /** Set the CSS class name to allow styling. */ public static final String CLASSNAME = "v-" + TAGNAME; public static final String DRAWER_CLASSNAME = CLASSNAME + "-content"; public static final String CAPTION_CLASSNAME = CLASSNAME + "-caption"; public static final String CAPTION_ICON_EXPANDED_CLASSNAME = CAPTION_CLASSNAME + "-expanded"; public static final String CAPTION_ICON_COLLAPSED_CLASSNAME = CAPTION_CLASSNAME + "-collapsed"; public static final String ATTRIBUTE_DEFAULTCAPTION__STRING = "defcaption"; public static final String ATTRIBUTE_ANIMATE__BOOLEAN = "animate"; public static final String ATTRIBUTE_ANIMATION_DURATION__INT = "animationSpeed"; public static final String ATTRIBUTE_DRAWER_HEIGHT__INT = "drawerHeight"; public static final String ATTRIBUTE_DRAWER_HAS_CONTENT__BOOLEAN = "hascontent"; public static final String ATTRIBUTE_CALCULATE_HEADER_WIDTH__BOOLEAN = "calculateWidth"; public static final String VARIABLE_DRAWERVISIBLE__BOOLEAN = "drawerVisible"; private final Panel root = new VerticalPanel(); private final SimplePanel drawer = new SimplePanel(); private final Widget loading = new Label(); private String componentCaption = null; private boolean componentCaptionIsVisible = true; private String defaultCaption = ""; private final Label captionLabel = new Label(defaultCaption); private final CaptionIcon captionIcon = new CaptionIcon(); private CaptionPanel captionPanel = new CaptionPanel(captionIcon, captionLabel); private boolean drawerHasContents = false; private int animationDuration = 500; private int drawerHeight = -1; private boolean calculateDrawerHeight = true; private boolean calculateCaptionWidth = true; private boolean animate = false; private boolean disabled = false; /** * The Vaadin property, intended for a {@link VCaption}, to tell whether the * caption is invisible. */ private final static String VAADIN_CAPTION_UIDL_INVISIBLE__BOOLEAN = "invisible"; /** * The Vaadin property, intended for a {@link VCaption}, for the caption's * contents. */ private final static String VAADIN_CAPTION_UIDL_TEXT__STRING = "caption"; /** The Vaadin property telling whether a component is disabled or not. */ private final static String VAADIN_DISABLED__BOOLEAN = "disabled"; /** Component identifier in UIDL communications. */ String uidlId; /** Reference to the server connection object. */ ApplicationConnection client; /** how many times the component has been updated from the server side */ private int updateCount = 0; /** * The constructor should first call super() to initialize the component and * then handle any initialization relevant to Vaadin. */ public VDrawer() { super(); initWidget(root); setStyleName(CLASSNAME); captionPanel = new CaptionPanel(captionIcon, captionLabel); captionPanel.addClickHandler(new ClickHandler() { public void onClick(final ClickEvent event) { toggleDrawerVisibility(); } }); root.add(captionPanel); root.add(drawer); drawer.setWidget(loading); drawer.setStylePrimaryName(DRAWER_CLASSNAME); } public void updateFromUIDL(final UIDL uidl, final ApplicationConnection client) { if (client.updateComponent(this, uidl, true)) { return; } this.client = client; uidlId = uidl.getId(); /* * we need to keep tabs on whether the component is disabled or not. We * get warnings if sending variable changes from disabled widgets. */ if (uidl.hasAttribute(VAADIN_DISABLED__BOOLEAN)) { disabled = uidl.getBooleanAttribute(VAADIN_DISABLED__BOOLEAN); } else { disabled = false; } if (uidl.hasAttribute(ATTRIBUTE_DEFAULTCAPTION__STRING)) { defaultCaption = uidl.getStringAttribute(ATTRIBUTE_DEFAULTCAPTION__STRING); updateCaptionInternal(); } if (uidl.hasAttribute(ATTRIBUTE_ANIMATION_DURATION__INT)) { animationDuration = uidl.getIntAttribute(ATTRIBUTE_ANIMATION_DURATION__INT); } if (uidl.hasAttribute(ATTRIBUTE_DRAWER_HEIGHT__INT)) { drawerHeight = uidl.getIntAttribute(ATTRIBUTE_DRAWER_HEIGHT__INT); if (drawerHeight == -1) { calculateDrawerHeight = true; adjustForAutoDimensions(); } else { calculateDrawerHeight = false; } } if (uidl.hasAttribute(ATTRIBUTE_CALCULATE_HEADER_WIDTH__BOOLEAN)) { calculateCaptionWidth = uidl.getBooleanAttribute(ATTRIBUTE_CALCULATE_HEADER_WIDTH__BOOLEAN); } // get the drawer Component if (uidl.getChildCount() > 0) { final UIDL drawerUIDL = uidl.getChildUIDL(0); final Paintable paintable = client.getPaintable(drawerUIDL); final Widget widgetCopyOfPaintable = (Widget) paintable; if (widgetCopyOfPaintable != drawer.getWidget()) { if (drawer.getWidget() == loading) { // if it's the initial component, just remove it. drawer.clear(); drawer.setHeight("0px"); } final Paintable oldDrawerContent = (Paintable) drawer.getWidget(); drawer.setWidget(widgetCopyOfPaintable); if (oldDrawerContent != null) { client.unregisterPaintable(oldDrawerContent); } final boolean wasVisible = isDrawerVisible(); setDrawerVisible(true); paintable.updateFromUIDL(drawerUIDL, client); setDrawerVisible(wasVisible); if (calculateDrawerHeight) { /* * Since we have a new component, let's recalculate its * size, and do the appropriate animagication. */ drawerHeight = -1; adjustForAutoDimensions(); } drawerHasContents = uidl.getBooleanAttribute(ATTRIBUTE_DRAWER_HAS_CONTENT__BOOLEAN); if (drawerHasContents) { /* * Since Vaadin doesn't notify the initial caption of a * component, we need to do dig it out manually. */ updateCaption(paintable, drawerUIDL); } else { clearComponentCaption(); captionIcon.setExpanded(false); } } } /* * This needs to be before the Component attaching step or we're trying * to calculate dimensions with an invisible Widget. That's not what we * prefer to do. */ if (uidl.hasVariable(VARIABLE_DRAWERVISIBLE__BOOLEAN)) { if (drawerHasContents) { animate = uidl.getBooleanAttribute(ATTRIBUTE_ANIMATE__BOOLEAN); final boolean contentIsVisible = uidl.getBooleanVariable(VARIABLE_DRAWERVISIBLE__BOOLEAN); if (animate) { DeferredCommand.addCommand(new Command() { public void execute() { new DrawerAnimation(contentIsVisible).run(animationDuration); } }); } else { setDrawerVisible(contentIsVisible); /* * when the page is refreshed, make sure that we have the * right height for the drawer */ if (contentIsVisible) { drawer.setHeight(drawerHeight + "px"); } else { drawer.setHeight("0px"); } } captionIcon.setExpanded(contentIsVisible); } else { drawer.setHeight("0px"); setDrawerVisible(false); captionIcon.setExpanded(false); } } // Util.notifyParentOfSizeChange(this, false); // hacky fix for the drawer area being visible upon first render updateCount++; if (updateCount == 1 && !isDrawerVisible()) { drawer.setHeight("0px"); } } private void toggleDrawerVisibility() { if (uidlId != null && client != null && isAttached() && !disabled) { client.updateVariable(uidlId, VARIABLE_DRAWERVISIBLE__BOOLEAN, !isDrawerVisible(), true); } } /** * <p> * Fix the drawer's height. * </p> * * <p> * This method will both calculate the needed drawer's dimensions, but also * animate the size change if the drawer happened to be visible. * </p> */ private void adjustForAutoDimensions() { if (isAttached() && drawer.getWidget() != null) { if (calculateDrawerHeight) { /* * The height needs to be calculated, so that the open/close * animations know how far to go. */ if (drawerHeight == -1) { final boolean wasVisible = isDrawerVisible(); setDrawerVisible(true); drawerHeight = Util.getRequiredHeight(drawer.getWidget()); setDrawerVisible(wasVisible); } if (isDrawerVisible()) { final int toHeight = drawer.getWidget().getOffsetHeight(); final int toWidth = drawer.getWidget().getOffsetWidth(); if (animate) { final int fromHeight = drawer.getOffsetHeight(); final int fromWidth = drawer.getOffsetWidth(); new Animation() { @Override protected void onUpdate(final double progress) { final long grownHeight = Math.round((toHeight - fromHeight) * progress); drawer.setHeight((fromHeight + grownHeight) + "px"); final long grownWidth = Math.round((toWidth - fromWidth) * progress); drawer.setWidth((fromWidth + grownWidth) + "px"); Util.notifyParentOfSizeChange(VDrawer.this, false); } }.run(animationDuration / 2); } else { drawer.setHeight(toHeight + "px"); drawer.setWidth(toWidth + "px"); } } } if (calculateCaptionWidth) { /* * The width of the header needs to be calculated, if the * component has no explicit width, or the header width will not * be the same when the drawer is visible and when it's hidden. */ final boolean wasVisible = isDrawerVisible(); setDrawerVisible(true); captionPanel.setWidth(drawer.getWidget().getOffsetWidth() + "px"); setDrawerVisible(wasVisible); } } } public RenderSpace getAllocatedSpace(final Widget child) { final int childHeight = child.getOffsetHeight(); final int childWidth = child.getOffsetWidth(); final int myWidth = getOffsetWidth(); // final int myHeight = getOffsetHeight(); final int captionWidth = captionPanel.getOffsetWidth(); final int finalHeight; final int finalWidth; if (calculateCaptionWidth) { finalWidth = Math.max(childWidth, Math.max(myWidth, captionWidth)); } else { finalWidth = captionWidth; } if (calculateDrawerHeight) { finalHeight = Math.max(childHeight, drawerHeight); } else { finalHeight = drawerHeight; } return new RenderSpace(finalWidth, finalHeight); } public boolean hasChildComponent(final Widget component) { return component.getParent() == drawer; } public void replaceChildComponent(final Widget oldComponent, final Widget newComponent) { if (oldComponent == drawer.getWidget()) { drawer.setWidget(newComponent); } } public boolean requestLayout(final Set<Paintable> children) { if (isDrawerVisible()) { if (calculateDrawerHeight) { final int requiredHeight = Util.getRequiredHeight(drawer.getWidget()); drawer.setHeight(requiredHeight + "px"); } if (calculateCaptionWidth) { final int requiredWidth = Util.getRequiredWidth(drawer.getWidget()); captionPanel.setWidth(requiredWidth + "px"); } return false; } else { return true; } } public void updateCaption(final Paintable component, final UIDL uidl) { /* * Since the caption panel doubles as the component caption, this needs * some elbow grease. */ if (component == drawer.getWidget()) { if (uidl.hasAttribute(VAADIN_CAPTION_UIDL_INVISIBLE__BOOLEAN)) { componentCaptionIsVisible = !uidl.getBooleanAttribute(VAADIN_CAPTION_UIDL_INVISIBLE__BOOLEAN); } if (uidl.hasAttribute(VAADIN_CAPTION_UIDL_TEXT__STRING)) { componentCaption = uidl.getStringAttribute(VAADIN_CAPTION_UIDL_TEXT__STRING); } updateCaptionInternal(); } } private void updateCaptionInternal() { if (componentCaptionIsVisible && componentCaption != null) { captionLabel.setText(componentCaption); } else { captionLabel.setText(defaultCaption); } } /** * <p> * A means to clear the component's explicit caption. * </p> * <p> * This needs to be called manually, because Vaadin doesn't call * {@link #updateCaption(Paintable, UIDL)} on its own. A bummer, really. */ private void clearComponentCaption() { componentCaption = null; componentCaptionIsVisible = false; updateCaptionInternal(); } private boolean isDrawerVisible() { return !drawer.getElement().getStyle().getProperty("visibility").equals("hidden"); } private void setDrawerVisible(final boolean visible) { drawer.getElement().getStyle().setProperty("visibility", visible ? "visible" : "hidden"); } public void add(final Widget w) { drawer.add(w); } public void clear() { drawer.clear(); } public Iterator<Widget> iterator() { return drawer.iterator(); } public boolean remove(final Widget w) { return drawer.remove(w); } @Override public void setWidth(final String width) { super.setWidth(width); drawer.setWidth(width); captionPanel.setWidth(width); } }