001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.user.client.DOM;
019: import com.google.gwt.user.client.Element;
020: import com.google.gwt.user.client.Event;
021:
022: /**
023: * A panel that stacks its children vertically, displaying only one at a time,
024: * with a header for each child which the user can click to display.
025: * <p>
026: * <img class='gallery' src='StackPanel.png'/>
027: * </p>
028: * <h3>CSS Style Rules</h3>
029: * <ul class='css'>
030: * <li>.gwt-StackPanel { the panel itself }</li>
031: * <li>.gwt-StackPanel .gwt-StackPanelItem { unselected items }</li>
032: * <li>.gwt-StackPanel .gwt-StackPanelItem-selected { selected items }</li>
033: * <li>.gwt-StackPanel .gwt-StackPanelContent { the wrapper around the contents
034: * of the item }</li>
035: * </ul>
036: * <p>
037: * <h3>Example</h3>
038: * {@example com.google.gwt.examples.StackPanelExample}
039: * </p>
040: */
041: public class StackPanel extends ComplexPanel {
042:
043: private Element body;
044: private int visibleStack = -1;
045:
046: /**
047: * Creates an empty stack panel.
048: */
049: public StackPanel() {
050: Element table = DOM.createTable();
051: setElement(table);
052:
053: body = DOM.createTBody();
054: DOM.appendChild(table, body);
055: DOM.setElementPropertyInt(table, "cellSpacing", 0);
056: DOM.setElementPropertyInt(table, "cellPadding", 0);
057:
058: DOM.sinkEvents(table, Event.ONCLICK);
059: setStyleName("gwt-StackPanel");
060: }
061:
062: /**
063: * Adds a new child with the given widget.
064: *
065: * @param w the widget to be added
066: */
067: @Override
068: public void add(Widget w) {
069: insert(w, getWidgetCount());
070: }
071:
072: /**
073: * Adds a new child with the given widget and header.
074: *
075: * @param w the widget to be added
076: * @param stackText the header text associated with this widget
077: */
078: public void add(Widget w, String stackText) {
079: add(w, stackText, false);
080: }
081:
082: /**
083: * Adds a new child with the given widget and header, optionally interpreting
084: * the header as HTML.
085: *
086: * @param w the widget to be added
087: * @param stackText the header text associated with this widget
088: * @param asHTML <code>true</code> to treat the specified text as HTML
089: */
090: public void add(Widget w, String stackText, boolean asHTML) {
091: add(w);
092: setStackText(getWidgetCount() - 1, stackText, asHTML);
093: }
094:
095: /**
096: * Gets the currently selected child index.
097: *
098: * @return selected child
099: */
100: public int getSelectedIndex() {
101: return visibleStack;
102: }
103:
104: /**
105: * Inserts a widget before the specified index.
106: *
107: * @param w the widget to be inserted
108: * @param beforeIndex the index before which it will be inserted
109: * @throws IndexOutOfBoundsException if <code>beforeIndex</code> is out of
110: * range
111: */
112: public void insert(Widget w, int beforeIndex) {
113: // header
114: Element trh = DOM.createTR();
115: Element tdh = DOM.createTD();
116: DOM.appendChild(trh, tdh);
117:
118: // body
119: Element trb = DOM.createTR();
120: Element tdb = DOM.createTD();
121: DOM.appendChild(trb, tdb);
122:
123: // DOM indices are 2x logical indices; 2 dom elements per stack item
124: beforeIndex = adjustIndex(w, beforeIndex);
125: int effectiveIndex = beforeIndex * 2;
126:
127: // this ordering puts the body below the header
128: DOM.insertChild(body, trb, effectiveIndex);
129: DOM.insertChild(body, trh, effectiveIndex);
130:
131: // header styling
132: setStyleName(tdh, "gwt-StackPanelItem", true);
133: DOM.setElementPropertyInt(tdh, "__owner", hashCode());
134: DOM.setElementProperty(tdh, "height", "1px");
135:
136: // body styling
137: setStyleName(tdb, "gwt-StackPanelContent", true);
138: DOM.setElementProperty(tdb, "height", "100%");
139: DOM.setElementProperty(tdb, "vAlign", "top");
140:
141: // Now that the DOM is connected, call insert (this ensures that onLoad() is
142: // not fired until the child widget is attached to the DOM).
143: super .insert(w, tdb, beforeIndex, false);
144:
145: // Update indices of all elements to the right.
146: updateIndicesFrom(beforeIndex);
147:
148: // Correct visible stack for new location.
149: if (visibleStack == -1) {
150: showStack(0);
151: } else {
152: setStackVisible(beforeIndex, false);
153: if (visibleStack >= beforeIndex) {
154: ++visibleStack;
155: }
156: }
157: }
158:
159: @Override
160: public void onBrowserEvent(Event event) {
161: if (DOM.eventGetType(event) == Event.ONCLICK) {
162: Element target = DOM.eventGetTarget(event);
163: int index = findDividerIndex(target);
164: if (index != -1) {
165: showStack(index);
166: }
167: }
168: }
169:
170: @Override
171: public boolean remove(int index) {
172: return remove(getWidget(index), index);
173: }
174:
175: @Override
176: public boolean remove(Widget child) {
177: return remove(child, getWidgetIndex(child));
178: }
179:
180: /**
181: * Sets the text associated with a child by its index.
182: *
183: * @param index the index of the child whose text is to be set
184: * @param text the text to be associated with it
185: */
186: public void setStackText(int index, String text) {
187: setStackText(index, text, false);
188: }
189:
190: /**
191: * Sets the text associated with a child by its index.
192: *
193: * @param index the index of the child whose text is to be set
194: * @param text the text to be associated with it
195: * @param asHTML <code>true</code> to treat the specified text as HTML
196: */
197: public void setStackText(int index, String text, boolean asHTML) {
198: if (index >= getWidgetCount()) {
199: return;
200: }
201:
202: Element td = DOM.getChild(DOM.getChild(body, index * 2), 0);
203: if (asHTML) {
204: DOM.setInnerHTML(td, text);
205: } else {
206: DOM.setInnerText(td, text);
207: }
208: }
209:
210: /**
211: * Shows the widget at the specified child index.
212: *
213: * @param index the index of the child to be shown
214: */
215: public void showStack(int index) {
216: if ((index >= getWidgetCount()) || (index == visibleStack)) {
217: return;
218: }
219:
220: if (visibleStack >= 0) {
221: setStackVisible(visibleStack, false);
222: }
223:
224: visibleStack = index;
225: setStackVisible(visibleStack, true);
226: }
227:
228: private int findDividerIndex(Element elem) {
229: while ((elem != null) && !DOM.compare(elem, getElement())) {
230: String expando = DOM.getElementProperty(elem, "__index");
231: if (expando != null) {
232: // Make sure it belongs to me!
233: int ownerHash = DOM.getElementPropertyInt(elem,
234: "__owner");
235: if (ownerHash == hashCode()) {
236: // Yes, it's mine.
237: return Integer.parseInt(expando);
238: } else {
239: // It must belong to some nested StackPanel.
240: return -1;
241: }
242: }
243: elem = DOM.getParent(elem);
244: }
245: return -1;
246: }
247:
248: private boolean remove(Widget child, int index) {
249: // Make sure to call this before disconnecting the DOM.
250: boolean removed = super .remove(child);
251: if (removed) {
252: // Calculate which internal table elements to remove.
253: int rowIndex = 2 * index;
254: Element tr = DOM.getChild(body, rowIndex);
255: DOM.removeChild(body, tr);
256: tr = DOM.getChild(body, rowIndex);
257: DOM.removeChild(body, tr);
258:
259: // Correct visible stack for new location.
260: if (visibleStack == index) {
261: visibleStack = -1;
262: } else if (visibleStack > index) {
263: --visibleStack;
264: }
265:
266: // Update indices of all elements to the right.
267: updateIndicesFrom(rowIndex);
268: }
269: return removed;
270: }
271:
272: private void setStackVisible(int index, boolean visible) {
273: // Get the first table row containing the widget's selector item.
274: Element tr = DOM.getChild(body, (index * 2));
275: if (tr == null) {
276: return;
277: }
278:
279: // Style the stack selector item.
280: Element td = DOM.getFirstChild(tr);
281: setStyleName(td, "gwt-StackPanelItem-selected", visible);
282:
283: // Show/hide the contained widget.
284: tr = DOM.getChild(body, (index * 2) + 1);
285: UIObject.setVisible(tr, visible);
286: getWidget(index).setVisible(visible);
287: }
288:
289: private void updateIndicesFrom(int beforeIndex) {
290: for (int i = beforeIndex, c = getWidgetCount(); i < c; ++i) {
291: Element childTR = DOM.getChild(body, i * 2);
292: Element childTD = DOM.getFirstChild(childTR);
293: DOM.setElementPropertyInt(childTD, "__index", i);
294: }
295: }
296:
297: }
|