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}