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.user.client.ui; import com.google.gwt.dom.client.Element; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.annotations.IsSafeHtml; import com.google.gwt.safehtml.shared.annotations.SuppressIsSafeHtmlCastCheck; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import cc.alcina.framework.common.client.util.Ax; /** * A panel that stacks its children vertically, displaying only one at a time, * with a header for each child which the user can click to display. * * <p> * This widget will <em>only</em> work in quirks mode. If your application is in * Standards Mode, use {@link StackLayoutPanel} instead. * </p> * * <p> * <img class='gallery' src='doc-files/StackPanel.png'/> * </p> * <h3>CSS Style Rules</h3> * <ul class='css'> * <li>.gwt-StackPanel { the panel itself }</li> * <li>.gwt-StackPanel .gwt-StackPanelItem { unselected items }</li> * <li>.gwt-StackPanel .gwt-StackPanelItem-selected { selected items }</li> * <li>.gwt-StackPanel .gwt-StackPanelContent { the wrapper around the contents * of the item }</li> * </ul> * <p> * <h3>Example</h3> {@example com.google.gwt.examples.StackPanelExample} * </p> * * @see StackLayoutPanel */ public class StackPanel extends ComplexPanel implements InsertPanel.ForIsWidget { private static final String DEFAULT_STYLENAME = "gwt-StackPanel"; private static final String DEFAULT_ITEM_STYLENAME = DEFAULT_STYLENAME + "Item"; private Element body; private int visibleStack = -1; /** * Creates an empty stack panel. */ public StackPanel() { Element table = DOM.createTable(); setElement(table); body = DOM.createTBody(); DOM.appendChild(table, body); table.setPropertyInt("cellSpacing", 0); table.setPropertyInt("cellPadding", 0); DOM.sinkEvents(table, Event.ONCLICK); setStyleName(DEFAULT_STYLENAME); } @Override public void add(Widget w) { insert(w, getWidgetCount()); } /** * Adds a new child with the given widget and header, optionally * interpreting the header as HTML. * * @param w * the widget to be added * @param stackHtml * the header html associated with this widget */ public void add(Widget w, SafeHtml stackHtml) { add(w, stackHtml.asString(), true); } /** * Adds a new child with the given widget and header. * * @param w * the widget to be added * @param stackText * the header text associated with this widget */ @SuppressIsSafeHtmlCastCheck public void add(Widget w, String stackText) { add(w, stackText, false); } /** * Adds a new child with the given widget and header, optionally * interpreting the header as HTML. * * @param w * the widget to be added * @param stackText * the header text associated with this widget * @param asHTML * <code>true</code> to treat the specified text as HTML */ public void add(Widget w, @IsSafeHtml String stackText, boolean asHTML) { add(w); setStackText(getWidgetCount() - 1, stackText, asHTML); } /** * Adds the {@code styleName} on the {@code * <tr> * } for the header specified by {@code index}. * * @param index * the index of the header row to apply to the style to * @param styleName * the name of the class to add */ public void addHeaderStyleName(int index, String styleName) { if (index >= getWidgetCount()) { return; } Element tr = DOM.getChild(body, index * 2); setStyleName(tr, styleName, true /* add */); } /** * Gets the currently selected child index. * * @return selected child */ public int getSelectedIndex() { return visibleStack; } public void insert(IsWidget w, int beforeIndex) { insert(asWidgetOrNull(w), beforeIndex); } public void insert(Widget w, int beforeIndex) { // header Element trh = DOM.createTR(); Element tdh = DOM.createTD(); DOM.appendChild(trh, tdh); DOM.appendChild(tdh, createHeaderElem()); // body Element trb = DOM.createTR(); Element tdb = DOM.createTD(); DOM.appendChild(trb, tdb); // DOM indices are 2x logical indices; 2 dom elements per stack item beforeIndex = adjustIndex(w, beforeIndex); int effectiveIndex = beforeIndex * 2; // this ordering puts the body below the header DOM.insertChild(body, trb, effectiveIndex); DOM.insertChild(body, trh, effectiveIndex); // header styling setStyleName(tdh, DEFAULT_ITEM_STYLENAME, true); tdh.setAttribute("__owner", String.valueOf(hashCode())); tdh.setPropertyString("height", "1px"); // body styling setStyleName(tdb, DEFAULT_STYLENAME + "Content", true); tdb.setPropertyString("height", "100%"); tdb.setPropertyString("vAlign", "top"); // Now that the DOM is connected, call insert (this ensures that // onLoad() is // not fired until the child widget is attached to the DOM). insert(w, tdb, beforeIndex, false); // Update indices of all elements to the right. updateIndicesFrom(beforeIndex); // Correct visible stack for new location. if (visibleStack == -1) { showStack(0); } else { setStackVisible(beforeIndex, false); if (visibleStack >= beforeIndex) { ++visibleStack; } // Reshow the stack to apply style names setStackVisible(visibleStack, true); } } @Override public void onBrowserEvent(Event event) { if (DOM.eventGetType(event) == Event.ONCLICK) { Element target = DOM.eventGetTarget(event); int index = findDividerIndex(target); if (index != -1) { showStack(index); } } super.onBrowserEvent(event); } @Override public boolean remove(int index) { return remove(getWidget(index), index); } @Override public boolean remove(Widget child) { return remove(child, getWidgetIndex(child)); } /** * Removes the {@code styleName} off the {@code * <tr> * } for the header specified by {@code index}. * * @param index * the index of the header row to remove the style from * @param styleName * the name of the class to remove */ public void removeHeaderStyleName(int index, String styleName) { if (index >= getWidgetCount()) { return; } Element tr = DOM.getChild(body, index * 2); setStyleName(tr, styleName, false /* remove */); } /** * Sets the html associated with a child by its index. * * @param index * the index of the child whose text is to be set * @param html * the html to be associated with it */ public void setStackText(int index, SafeHtml html) { setStackText(index, html.asString(), true); } /** * Sets the text associated with a child by its index. * * @param index * the index of the child whose text is to be set * @param text * the text to be associated with it */ @SuppressIsSafeHtmlCastCheck public void setStackText(int index, String text) { setStackText(index, text, false); } /** * Sets the text associated with a child by its index. * * @param index * the index of the child whose text is to be set * @param text * the text to be associated with it * @param asHTML * <code>true</code> to treat the specified text as HTML */ public void setStackText(int index, @IsSafeHtml String text, boolean asHTML) { if (index >= getWidgetCount()) { return; } Element tdWrapper = DOM.getChild(DOM.getChild(body, index * 2), 0); Element headerElem = DOM.getFirstChild(tdWrapper); if (asHTML) { getHeaderTextElem(headerElem).setInnerHTML(text); } else { getHeaderTextElem(headerElem).setInnerText(text); } } /** * Shows the widget at the specified child index. * * @param index * the index of the child to be shown */ public void showStack(int index) { if ((index >= getWidgetCount()) || (index < 0) || (index == visibleStack)) { return; } if (visibleStack >= 0) { setStackVisible(visibleStack, false); } visibleStack = index; setStackVisible(visibleStack, true); } private int findDividerIndex(Element elem) { while (elem != null && elem != getElement()) { Ax.out("find divider index: %s %s", elem.getTagName(), elem.hashCode()); String expando = elem.getAttribute("__index"); if (!Ax.isBlank(expando)) { // Make sure it belongs to me! String ownerHashAttr = elem.getAttribute("__owner"); int ownerHash = Ax.isBlank(ownerHashAttr) ? -1 : Integer.parseInt(ownerHashAttr); if (ownerHash == hashCode()) { // Yes, it's mine. return Integer.parseInt(expando); } else { // It must belong to some nested StackPanel. return -1; } } elem = DOM.getParent(elem); } return -1; } private boolean remove(Widget child, int index) { // Make sure to call this before disconnecting the DOM. boolean removed = super.remove(child); if (removed) { // Calculate which internal table elements to remove. int rowIndex = 2 * index; Element tr = DOM.getChild(body, rowIndex); body.removeChild(tr); tr = DOM.getChild(body, rowIndex); body.removeChild(tr); // Correct visible stack for new location. if (visibleStack == index) { visibleStack = -1; } else if (visibleStack > index) { --visibleStack; } // Update indices of all elements to the right. updateIndicesFrom(index); } return removed; } private void setStackContentVisible(int index, boolean visible) { Element tr = DOM.getChild(body, (index * 2) + 1); UIObject.setVisible(tr, visible); getWidget(index).setVisible(visible); } private void setStackVisible(int index, boolean visible) { // Get the first table row containing the widget's selector item. Element tr = DOM.getChild(body, (index * 2)); if (tr == null) { return; } // Style the stack selector item. Element td = DOM.getFirstChild(tr); setStyleName(td, DEFAULT_ITEM_STYLENAME + "-selected", visible); // Show/hide the contained widget. setStackContentVisible(index, visible); // Set the style of the next header Element trNext = DOM.getChild(body, ((index + 1) * 2)); if (trNext != null) { Element tdNext = DOM.getFirstChild(trNext); setStyleName(tdNext, DEFAULT_ITEM_STYLENAME + "-below-selected", visible); } } private void updateIndicesFrom(int beforeIndex) { for (int i = beforeIndex, c = getWidgetCount(); i < c; ++i) { Element childTR = DOM.getChild(body, i * 2); Element childTD = DOM.getFirstChild(childTR); childTD.setAttribute("__index", String.valueOf(i)); Ax.out("update child index: %s - tr %s", childTD.hashCode(), childTR.hashCode()); // Update the special style on the first element if (beforeIndex == 0) { setStyleName(childTD, DEFAULT_ITEM_STYLENAME + "-first", true); } else { setStyleName(childTD, DEFAULT_ITEM_STYLENAME + "-first", false); } } } /** * <b>Affected Elements:</b> * <ul> * <li>-text# = The element around the header at the specified index.</li> * <li>-text-wrapper# = The element around the header at the specified * index.</li> * <li>-content# = The element around the body at the specified index.</li> * </ul> * * @see UIObject#onEnsureDebugId(String) */ @Override protected void onEnsureDebugId(String baseID) { super.onEnsureDebugId(baseID); int numHeaders = DOM.getChildCount(body) >> 1; for (int i = 0; i < numHeaders; i++) { Element tdWrapper = DOM.getFirstChild(DOM.getChild(body, 2 * i)); Element headerElem = DOM.getFirstChild(tdWrapper); Element bodyElem = DOM.getFirstChild(DOM.getChild(body, 2 * i + 1)); ensureDebugId(tdWrapper, baseID, "text-wrapper" + i); ensureDebugId(bodyElem, baseID, "content" + i); ensureDebugId(getHeaderTextElem(headerElem), baseID, "text" + i); } } /** * Returns a header element. */ Element createHeaderElem() { return DOM.createDiv(); } /** * Get the element that holds the header text given the header element * created by #createHeaderElement. * * @param headerElem * the header element * @return the element around the header text */ Element getHeaderTextElem(Element headerElem) { return headerElem; } }