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 }