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