Java tutorial
/* * Copyright 2008 Google Inc. * * 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 com.google.gwt.gen2.complexpanel.client; 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.EventHandler; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.gen2.widgetbase.client.Gen2CssInjector; 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.Timer; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.ToggleButton; import com.google.gwt.user.client.ui.Widget; import java.util.Iterator; /** * {@link CollapsiblePanel} is intended to be used as a navigation panel with * the ability to collapse itself. * * The {@link CollapsiblePanel} will collapse itself to the left in LTR mode and * to the right in RTL mode. * * When collapsed, the contents of the panel will be displayed only when the * user mouse hovers over the hover bar, otherwise is will stay collapsed to the * left. * <p> * * The user must specify a fixed width and height either programatically or via * css that must already be fully loaded before the panel is attached. * * <dl> * <dd>.gwt-CollapsiblePanel</dd> * <dt>the collapsible panel</dt> * <dd>.CollapsibleHoverBox</dd> * <dt>The hover bar that appears when the widget is collapsed</dt> * <dd>.CollapsibleContainer</dd> * <dt>The container for the panel's content</dt> * </dl> */ public class CollapsiblePanel extends Composite implements HasWidgets { /** * Event fired after the panel shown in its collapsed state. */ public static class CollapsedStateEvent extends GwtEvent<CollapsedStateHandler> { static Type<CollapsedStateHandler> type = new Type<CollapsedStateHandler>();; @Override public com.google.gwt.event.shared.GwtEvent.Type<CollapsedStateHandler> getAssociatedType() { return type; } @Override protected void dispatch(CollapsedStateHandler handler) { handler.onCollapsedState(this); } } /** * Handler for {@link CollapsedStateEvent} events. */ public static interface CollapsedStateHandler extends EventHandler { void onCollapsedState(CollapsedStateEvent e); } /** * Event fired after the panel is shown in a expanded state. */ public static class ExpandedStateEvent extends GwtEvent<ExpandedStateHandler> { static Type<ExpandedStateHandler> type = new Type<ExpandedStateHandler>();; @Override public com.google.gwt.event.shared.GwtEvent.Type<ExpandedStateHandler> getAssociatedType() { return type; } @Override protected void dispatch(ExpandedStateHandler handler) { handler.onExpandedState(this); } } /** * Handler for {@link ExpandedStateEvent} events. */ public static interface ExpandedStateHandler extends EventHandler { void onExpandedState(ExpandedStateEvent e); } /** * Current {@link CollapsiblePanel} state. */ enum State { WILL_HIDE, HIDING, IS_HIDDEN, WILL_SHOW, SHOWING, IS_SHOWN, EXPANDED; public static void checkTo(State from, State to) { boolean valid = check(from, to); if (!valid) { throw new IllegalStateException(from + " -> " + to + " is an illegal state transition"); } } private static boolean check(State from, State to) { if (to == EXPANDED || from == null) { return true; } switch (from) { case WILL_HIDE: return (to == IS_SHOWN || to == HIDING); case HIDING: return (to == IS_HIDDEN || to == SHOWING); case IS_HIDDEN: return (to == WILL_SHOW); case WILL_SHOW: return (to == SHOWING || to == IS_HIDDEN); case SHOWING: return (to == IS_SHOWN || to == HIDING); case IS_SHOWN: return to == WILL_HIDE; case EXPANDED: return to == HIDING; default: throw new IllegalStateException("Unknown state"); } } public boolean shouldHide() { return this == SHOWING || this == IS_SHOWN; } public boolean shouldShow() { return this == HIDING || this == IS_HIDDEN; } } /** * {@link CollapsiblePanel} styles. * */ static class Styles { static String DEFAULT = "gwt-CollapsiblePanel"; static String CONTAINER = "CollapsibleContainer"; static String HOVER_BAR = "CollapsibleHoverBar"; } /** * Delays showing of the {@link CollapsiblePanel}. */ private class DelayHide extends Timer { public void activate() { setState(State.WILL_HIDE); delayedHide.schedule(getDelayBeforeHide()); } @Override public void run() { hide(); } } /** * Delays showing of the {@link CollapsiblePanel}. */ private class DelayShow extends Timer { public void activate() { setState(State.WILL_SHOW); delayedShow.schedule(getDelayBeforeShow()); } @Override public void run() { show(); } } /** * Injects the default css for the collapsible panel. */ public static void injectDefaultCss() { Gen2CssInjector.addCollapsiblePanelDefault(); } private int hoverBarWidth; /** * Number of intervals used to display panel. */ private int timeToSlide = 150; /** * How many milliseconds to delay a hover event before executing it. */ private int delayBeforeShow = 300; /** * How many milliseconds to delay a hide event before executing it. */ private int delayBeforeHide = 200; /** * The internal state of the Collapsible panel. */ private State state; /** * Showing the panel bar sliding in. */ private Animation hidingTimer = new Animation() { @Override public void onCancel() { // Override default behavior to do nothing instead } @Override public void onComplete() { container.completeHide(); setState(State.IS_HIDDEN); } @Override public void onUpdate(double progress) { container.updateHide(progress); } }; /** * Showing the panel sliding out. */ private Animation showingTimer = new Animation() { @Override public void onCancel() { // Override default behavior to do nothing instead } @Override public void onComplete() { container.completeShow(); setState(State.IS_SHOWN); } @Override public void onUpdate(double progress) { container.updateShow(progress); } }; /** * How long to delay the showing of the panel. */ private DelayShow delayedShow = new DelayShow(); /** * How long to delay the hiding of the panel. */ private DelayHide delayedHide = new DelayHide(); // Collapsible panel structure private SliderPanelImpl container; private SimplePanel hoverBar; private ToggleButton collapseToggle; private BiDiRelativePanel master; private boolean hasContents; /** * Create a new collapsible panel. */ public CollapsiblePanel() { // Create the composite widget. master = new BiDiRelativePanel() { { sinkEvents(Event.ONMOUSEOUT | Event.ONMOUSEOVER); } @Override public void onBrowserEvent(Event event) { // Cannot handle browser events until contents are initialized. if (!hasContents) { return; } if (isCollapsed()) { switch (DOM.eventGetType(event)) { case Event.ONMOUSEOUT: Element to = DOM.eventGetToElement(event); if (to == null && state == State.SHOWING) { // Linux hosted mode hack. return; } if (to != null && DOM.isOrHasChild(master.getElement(), to)) { break; } switch (state) { case WILL_SHOW: setState(State.IS_HIDDEN); delayedShow.cancel(); break; case SHOWING: hide(); break; case IS_SHOWN: delayedHide.activate(); break; } break; case Event.ONMOUSEOVER: if (state == State.WILL_HIDE) { setState(State.IS_SHOWN); delayedHide.cancel(); } break; } } super.onBrowserEvent(event); } }; master.getElement().getStyle().setProperty("overflow", "visible"); initWidget(master); setStyleName(Styles.DEFAULT); // Create hovering container. // Create the composite widget. hoverBar = new SimplePanel() { { sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEDOWN); } @Override public void onBrowserEvent(Event event) { // Cannot handle browser events until contents are initialized. if (!hasContents) { return; } if (isCollapsed()) { switch (DOM.eventGetType(event)) { case Event.ONMOUSEOVER: switch (state) { case HIDING: show(); break; case IS_HIDDEN: delayedShow.activate(); break; } break; case Event.ONMOUSEDOWN: setCollapsedState(false, true); } } } }; // Hover bar. hoverBar.setStyleName(Styles.HOVER_BAR); master.addAbsolute(hoverBar, 0, 0); // Actual slider panel. container = new SliderPanelImpl(); master.addAbsolute(container, 0, 0); setState(State.EXPANDED); } /** * Creates a new collapsible panel. * * @param contents the contents to be shown in the panel */ public CollapsiblePanel(Widget contents) { this(); initContents(contents); } /** * Adds the content to this panel. May only be called once. */ public void add(Widget w) { initContents(w); } /** * Add handler which is called when the widget is shown in its collapsed * state. * * @param handler the handler */ public HandlerRegistration addCollapsedStateHandler(CollapsedStateHandler handler) { return addHandler(handler, CollapsedStateEvent.type); } /** * Adds a handler to be shown when the panel is in its expanded state. * * @param handler handler */ public void addExpandedStateHandler(ExpandedStateHandler handler) { addHandler(handler, ExpandedStateEvent.type); } public void clear() { throw new IllegalStateException("Collapsible Panel cannot be cleared once initialized"); } public Widget getContents() { return container.getWidget(); } /** * Hides the panel when the panel is the collapsible state. Does nothing if * the panel is expanded or not attached. */ public final void hide() { if (getState() != State.EXPANDED && isAttached()) { hiding(); } } /** * Uses the given toggle button to control whether the panel is collapsed or * not. Note, does not move the control toggle, so it is up to the user to * place the toggle in the desired location. */ public void hookupControlToggle(ToggleButton button) { this.collapseToggle = button; collapseToggle.setDown(!this.isCollapsed()); collapseToggle.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { setCollapsedState(!collapseToggle.isDown(), true); } }); } /** * Is the panel currently in its collapsed state. */ public boolean isCollapsed() { return getState() != State.EXPANDED; } public Iterator<Widget> iterator() { return master.iterator(); } public boolean remove(Widget w) { return false; } /** * Sets the state of the collapsible panel. * * @param collapsed is the panel collapsed? */ public final void setCollapsedState(boolean collapsed) { setCollapsedState(collapsed, false); } /** * Sets the state of the collapsible panel. * * @param collapsed is the panel collapsed? * @param fireEvents should the change listeners be fired? */ public final void setCollapsedState(boolean collapsed, boolean fireEvents) { if (isCollapsed() == collapsed) { return; } if (collapseToggle != null) { collapseToggle.setDown(!collapsed); } if (collapsed) { becomeCollapsed(); } else { becomeExpanded(); } if (fireEvents) { if (collapsed) { fireEvent(new CollapsedStateEvent()); } else { fireEvent(new ExpandedStateEvent()); } } } /** * Sets the delay before the collapsible panel hides the panel after the user * leaves the panel. * * @param delayBeforeHide the delayBeforeHide to set */ public void setDelayBeforeHide(int delayBeforeHide) { this.delayBeforeHide = delayBeforeHide; } /** * Set delay before showing the panel after the user has hovered over the * hover bar. * * @param delayBeforeShow the delayBeforeShow to set */ public void setDelayBeforeShow(int delayBeforeShow) { this.delayBeforeShow = delayBeforeShow; } @Override public void setHeight(String height) { container.setHeight(height); computeSize(); } /** * Sets the contents of the hover bar. */ public void setHoverBarContents(Widget bar) { hoverBar.setWidget(bar); computeSize(); } /** * Sets the hover bar's width. */ public void setHoverBarWidth(String width) { hoverBar.setWidth(width); computeSize(); } /** * Sets the time to slide the panel out. * * @param timeToSlide the timeToSlide to set */ public void setTimeToSlide(int timeToSlide) { this.timeToSlide = timeToSlide; } @Override public void setVisible(boolean isVisible) { super.setVisible(isVisible); if (isVisible) { computeSize(); } } @Override public void setWidth(String width) { container.setWidth(width); computeSize(); } /** * Shows the panel if the panel is in a collapsible state. Does nothing if the * panel is expanded or not attached. * * Note: this method should only be called if the user's mouse will be over * the panel's contents after show() is executed. */ public void show() { if (getState() != State.EXPANDED && isAttached()) { cancelAllTimers(); setState(State.SHOWING); container.startShow(); showingTimer.run(this.getTimeToSlide()); } } /** * This method is called immediately after a widget becomes attached to the * browser's document. */ @Override protected void onLoad() { computeSize(); } /** * Visible for testing. * * @return the hover bar component */ SimplePanel getHoverBar() { return hoverBar; } /** * Visible for testing. Note that the state is completely internal to the * implementation. * * @return the current state */ State getState() { return state; } private void adjustmentsForCollapsedState() { master.setHeight(container.getOffsetHeight() + "px"); // IE RTL bug create a phantom scrollbar without the +1 master.setWidth(hoverBarWidth + 1 + "px"); master.setAbsolutePosition(container, hoverBarWidth / 2, 0); } private void adjustmentsForExpandedState() { container.completeShow(); master.setAbsolutePosition(container, 0, 0); master.setWidth(container.getOffsetWidth() + "px"); } /** * Display this panel in its collapsed state. The panel's contents will be * hidden and only the hover var will be visible. */ private void becomeCollapsed() { cancelAllTimers(); // Now hide. if (isAttached()) { adjustmentsForCollapsedState(); hiding(); } else { // onLoad will ensure this is correctly displayed. Here we just ensure // that the panel is the correct final hidden state. Forcing the state to // is hidden regardless of what is was. state = State.IS_HIDDEN; } } /** * Display this panel in its expanded state. The panel's contents will be * fully visible and take up all required space. */ private void becomeExpanded() { cancelAllTimers(); // The master width needs to be readjusted back to it's original size. if (isAttached()) { adjustmentsForExpandedState(); } setState(State.EXPANDED); } private void cancelAllTimers() { delayedHide.cancel(); delayedShow.cancel(); showingTimer.cancel(); hidingTimer.cancel(); } private void computeSize() { if (!(isAttached() && isVisible() && hasContents)) { return; } // We need this + 1 to avoid triggering on IE a phantom right scrollbar when // switching between collapsed and expanded states. hoverBarWidth = hoverBar.getOffsetWidth(); if (hoverBarWidth <= 1) { throw new IllegalStateException( "The underlying hover bar width is not set. Please ensure that the .CollapsibleHoverBar css style has a fixed width"); } container.computeSize(); master.setHeight(container.getOffsetHeight() + "px"); hoverBar.setHeight(container.getOffsetHeight() + "px"); if (getState() == State.EXPANDED) { adjustmentsForExpandedState(); } else { cancelAllTimers(); adjustmentsForCollapsedState(); container.completeHide(); state = State.IS_HIDDEN; } } /** * Gets the delay before hiding. * * @return the delayBeforeHide */ private int getDelayBeforeHide() { return delayBeforeHide; } /** * Gets the delay before showing the panel. * * @return the delayBeforeShow */ private int getDelayBeforeShow() { return delayBeforeShow; } /** * Gets the time to slide the panel out. * * @return the timeToSlide */ private int getTimeToSlide() { return timeToSlide; } private void hiding() { assert (isAttached()); cancelAllTimers(); setState(State.HIDING); container.startHide(); hidingTimer.run(timeToSlide); } /** * Initialize the panel's contents. * * @param contents contents */ private void initContents(Widget contents) { if (hasContents) { throw new IllegalStateException("Contents have already be set"); } container.add(contents); hasContents = true; computeSize(); } private void setState(State state) { // checks are assuming animation. State.checkTo(this.state, state); this.state = state; } }