001 /* 002 * $Id: BasicTaskPaneUI.java,v 1.7 2005/11/17 00:40:31 rbair Exp $ 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.plaf.basic; 022 023 import java.awt.Color; 024 import java.awt.Component; 025 import java.awt.Cursor; 026 import java.awt.Dimension; 027 import java.awt.Graphics; 028 import java.awt.Insets; 029 import java.awt.Rectangle; 030 import java.awt.event.ActionEvent; 031 import java.awt.event.FocusEvent; 032 import java.awt.event.FocusListener; 033 import java.awt.event.MouseEvent; 034 import java.beans.PropertyChangeEvent; 035 import java.beans.PropertyChangeListener; 036 037 import javax.swing.AbstractAction; 038 import javax.swing.Action; 039 import javax.swing.ActionMap; 040 import javax.swing.BorderFactory; 041 import javax.swing.Icon; 042 import javax.swing.InputMap; 043 import javax.swing.JComponent; 044 import javax.swing.JLabel; 045 import javax.swing.LookAndFeel; 046 import javax.swing.SwingUtilities; 047 import javax.swing.UIManager; 048 import javax.swing.border.Border; 049 import javax.swing.border.CompoundBorder; 050 import javax.swing.event.MouseInputAdapter; 051 import javax.swing.event.MouseInputListener; 052 import javax.swing.plaf.ActionMapUIResource; 053 import javax.swing.plaf.ComponentUI; 054 import javax.swing.plaf.basic.BasicGraphicsUtils; 055 056 import org.jdesktop.swingx.JXCollapsiblePane; 057 import org.jdesktop.swingx.JXHyperlink; 058 import org.jdesktop.swingx.JXTaskPane; 059 import org.jdesktop.swingx.icon.EmptyIcon; 060 import org.jdesktop.swingx.plaf.TaskPaneUI; 061 062 /** 063 * Base implementation of the <code>JXTaskPane</code> UI. 064 * 065 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a> 066 */ 067 public class BasicTaskPaneUI extends TaskPaneUI { 068 069 private static FocusListener focusListener = new RepaintOnFocus(); 070 071 public static ComponentUI createUI(JComponent c) { 072 return new BasicTaskPaneUI(); 073 } 074 075 protected static int TITLE_HEIGHT = 25; 076 protected static int ROUND_HEIGHT = 5; 077 078 protected JXTaskPane group; 079 080 protected boolean mouseOver; 081 protected MouseInputListener mouseListener; 082 083 protected PropertyChangeListener propertyListener; 084 085 public void installUI(JComponent c) { 086 super.installUI(c); 087 group = (JXTaskPane)c; 088 089 installDefaults(); 090 installListeners(); 091 installKeyboardActions(); 092 } 093 094 protected void installDefaults() { 095 group.setOpaque(true); 096 group.setBorder(createPaneBorder()); 097 ((JComponent)group.getContentPane()).setBorder(createContentPaneBorder()); 098 099 LookAndFeel.installColorsAndFont( 100 group, 101 "TaskPane.background", 102 "TaskPane.foreground", 103 "TaskPane.font"); 104 105 LookAndFeel.installColorsAndFont( 106 (JComponent)group.getContentPane(), 107 "TaskPane.background", 108 "TaskPane.foreground", 109 "TaskPane.font"); 110 } 111 112 protected void installListeners() { 113 mouseListener = createMouseInputListener(); 114 group.addMouseMotionListener(mouseListener); 115 group.addMouseListener(mouseListener); 116 117 group.addFocusListener(focusListener); 118 propertyListener = createPropertyListener(); 119 group.addPropertyChangeListener(propertyListener); 120 } 121 122 protected void installKeyboardActions() { 123 InputMap inputMap = (InputMap)UIManager.get("TaskPane.focusInputMap"); 124 if (inputMap != null) { 125 SwingUtilities.replaceUIInputMap( 126 group, 127 JComponent.WHEN_FOCUSED, 128 inputMap); 129 } 130 131 ActionMap map = getActionMap(); 132 if (map != null) { 133 SwingUtilities.replaceUIActionMap(group, map); 134 } 135 } 136 137 ActionMap getActionMap() { 138 ActionMap map = new ActionMapUIResource(); 139 map.put("toggleExpanded", new ToggleExpandedAction()); 140 return map; 141 } 142 143 public void uninstallUI(JComponent c) { 144 uninstallListeners(); 145 super.uninstallUI(c); 146 } 147 148 protected void uninstallListeners() { 149 group.removeMouseListener(mouseListener); 150 group.removeMouseMotionListener(mouseListener); 151 group.removeFocusListener(focusListener); 152 group.removePropertyChangeListener(propertyListener); 153 } 154 155 protected MouseInputListener createMouseInputListener() { 156 return new ToggleListener(); 157 } 158 159 protected PropertyChangeListener createPropertyListener() { 160 return new ChangeListener(); 161 } 162 163 protected boolean isInBorder(MouseEvent event) { 164 return event.getY() < getTitleHeight(); 165 } 166 167 protected final int getTitleHeight() { 168 return TITLE_HEIGHT; 169 } 170 171 protected Border createPaneBorder() { 172 return new PaneBorder(); 173 } 174 175 @Override 176 public Dimension getPreferredSize(JComponent c) { 177 Component component = group.getComponent(0); 178 if (!(component instanceof JXCollapsiblePane)) { 179 // something wrong in this JXTaskPane 180 return super.getPreferredSize(c); 181 } 182 183 JXCollapsiblePane collapsible = (JXCollapsiblePane)component; 184 Dimension dim = collapsible.getPreferredSize(); 185 186 Border groupBorder = group.getBorder(); 187 if (groupBorder instanceof PaneBorder) { 188 Dimension border = ((PaneBorder)groupBorder).getPreferredSize(group); 189 dim.width = Math.max(dim.width, border.width); 190 dim.height += border.height; 191 } else { 192 dim.height += getTitleHeight(); 193 } 194 195 return dim; 196 } 197 198 protected Border createContentPaneBorder() { 199 Color borderColor = UIManager.getColor("TaskPane.borderColor"); 200 return new CompoundBorder(new ContentPaneBorder(borderColor), BorderFactory 201 .createEmptyBorder(10, 10, 10, 10)); 202 } 203 204 public Component createAction(Action action) { 205 JXHyperlink button = new JXHyperlink(action); 206 button.setOpaque(false); 207 button.setBorder(null); 208 button.setBorderPainted(false); 209 button.setFocusPainted(true); 210 button.setForeground(UIManager.getColor("TaskPane.titleForeground")); 211 return button; 212 } 213 214 protected void ensureVisible() { 215 SwingUtilities.invokeLater(new Runnable() { 216 public void run() { 217 group.scrollRectToVisible( 218 new Rectangle(group.getWidth(), group.getHeight())); 219 } 220 }); 221 } 222 223 static class RepaintOnFocus implements FocusListener { 224 public void focusGained(FocusEvent e) { 225 e.getComponent().repaint(); 226 } 227 public void focusLost(FocusEvent e) { 228 e.getComponent().repaint(); 229 } 230 } 231 232 class ChangeListener implements PropertyChangeListener { 233 public void propertyChange(PropertyChangeEvent evt) { 234 // if group is expanded but not animated 235 // or if animated has reached expanded state 236 // scroll to visible if scrollOnExpand is enabled 237 if ((JXTaskPane.EXPANDED_CHANGED_KEY.equals(evt.getPropertyName()) 238 && Boolean.TRUE.equals(evt.getNewValue()) && !group.isAnimated()) 239 || (JXCollapsiblePane.ANIMATION_STATE_KEY.equals(evt.getPropertyName()) && "expanded" 240 .equals(evt.getNewValue()))) { 241 if (group.isScrollOnExpand()) { 242 ensureVisible(); 243 } 244 } 245 } 246 } 247 248 class ToggleListener extends MouseInputAdapter { 249 public void mouseEntered(MouseEvent e) { 250 if (isInBorder(e)) { 251 e.getComponent().setCursor( 252 Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 253 } else { 254 mouseOver = false; 255 group.repaint(); 256 } 257 } 258 public void mouseExited(MouseEvent e) { 259 e.getComponent().setCursor(Cursor.getDefaultCursor()); 260 mouseOver = false; 261 group.repaint(); 262 } 263 public void mouseMoved(MouseEvent e) { 264 if (isInBorder(e)) { 265 e.getComponent().setCursor( 266 Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 267 mouseOver = true; 268 group.repaint(); 269 } else { 270 e.getComponent().setCursor(Cursor.getDefaultCursor()); 271 mouseOver = false; 272 group.repaint(); 273 } 274 } 275 public void mouseReleased(MouseEvent e) { 276 if (isInBorder(e)) { 277 group.setExpanded(!group.isExpanded()); 278 } 279 } 280 } 281 282 class ToggleExpandedAction extends AbstractAction { 283 public ToggleExpandedAction() { 284 super("toggleExpanded"); 285 } 286 public void actionPerformed(ActionEvent e) { 287 group.setExpanded(!group.isExpanded()); 288 } 289 public boolean isEnabled() { 290 return group.isVisible(); 291 } 292 } 293 294 protected static class ChevronIcon implements Icon { 295 boolean up = true; 296 public ChevronIcon(boolean up) { 297 this.up = up; 298 } 299 public int getIconHeight() { 300 return 3; 301 } 302 public int getIconWidth() { 303 return 6; 304 } 305 public void paintIcon(Component c, Graphics g, int x, int y) { 306 if (up) { 307 g.drawLine(x + 3, y, x, y + 3); 308 g.drawLine(x + 3, y, x + 6, y + 3); 309 } else { 310 g.drawLine(x, y, x + 3, y + 3); 311 g.drawLine(x + 3, y + 3, x + 6, y); 312 } 313 } 314 } 315 316 /** 317 * The border around the content pane 318 */ 319 protected static class ContentPaneBorder implements Border { 320 Color color; 321 public ContentPaneBorder(Color color) { 322 this.color = color; 323 } 324 public Insets getBorderInsets(Component c) { 325 return new Insets(0, 1, 1, 1); 326 } 327 public boolean isBorderOpaque() { 328 return true; 329 } 330 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 331 g.setColor(color); 332 g.drawLine(x, y, x, y + height - 1); 333 g.drawLine(x, y + height - 1, x + width - 1, y + height - 1); 334 g.drawLine(x + width - 1, y, x + width - 1, y + height - 1); 335 } 336 } 337 338 /** 339 * The border of the taskpane group paints the "text", the "icon", the 340 * "expanded" status and the "special" type. 341 * 342 */ 343 protected class PaneBorder implements Border { 344 345 protected Color borderColor; 346 protected Color titleForeground; 347 protected Color specialTitleBackground; 348 protected Color specialTitleForeground; 349 protected Color titleBackgroundGradientStart; 350 protected Color titleBackgroundGradientEnd; 351 352 protected Color titleOver; 353 protected Color specialTitleOver; 354 355 protected JLabel label; 356 357 public PaneBorder() { 358 borderColor = UIManager.getColor("TaskPane.borderColor"); 359 360 titleForeground = UIManager.getColor("TaskPane.titleForeground"); 361 362 specialTitleBackground = UIManager 363 .getColor("TaskPane.specialTitleBackground"); 364 specialTitleForeground = UIManager 365 .getColor("TaskPane.specialTitleForeground"); 366 367 titleBackgroundGradientStart = UIManager 368 .getColor("TaskPane.titleBackgroundGradientStart"); 369 titleBackgroundGradientEnd = UIManager 370 .getColor("TaskPane.titleBackgroundGradientEnd"); 371 372 titleOver = UIManager.getColor("TaskPane.titleOver"); 373 if (titleOver == null) { 374 titleOver = specialTitleBackground.brighter(); 375 } 376 specialTitleOver = UIManager.getColor("TaskPane.specialTitleOver"); 377 if (specialTitleOver == null) { 378 specialTitleOver = specialTitleBackground.brighter(); 379 } 380 381 label = new JLabel(); 382 label.setOpaque(false); 383 label.setIconTextGap(8); 384 } 385 386 public Insets getBorderInsets(Component c) { 387 return new Insets(getTitleHeight(), 0, 0, 0); 388 } 389 390 public boolean isBorderOpaque() { 391 return true; 392 } 393 394 /** 395 * Calculates the preferred border size, its size so all its content fits. 396 */ 397 public Dimension getPreferredSize(JXTaskPane group) { 398 // calculate the title width so it is fully visible 399 // it starts with the title width 400 configureLabel(group); 401 Dimension dim = label.getPreferredSize(); 402 // add the title left offset 403 dim.width += 3; 404 // add the controls width 405 dim.width += TITLE_HEIGHT; 406 // and some space between label and controls 407 dim.width += 3; 408 409 dim.height = getTitleHeight(); 410 return dim; 411 } 412 413 protected void paintTitleBackground(JXTaskPane group, Graphics g) { 414 if (group.isSpecial()) { 415 g.setColor(specialTitleBackground); 416 } else { 417 g.setColor(titleBackgroundGradientStart); 418 } 419 g.fillRect(0, 0, group.getWidth(), getTitleHeight() - 1); 420 } 421 422 protected void paintTitle( 423 JXTaskPane group, 424 Graphics g, 425 Color textColor, 426 int x, 427 int y, 428 int width, 429 int height) { 430 configureLabel(group); 431 label.setForeground(textColor); 432 g.translate(x, y); 433 label.setBounds(0, 0, width, height); 434 label.paint(g); 435 g.translate(-x, -y); 436 } 437 438 protected void configureLabel(JXTaskPane group) { 439 label.applyComponentOrientation(group.getComponentOrientation()); 440 label.setFont(group.getFont()); 441 label.setText(group.getTitle()); 442 label.setIcon( 443 group.getIcon() == null ? new EmptyIcon() : group.getIcon()); 444 } 445 446 protected void paintExpandedControls(JXTaskPane group, Graphics g, int x, 447 int y, int width, int height) {} 448 449 protected Color getPaintColor(JXTaskPane group) { 450 Color paintColor; 451 if (isMouseOverBorder()) { 452 if (mouseOver) { 453 if (group.isSpecial()) { 454 paintColor = specialTitleOver; 455 } else { 456 paintColor = titleOver; 457 } 458 } else { 459 if (group.isSpecial()) { 460 paintColor = specialTitleForeground; 461 } else { 462 paintColor = titleForeground; 463 } 464 } 465 } else { 466 if (group.isSpecial()) { 467 paintColor = specialTitleForeground; 468 } else { 469 paintColor = titleForeground; 470 } 471 } 472 return paintColor; 473 } 474 475 public void paintBorder( 476 Component c, 477 Graphics g, 478 int x, 479 int y, 480 int width, 481 int height) { 482 483 JXTaskPane group = (JXTaskPane)c; 484 485 // calculate position of title and toggle controls 486 int controlWidth = TITLE_HEIGHT - 2 * ROUND_HEIGHT; 487 int controlX = group.getWidth() - TITLE_HEIGHT; 488 int controlY = ROUND_HEIGHT - 1; 489 int titleX = 3; 490 int titleY = 0; 491 int titleWidth = group.getWidth() - getTitleHeight() - 3; 492 int titleHeight = getTitleHeight(); 493 494 if (!group.getComponentOrientation().isLeftToRight()) { 495 controlX = group.getWidth() - controlX - controlWidth; 496 titleX = group.getWidth() - titleX - titleWidth; 497 } 498 499 // paint the title background 500 paintTitleBackground(group, g); 501 502 // paint the the toggles 503 paintExpandedControls(group, g, controlX, controlY, controlWidth, 504 controlWidth); 505 506 // paint the title text and icon 507 Color paintColor = getPaintColor(group); 508 509 // focus painted same color as text 510 if (group.hasFocus()) { 511 paintFocus(g, 512 paintColor, 513 3, 514 3, 515 width - 6, 516 getTitleHeight() - 6); 517 } 518 519 paintTitle( 520 group, 521 g, 522 paintColor, 523 titleX, 524 titleY, 525 titleWidth, 526 titleHeight); 527 } 528 529 protected void paintRectAroundControls(JXTaskPane group, Graphics g, int x, 530 int y, int width, int height, Color highColor, Color lowColor) { 531 if (mouseOver) { 532 int x2 = x + width; 533 int y2 = y + height; 534 g.setColor(highColor); 535 g.drawLine(x, y, x2, y); 536 g.drawLine(x, y, x, y2); 537 g.setColor(lowColor); 538 g.drawLine(x2, y, x2, y2); 539 g.drawLine(x, y2, x2, y2); 540 } 541 } 542 543 protected void paintOvalAroundControls(JXTaskPane group, Graphics g, int x, 544 int y, int width, int height) { 545 if (group.isSpecial()) { 546 g.setColor(specialTitleBackground.brighter()); 547 g.drawOval( 548 x, 549 y, 550 width, 551 height); 552 } else { 553 g.setColor(titleBackgroundGradientStart); 554 g.fillOval( 555 x, 556 y, 557 width, 558 height); 559 560 g.setColor(titleBackgroundGradientEnd.darker()); 561 g.drawOval( 562 x, 563 y, 564 width, 565 width); 566 } 567 } 568 569 protected void paintChevronControls(JXTaskPane group, Graphics g, int x, 570 int y, int width, int height) { 571 ChevronIcon chevron; 572 if (group.isExpanded()) { 573 chevron = new ChevronIcon(true); 574 } else { 575 chevron = new ChevronIcon(false); 576 } 577 int chevronX = x + width / 2 - chevron.getIconWidth() / 2; 578 int chevronY = y + (height / 2 - chevron.getIconHeight()); 579 chevron.paintIcon(group, g, chevronX, chevronY); 580 chevron.paintIcon( 581 group, 582 g, 583 chevronX, 584 chevronY + chevron.getIconHeight() + 1); 585 } 586 587 protected void paintFocus(Graphics g, Color paintColor, int x, int y, int width, int height) { 588 g.setColor(paintColor); 589 BasicGraphicsUtils.drawDashedRect( 590 g, 591 x, 592 y, 593 width, 594 height); 595 } 596 597 /** 598 * Default implementation returns false. 599 * 600 * @return true if this border wants to display things differently when the 601 * mouse is over it 602 */ 603 protected boolean isMouseOverBorder() { 604 return false; 605 } 606 } 607 608 }