001 /*
002 * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
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 asquare.gwt.tk.client.ui;
017
018 import java.util.Iterator;
019 import java.util.List;
020 import java.util.Vector;
021
022 import asquare.gwt.tk.client.ui.behavior.ControllerAdaptor;
023 import asquare.gwt.tk.client.ui.behavior.PreventSelectionController;
024
025 import com.google.gwt.user.client.DOM;
026 import com.google.gwt.user.client.Element;
027 import com.google.gwt.user.client.Event;
028 import com.google.gwt.user.client.ui.HTML;
029 import com.google.gwt.user.client.ui.Widget;
030
031 /**
032 * A panel that consists of a hideable content DIV and an optional header DIV.
033 * By default, the user can click the header to show/hide the content.
034 * The panel is closed initially. It can be opened & closed with
035 * {@link #setOpen(boolean)}.
036 *
037 * <p>Usage Notes</p>
038 * <ul>
039 * <li><code>width:1px</code> will cancel <code>white-space:nowrap</code> in IE6. <br/>
040 * Workaround: use <code>&nbsp;</code> to prevent line wrapping in the header.</li>
041 * <li>In IE6, changing the header's background image via CSS will cause the cursor to revert to
042 * <code>default</code>. The cursor CSS will be applied as soon as the cursor is moved. </li>
043 * </ul>
044 *
045 * <h3>CSS Style Rules</h3>
046 * <ul class='css'>
047 * <li>.tk-DropDownPanel { the outer DIV }</li>
048 * <li>.tk-DropDownPanelHeader { the header DIV }</li>
049 * <li>.tk-DropDownPanelContent { the content DIV }</li>
050 * <li>.tk-DropDownPanel-open { this is applied to the outer DIV when it's
051 * content child is visible }</li>
052 * </ul>
053 */
054 public class DropDownPanel extends CComplexPanel
055 {
056 public static final String PROPERTY_OPEN = "DropDownPanel-open";
057 public static final String PROPERTY_INTERACTIVE = "DropDownPanel-interactive";
058
059 private final Vector m_listeners = new Vector();
060 private final Element m_contentDiv;
061 private HTML m_header = null;
062 private boolean m_interactive = true;
063 private boolean m_open = false;
064
065 /**
066 * Constructs an empty panel. You can customize it later with
067 * {@link #add(Widget)} and {@link #setHeaderText(String, boolean)}.
068 */
069 public DropDownPanel()
070 {
071 this(null, null, false);
072 }
073
074 /**
075 * Constructs a new panel, adding a widget to the content area and
076 * optionally setting the header.
077 *
078 * @param w the widget to be added, or null
079 * @param text the header text associated with this widget, or null
080 * @param asHTML <code>true</code> to treat the specified text as HTML.
081 * Ignored if <code>text</code> is <code>null</code>.
082 */
083 public DropDownPanel(Widget w, String text, boolean asHTML)
084 {
085 super(DOM.createDiv());
086 setStyleName("tk-DropDownPanel");
087
088 m_contentDiv = DOM.createDiv();
089 setStyleName(m_contentDiv, "tk-DropDownPanelContent", true);
090 setVisible(m_contentDiv, false);
091 DOM.appendChild(getElement(), m_contentDiv);
092
093 if (w != null)
094 {
095 add(w);
096 }
097
098 if (text != null)
099 {
100 setHeaderText(text, asHTML);
101 }
102 }
103
104 protected List createHeaderControllers()
105 {
106 List result = new Vector();
107 result.add(new OpenerController(this));
108 result.add(PreventSelectionController.getInstance());
109 return result;
110 }
111
112 /**
113 * Add a listener to be notified when the content DIV is shown/hidden.
114 */
115 public void addDropDownListener(DropDownListener listener)
116 {
117 m_listeners.add(listener);
118 }
119
120 public void removeDropDownListener(DropDownListener listener)
121 {
122 m_listeners.remove(listener);
123 }
124
125 /**
126 * Adds a widget to the panel. Only one widget may be added.
127 *
128 * @param w the widget to be added
129 */
130 public void add(Widget w)
131 {
132 super.add(w, m_contentDiv);
133 }
134
135 /**
136 * Has the header been specified?
137 *
138 * @return false if no header
139 */
140 public boolean hasHeader()
141 {
142 return m_header != null;
143 }
144
145 /**
146 * Sets the text in the header DIV. Creates the header if necessary.
147 * Removes the header if null;
148 *
149 * @param text the text to be associated with it, or null to remove the header
150 * @param asHTML <code>true</code> to treat the specified text as HTML
151 */
152 public void setHeaderText(String text, boolean asHTML)
153 {
154 if (text == null)
155 {
156 if (m_header != null)
157 {
158 super.remove(m_header);
159 }
160 }
161 else
162 {
163 if (m_header == null)
164 {
165 m_header = new HTML();
166 m_header.setStyleName("tk-DropDownPanelHeader");
167 CWrapper headerWrapper = new CWrapper(m_header, createHeaderControllers());
168 super.add(headerWrapper, null);
169 DOM.insertChild(getElement(), headerWrapper.getElement(), 0);
170 }
171 if (asHTML)
172 {
173 m_header.setHTML(text);
174 }
175 else
176 {
177 m_header.setText(text);
178 }
179 }
180 }
181
182 public boolean remove(Widget w)
183 {
184 if (w.getParent() != this)
185 throw new IllegalArgumentException();
186
187 DOM.removeChild(m_contentDiv, w.getElement());
188 return super.remove(w);
189 }
190
191 /**
192 * Sets the visibility of the content DIV.
193 *
194 * @param open <code>true</code> to show the content DIV
195 */
196 public void setOpen(boolean open)
197 {
198 if (m_open != open)
199 {
200 m_open = open;
201 setStyleName(getElement(), "tk-DropDownPanel-open", m_open);
202 setVisible(m_contentDiv, m_open);
203 if (m_open)
204 {
205 fireDropDownOpened();
206 }
207 else
208 {
209 fireDropDownClosed();
210 }
211 }
212 }
213
214 /**
215 * Is the content DIV currently visible?
216 */
217 public boolean isOpen()
218 {
219 return m_open;
220 }
221
222 /**
223 * Toggles the visibility of the content DIV.
224 */
225 public void toggleOpen()
226 {
227 setOpen(! m_open);
228 }
229
230 /**
231 * Will clicking in the header will toggle the content DIV?
232 *
233 * @return true if header click processing is enabled
234 */
235 public boolean isInteractive()
236 {
237 return m_interactive;
238 }
239
240 /**
241 * Sets whether clicking in the header will toggle the content DIV.
242 *
243 * @param interactive true to enable header click processing
244 */
245 public void setInteractive(boolean interactive)
246 {
247 m_interactive = interactive;
248 }
249
250 private void fireDropDownOpened()
251 {
252 if (m_listeners.size() == 0)
253 return;
254
255 for (Iterator iter = m_listeners.iterator(); iter.hasNext();)
256 {
257 ((DropDownListener) iter.next()).dropDownOpened(this);
258 }
259 }
260
261 private void fireDropDownClosed()
262 {
263 if (m_listeners.size() == 0)
264 return;
265
266 for (Iterator iter = m_listeners.iterator(); iter.hasNext();)
267 {
268 ((DropDownListener) iter.next()).dropDownClosed(this);
269 }
270 }
271
272 public static class OpenerController extends ControllerAdaptor
273 {
274 public final DropDownPanel m_dd;
275
276 public OpenerController(DropDownPanel dd)
277 {
278 super(Event.ONCLICK, OpenerController.class);
279 m_dd = dd;
280 }
281
282 protected boolean doBrowserEvent(Widget widget, Event event)
283 {
284 if (m_dd.isInteractive())
285 {
286 m_dd.toggleOpen();
287 }
288 return true;
289 }
290 }
291 }