001/*
002 * $Id: JXTaskPane.java 4158 2012-02-03 18:29:40Z kschaefe $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx;
022
023import java.awt.BorderLayout;
024import java.awt.Component;
025import java.awt.Container;
026import java.awt.LayoutManager;
027import java.beans.PropertyChangeEvent;
028import java.beans.PropertyChangeListener;
029
030import javax.swing.Action;
031import javax.swing.Icon;
032import javax.swing.JComponent;
033import javax.swing.JPanel;
034import javax.swing.UIManager;
035
036import org.jdesktop.beans.JavaBean;
037import org.jdesktop.swingx.plaf.LookAndFeelAddons;
038import org.jdesktop.swingx.plaf.TaskPaneAddon;
039import org.jdesktop.swingx.plaf.TaskPaneUI;
040
041/**
042 * <code>JXTaskPane</code> is a container for tasks and other
043 * arbitrary components.
044 * 
045 * <p>
046 * Several <code>JXTaskPane</code>s are usually grouped together within a
047 * {@link org.jdesktop.swingx.JXTaskPaneContainer}. However it is not mandatory
048 * to use a JXTaskPaneContainer as the parent for JXTaskPane. The JXTaskPane can
049 * be added to any other container. See
050 * {@link org.jdesktop.swingx.JXTaskPaneContainer} to understand the benefits of
051 * using it as the parent container.
052 * 
053 * <p>
054 * <code>JXTaskPane</code> provides control to expand and
055 * collapse the content area in order to show or hide the task list. It can have an
056 * <code>icon</code>, a <code>title</code> and can be marked as
057 * <code>special</code>. Marking a <code>JXTaskPane</code> as
058 * <code>special</code> ({@link #setSpecial(boolean)} is only a hint for
059 * the pluggable UI which will usually paint it differently (by example by
060 * using another color for the border of the pane).
061 * 
062 * <p> 
063 * When the JXTaskPane is expanded or collapsed, it will be
064 * animated with a fade effect. The animated can be disabled on a per
065 * component basis through {@link #setAnimated(boolean)}.
066 * 
067 * To disable the animation for all newly created <code>JXTaskPane</code>,
068 * use the UIManager property:
069 * <code>UIManager.put("TaskPane.animate", Boolean.FALSE);</code>.
070 * 
071 * <p>
072 * Example:
073 * <pre>
074 * <code>
075 * JXFrame frame = new JXFrame();
076 * 
077 * // a container to put all JXTaskPane together
078 * JXTaskPaneContainer taskPaneContainer = new JXTaskPaneContainer();
079 * 
080 * // create a first taskPane with common actions
081 * JXTaskPane actionPane = new JXTaskPane();
082 * actionPane.setTitle("Files and Folders");
083 * actionPane.setSpecial(true);
084 * 
085 * // actions can be added, a hyperlink will be created
086 * Action renameSelectedFile = createRenameFileAction();
087 * actionPane.add(renameSelectedFile);
088 * actionPane.add(createDeleteFileAction());
089 * 
090 * // add this taskPane to the taskPaneContainer
091 * taskPaneContainer.add(actionPane);
092 * 
093 * // create another taskPane, it will show details of the selected file
094 * JXTaskPane details = new JXTaskPane();
095 * details.setTitle("Details");
096 *  
097 * // add standard components to the details taskPane
098 * JLabel searchLabel = new JLabel("Search:");
099 * JTextField searchField = new JTextField("");
100 * details.add(searchLabel);
101 * details.add(searchField);
102 * 
103 * taskPaneContainer.add(details);
104 * 
105 * // put the action list on the left 
106 * frame.add(taskPaneContainer, BorderLayout.EAST);
107 * 
108 * // and a file browser in the middle
109 * frame.add(fileBrowser, BorderLayout.CENTER);
110 * 
111 * frame.pack();
112 * frame.setVisible(true);
113 * </code>
114 * </pre>
115 * 
116 * @see org.jdesktop.swingx.JXTaskPaneContainer
117 * @see org.jdesktop.swingx.JXCollapsiblePane
118 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
119 * @author Karl George Schaefer
120 * 
121 * @javabean.attribute
122 *          name="isContainer"
123 *          value="Boolean.TRUE"
124 *          rtexpr="true"
125 *          
126 * @javabean.attribute
127 *          name="containerDelegate"
128 *          value="getContentPane"
129 *          
130 * @javabean.class
131 *          name="JXTaskPane"
132 *          shortDescription="JXTaskPane is a container for tasks and other arbitrary components."
133 *          stopClass="java.awt.Component"
134 * 
135 * @javabean.icons
136 *          mono16="JXTaskPane16-mono.gif"
137 *          color16="JXTaskPane16.gif"
138 *          mono32="JXTaskPane32-mono.gif"
139 *          color32="JXTaskPane32.gif"
140 */
141@JavaBean
142public class JXTaskPane extends JPanel implements
143  JXCollapsiblePane.CollapsiblePaneContainer, Mnemonicable {
144
145  /**
146   * JXTaskPane pluggable UI key <i>swingx/TaskPaneUI</i> 
147   */
148  public final static String uiClassID = "swingx/TaskPaneUI";
149  
150  // ensure at least the default ui is registered
151  static {
152    LookAndFeelAddons.contribute(new TaskPaneAddon());
153  }
154
155  /**
156   * Used when generating PropertyChangeEvents for the "scrollOnExpand" property
157   */
158  public static final String SCROLL_ON_EXPAND_CHANGED_KEY = "scrollOnExpand";
159
160  /**
161   * Used when generating PropertyChangeEvents for the "title" property
162   */
163  public static final String TITLE_CHANGED_KEY = "title";
164
165  /**
166   * Used when generating PropertyChangeEvents for the "icon" property
167   */
168  public static final String ICON_CHANGED_KEY = "icon";
169
170  /**
171   * Used when generating PropertyChangeEvents for the "special" property
172   */
173  public static final String SPECIAL_CHANGED_KEY = "special";
174
175  /**
176   * Used when generating PropertyChangeEvents for the "animated" property
177   */
178  public static final String ANIMATED_CHANGED_KEY = "animated";
179
180  private String title;
181  private Icon icon;
182  private boolean special;
183  private boolean collapsed;
184  private boolean scrollOnExpand;
185
186  private int        mnemonic;
187  private int        mnemonicIndex           = -1;
188  
189  private JXCollapsiblePane collapsePane;
190  
191  /**
192   * Creates a new empty <code>JXTaskPane</code>.
193   */
194  public JXTaskPane() {
195      this((String) null);
196  }
197
198    /**
199     * Creates a new task pane with the specified title.
200     * 
201     * @param title
202     *            the title to use
203     */
204    public JXTaskPane(String title) {
205        this(title, null);
206    }
207
208    /**
209     * Creates a new task pane with the specified icon.
210     * 
211     * @param icon
212     *            the icon to use
213     */
214    public JXTaskPane(Icon icon) {
215        this(null, icon);
216    }
217
218    /**
219     * Creates a new task pane with the specified title and icon.
220     * 
221     * @param title
222     *            the title to use
223     * @param icon
224     *            the icon to use
225     */
226    public JXTaskPane(String title, Icon icon) {
227      collapsePane = new JXCollapsiblePane();
228      super.setLayout(new BorderLayout(0, 0));
229      super.addImpl(collapsePane, BorderLayout.CENTER, -1);
230      
231      setTitle(title);
232      setIcon(icon);
233      
234      updateUI();
235      setFocusable(true);
236
237      // disable animation if specified in UIManager
238      setAnimated(!Boolean.FALSE.equals(UIManager.get("TaskPane.animate")));
239      
240      // listen for animation events and forward them to registered listeners
241        collapsePane.addPropertyChangeListener("collapsed", new PropertyChangeListener() {
242            @Override
243            public void propertyChange(PropertyChangeEvent evt) {
244                JXTaskPane.this.firePropertyChange(evt.getPropertyName(), evt.getOldValue(),
245                        evt.getNewValue());
246            }
247        });
248  }
249
250  /**
251   * Returns the contentPane object for this JXTaskPane.
252   * @return the contentPane property
253   */
254  public Container getContentPane() {
255    return collapsePane.getContentPane();
256  }
257  
258  /**
259   * Notification from the <code>UIManager</code> that the L&F has changed.
260   * Replaces the current UI object with the latest version from the <code>UIManager</code>.
261   * 
262   * @see javax.swing.JComponent#updateUI
263   */
264  @Override
265  public void updateUI() {
266    // collapsePane is null when updateUI() is called by the "super()"
267    // constructor
268    if (collapsePane == null) {
269      return;
270    }
271    setUI((TaskPaneUI)LookAndFeelAddons.getUI(this, TaskPaneUI.class));
272  }
273  
274  /**
275   * Sets the L&F object that renders this component.
276   * 
277   * @param ui the <code>TaskPaneUI</code> L&F object
278   * @see javax.swing.UIDefaults#getUI
279   * 
280   * @beaninfo bound: true hidden: true description: The UI object that
281   * implements the taskpane group's LookAndFeel.
282   */
283  public void setUI(TaskPaneUI ui) {
284    super.setUI(ui);
285  }
286
287  /**
288   * Returns the name of the L&F class that renders this component.
289   * 
290   * @return the string {@link #uiClassID}
291   * @see javax.swing.JComponent#getUIClassID
292   * @see javax.swing.UIDefaults#getUI
293   */
294  @Override
295  public String getUIClassID() {
296    return uiClassID;
297  }
298
299  /**
300   * Returns the title currently displayed in the border of this pane.
301   * 
302   * @return the title currently displayed in the border of this pane
303   */
304  public String getTitle() {
305    return title;
306  }
307
308  /**
309   * Sets the title to be displayed in the border of this pane.
310   * 
311   * @param title the title to be displayed in the border of this pane
312   * @javabean.property
313   *          bound="true"
314   *          preferred="true"
315   */
316  public void setTitle(String title) {
317    String old = this.title;
318    this.title = title;
319    firePropertyChange(TITLE_CHANGED_KEY, old, title);
320  }
321
322  /**
323   * Returns the icon currently displayed in the border of this pane.
324   * 
325   * @return the icon currently displayed in the border of this pane
326   */
327  public Icon getIcon() {
328    return icon;
329  }
330
331  /**
332   * Sets the icon to be displayed in the border of this pane. Some pluggable
333   * UIs may impose size constraints for the icon. A size of 16x16 pixels is
334   * the recommended icon size.
335   * 
336   * @param icon the icon to be displayed in the border of this pane
337   * @javabean.property
338   *          bound="true"
339   *          preferred="true"
340   */
341  public void setIcon(Icon icon) {
342    Icon old = this.icon;
343    this.icon = icon;
344    firePropertyChange(ICON_CHANGED_KEY, old, icon);
345  }
346
347  /**
348   * Returns true if this pane is "special".
349   * 
350   * @return true if this pane is "special"
351   * @see #setSpecial(boolean)
352   */
353  public boolean isSpecial() {
354    return special;
355  }
356
357  /**
358   * Sets this pane to be "special" or not. Marking a <code>JXTaskPane</code>
359   * as <code>special</code> is only a hint for the pluggable UI which will
360   * usually paint it differently (by example by using another color for the
361   * border of the pane).
362   * 
363   * <p>
364   * Usually the first JXTaskPane in a JXTaskPaneContainer is marked as special
365   * because it contains the default set of actions which can be executed given
366   * the current context.
367   * 
368   * @param special
369   *          true if this pane is "special", false otherwise
370   * @javabean.property bound="true" preferred="true"
371   */
372  public void setSpecial(boolean special) {
373      boolean oldValue = isSpecial();
374      this.special = special;
375      firePropertyChange(SPECIAL_CHANGED_KEY, oldValue, isSpecial());
376  }
377
378  /**
379   * Should this group be scrolled to be visible on expand.
380   * 
381   * @param scrollOnExpand true to scroll this group to be
382   * visible if this group is expanded.
383   * 
384   * @see #setCollapsed(boolean)
385   * 
386   * @javabean.property
387   *          bound="true"
388   *          preferred="true"
389   */
390  public void setScrollOnExpand(boolean scrollOnExpand) {
391      boolean oldValue = isScrollOnExpand();
392      this.scrollOnExpand = scrollOnExpand;
393      firePropertyChange(SCROLL_ON_EXPAND_CHANGED_KEY,
394              oldValue, isScrollOnExpand());
395  }
396  
397  /**
398   * Should this group scroll to be visible after
399   * this group was expanded.
400   * 
401   * @return true if we should scroll false if nothing
402   * should be done.
403   */
404  public boolean isScrollOnExpand() {
405    return scrollOnExpand;
406  }
407  
408    /**
409     * Expands or collapses this group.
410     * <p>
411     * As of SwingX 1.6.3, the property change event only fires when the
412     * state is accurate.  As such, animated task pane fire once the 
413     * animation is complete.
414     * 
415     * @param collapsed
416     *                true to collapse the group, false to expand it
417     * @javabean.property
418     *          bound="true"
419     *          preferred="false"
420     */
421    public void setCollapsed(boolean collapsed) {
422        boolean oldValue = isCollapsed();
423        this.collapsed = collapsed;
424        collapsePane.setCollapsed(collapsed);
425    }
426    
427    /**
428     * Returns the collapsed state of this task pane.
429     * 
430     * @return {@code true} if the task pane is collapsed; {@code false}
431     *         otherwise
432     */
433    public boolean isCollapsed() {
434        return collapsed;
435    }
436
437  /**
438   * Enables or disables animation during expand/collapse transition.
439   * 
440   * @param animated
441   * @javabean.property
442   *          bound="true"
443   *          preferred="true"
444   */
445  public void setAnimated(boolean animated) {
446      boolean oldValue = isAnimated();
447      collapsePane.setAnimated(animated);
448      firePropertyChange(ANIMATED_CHANGED_KEY, oldValue, isAnimated());
449  }
450  
451  /**
452   * Returns true if this task pane is animated during expand/collapse
453   * transition.
454   * 
455   * @return true if this task pane is animated during expand/collapse
456   *         transition.
457   */
458  public boolean isAnimated() {
459    return collapsePane.isAnimated();
460  }
461
462    /**
463     * {@inheritDoc}
464     * <p>
465     * If the character defined by the mnemonic is found within the task pane's 
466     * text string, the first occurrence of it will be underlined to indicate
467     * the mnemonic to the user.
468     */
469    @Override
470    public int getMnemonic() {
471        return mnemonic;
472    }
473
474    /**
475     * {@inheritDoc}
476     */
477    @Override
478    public void setMnemonic(int mnemonic) {
479        int oldValue = getMnemonic();
480        this.mnemonic = mnemonic;
481        
482        firePropertyChange("mnemonic", oldValue, getMnemonic());
483        
484        updateDisplayedMnemonicIndex(getTitle(), mnemonic);
485        revalidate();
486        repaint();
487    }
488
489    /**
490     * Update the displayedMnemonicIndex property. This method
491     * is called when either text or mnemonic changes. The new
492     * value of the displayedMnemonicIndex property is the index
493     * of the first occurrence of mnemonic in text.
494     */
495    private void updateDisplayedMnemonicIndex(String text, int mnemonic) {
496        if (text == null || mnemonic == '\0') {
497            mnemonicIndex = -1;
498            
499            return;
500        }
501
502        char uc = Character.toUpperCase((char)mnemonic);
503        char lc = Character.toLowerCase((char)mnemonic);
504
505        int uci = text.indexOf(uc);
506        int lci = text.indexOf(lc);
507
508        if (uci == -1) {
509            mnemonicIndex = lci;
510        } else if(lci == -1) {
511            mnemonicIndex = uci;
512        } else {
513            mnemonicIndex = (lci < uci) ? lci : uci;
514        }
515    }
516
517    /**
518     * {@inheritDoc}
519     */
520    @Override
521    public int getDisplayedMnemonicIndex() {
522        return mnemonicIndex;
523    }
524
525    /**
526     * {@inheritDoc}
527     */
528    @Override
529    public void setDisplayedMnemonicIndex(int index)
530                                          throws IllegalArgumentException {
531        int oldValue = mnemonicIndex;
532        if (index == -1) {
533            mnemonicIndex = -1;
534        } else {
535            String text = getTitle();
536            int textLength = (text == null) ? 0 : text.length();
537            if (index < -1 || index >= textLength) {  // index out of range
538                throw new IllegalArgumentException("index == " + index);
539            }
540        }
541        mnemonicIndex = index;
542        firePropertyChange("displayedMnemonicIndex", oldValue, index);
543        if (index != oldValue) {
544            revalidate();
545            repaint();
546        }
547    }
548  
549  /**
550   * Adds an action to this <code>JXTaskPane</code>. Returns a
551   * component built from the action. The returned component has been
552   * added to the <code>JXTaskPane</code>.
553   * 
554   * @param action
555   * @return a component built from the action
556   */
557  public Component add(Action action) {
558    Component c = ((TaskPaneUI)ui).createAction(action);
559    add(c);
560    return c;
561  }
562
563  /**
564   * @see JXCollapsiblePane.CollapsiblePaneContainer
565   */
566  @Override
567public Container getValidatingContainer() {
568    return getParent();
569  }
570  
571  /**
572   * Overridden to redirect call to the content pane.
573   */
574  @Override
575  protected void addImpl(Component comp, Object constraints, int index) {
576    getContentPane().add(comp, constraints, index);
577    //Fixes SwingX #364; adding to internal component we need to revalidate ourself
578    revalidate();
579  }
580
581  /**
582   * Overridden to redirect call to the content pane.
583   */
584  @Override
585  public void setLayout(LayoutManager mgr) {
586    if (collapsePane != null) {
587      getContentPane().setLayout(mgr);
588    }
589  }
590  
591  /**
592   * Overridden to redirect call to the content pane
593   */
594  @Override
595  public void remove(Component comp) {
596    getContentPane().remove(comp);
597  }
598
599  /**
600   * Overridden to redirect call to the content pane.
601   */
602  @Override
603  public void remove(int index) {
604    getContentPane().remove(index);
605  }
606  
607  /**
608   * Overridden to redirect call to the content pane.
609   */
610  @Override
611  public void removeAll() {
612    getContentPane().removeAll();
613  }
614  
615  /**
616   * @see JComponent#paramString()
617   */
618  @Override
619  protected String paramString() {
620    return super.paramString()
621      + ",title="
622      + getTitle()
623      + ",icon="
624      + getIcon()
625      + ",collapsed="
626      + String.valueOf(isCollapsed())
627      + ",special="
628      + String.valueOf(isSpecial())
629      + ",scrollOnExpand=" 
630      + String.valueOf(isScrollOnExpand())
631      + ",ui=" + getUI();
632  }
633
634}