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 &amp; 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>&amp;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    }