1 /* 2 * Copyright (c) 2004, 2006, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.tools.jconsole; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 31 import javax.swing.*; 32 import javax.swing.border.*; 33 import javax.swing.plaf.*; 34 import javax.swing.plaf.basic.BasicGraphicsUtils; 35 36 import sun.tools.jconsole.resources.Messages; 37 38 import static javax.swing.SwingConstants.*; 39 40 import static sun.tools.jconsole.JConsole.*; 41 42 @SuppressWarnings("serial") 43 public class BorderedComponent extends JPanel implements ActionListener { 44 JButton moreOrLessButton; 45 String valueLabelStr; 46 JLabel label; 47 JComponent comp; 48 boolean collapsed = false; 49 50 private Icon collapseIcon; 51 private Icon expandIcon; 52 53 private static Image getImage(String name) { 54 Toolkit tk = Toolkit.getDefaultToolkit(); 55 name = "resources/" + name + ".png"; 56 return tk.getImage(BorderedComponent.class.getResource(name)); 57 } 58 59 public BorderedComponent(String text) { 60 this(text, null, false); 61 } 62 63 public BorderedComponent(String text, JComponent comp) { 64 this(text, comp, false); 65 } 66 67 public BorderedComponent(String text, JComponent comp, boolean collapsible) { 68 super(null); 69 70 this.comp = comp; 71 72 // Only add border if text is not null 73 if (text != null) { 74 TitledBorder border; 75 if (collapsible) { 76 final JLabel textLabel = new JLabel(text); 77 JPanel borderLabel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0)) { 78 public int getBaseline(int w, int h) { 79 Dimension dim = textLabel.getPreferredSize(); 80 return textLabel.getBaseline(dim.width, dim.height) + textLabel.getY(); 81 } 82 }; 83 borderLabel.add(textLabel); 84 border = new LabeledBorder(borderLabel); 85 textLabel.setForeground(border.getTitleColor()); 86 87 if (IS_WIN) { 88 collapseIcon = new ImageIcon(getImage("collapse-winlf")); 89 expandIcon = new ImageIcon(getImage("expand-winlf")); 90 } else { 91 collapseIcon = new ArrowIcon(SOUTH, textLabel); 92 expandIcon = new ArrowIcon(EAST, textLabel); 93 } 94 95 moreOrLessButton = new JButton(collapseIcon); 96 moreOrLessButton.setContentAreaFilled(false); 97 moreOrLessButton.setBorderPainted(false); 98 moreOrLessButton.setMargin(new Insets(0, 0, 0, 0)); 99 moreOrLessButton.addActionListener(this); 100 String toolTip = 101 Messages.BORDERED_COMPONENT_MORE_OR_LESS_BUTTON_TOOLTIP; 102 moreOrLessButton.setToolTipText(toolTip); 103 borderLabel.add(moreOrLessButton); 104 borderLabel.setSize(borderLabel.getPreferredSize()); 105 add(borderLabel); 106 } else { 107 border = new TitledBorder(text); 108 } 109 setBorder(new CompoundBorder(new FocusBorder(this), border)); 110 } else { 111 setBorder(new FocusBorder(this)); 112 } 113 if (comp != null) { 114 add(comp); 115 } 116 } 117 118 public void setComponent(JComponent comp) { 119 if (this.comp != null) { 120 remove(this.comp); 121 } 122 this.comp = comp; 123 if (!collapsed) { 124 LayoutManager lm = getLayout(); 125 if (lm instanceof BorderLayout) { 126 add(comp, BorderLayout.CENTER); 127 } else { 128 add(comp); 129 } 130 } 131 revalidate(); 132 } 133 134 public void setValueLabel(String str) { 135 this.valueLabelStr = str; 136 if (label != null) { 137 label.setText(Resources.format(Messages.CURRENT_VALUE, valueLabelStr)); 138 } 139 } 140 141 public void actionPerformed(ActionEvent ev) { 142 if (collapsed) { 143 if (label != null) { 144 remove(label); 145 } 146 add(comp); 147 moreOrLessButton.setIcon(collapseIcon); 148 } else { 149 remove(comp); 150 if (valueLabelStr != null) { 151 if (label == null) { 152 label = new JLabel(Resources.format(Messages.CURRENT_VALUE, 153 valueLabelStr)); 154 } 155 add(label); 156 } 157 moreOrLessButton.setIcon(expandIcon); 158 } 159 collapsed = !collapsed; 160 161 JComponent container = (JComponent)getParent(); 162 if (container != null && 163 container.getLayout() instanceof VariableGridLayout) { 164 165 ((VariableGridLayout)container.getLayout()).setFillRow(this, !collapsed); 166 container.revalidate(); 167 } 168 } 169 170 public Dimension getMinimumSize() { 171 if (getLayout() != null) { 172 // A layout manager has been set, so delegate to it 173 return super.getMinimumSize(); 174 } 175 176 if (moreOrLessButton != null) { 177 Dimension d = moreOrLessButton.getMinimumSize(); 178 Insets i = getInsets(); 179 d.width += i.left + i.right; 180 d.height += i.top + i.bottom; 181 return d; 182 } else { 183 return super.getMinimumSize(); 184 } 185 } 186 187 public void doLayout() { 188 if (getLayout() != null) { 189 // A layout manager has been set, so delegate to it 190 super.doLayout(); 191 return; 192 } 193 194 Dimension d = getSize(); 195 Insets i = getInsets(); 196 197 if (collapsed) { 198 if (label != null) { 199 Dimension p = label.getPreferredSize(); 200 label.setBounds(i.left, 201 i.top + (d.height - i.top - i.bottom - p.height) / 2, 202 p.width, 203 p.height); 204 } 205 } else { 206 if (comp != null) { 207 comp.setBounds(i.left, 208 i.top, 209 d.width - i.left - i.right, 210 d.height - i.top - i.bottom); 211 } 212 } 213 } 214 215 private static class ArrowIcon implements Icon { 216 private int direction; 217 private JLabel textLabel; 218 219 public ArrowIcon(int direction, JLabel textLabel) { 220 this.direction = direction; 221 this.textLabel = textLabel; 222 } 223 224 public void paintIcon(Component c, Graphics g, int x, int y) { 225 int w = getIconWidth(); 226 int h = w; 227 Polygon p = new Polygon(); 228 switch (direction) { 229 case EAST: 230 p.addPoint(x + 2, y); 231 p.addPoint(x + w - 2, y + h / 2); 232 p.addPoint(x + 2, y + h - 1); 233 break; 234 235 case SOUTH: 236 p.addPoint(x, y + 2); 237 p.addPoint(x + w / 2, y + h - 2); 238 p.addPoint(x + w - 1, y + 2); 239 break; 240 } 241 g.fillPolygon(p); 242 } 243 244 public int getIconWidth() { 245 return getIconHeight(); 246 } 247 248 public int getIconHeight() { 249 Graphics g = textLabel.getGraphics(); 250 if (g != null) { 251 int h = g.getFontMetrics(textLabel.getFont()).getAscent() * 6/10; 252 if (h % 2 == 0) { 253 h += 1; // Make it odd 254 } 255 return h; 256 } else { 257 return 7; 258 } 259 } 260 } 261 262 263 /** 264 * A subclass of <code>TitledBorder</code> which implements an arbitrary border 265 * with the addition of a JComponent (JLabel, JPanel, etc) in the 266 * default position. 267 * <p> 268 * If the border property value is not 269 * specified in the constuctor or by invoking the appropriate 270 * set method, the property value will be defined by the current 271 * look and feel, using the following property name in the 272 * Defaults Table: 273 * <ul> 274 * <li>"TitledBorder.border" 275 * </ul> 276 */ 277 protected static class LabeledBorder extends TitledBorder { 278 protected JComponent label; 279 280 private Point compLoc = new Point(); 281 282 /** 283 * Creates a LabeledBorder instance. 284 * 285 * @param label the label the border should display 286 */ 287 public LabeledBorder(JComponent label) { 288 this(null, label); 289 } 290 291 /** 292 * Creates a LabeledBorder instance with the specified border 293 * and an empty label. 294 * 295 * @param border the border 296 */ 297 public LabeledBorder(Border border) { 298 this(border, null); 299 } 300 301 /** 302 * Creates a LabeledBorder instance with the specified border and 303 * label. 304 * 305 * @param border the border 306 * @param label the label the border should display 307 */ 308 public LabeledBorder(Border border, JComponent label) { 309 super(border); 310 311 this.label = label; 312 313 if (label instanceof JLabel && 314 label.getForeground() instanceof ColorUIResource) { 315 316 label.setForeground(getTitleColor()); 317 } 318 319 } 320 321 /** 322 * Paints the border for the specified component with the 323 * specified position and size. 324 * @param c the component for which this border is being painted 325 * @param g the paint graphics 326 * @param x the x position of the painted border 327 * @param y the y position of the painted border 328 * @param width the width of the painted border 329 * @param height the height of the painted border 330 */ 331 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 332 333 Border border = getBorder(); 334 335 if (label == null) { 336 if (border != null) { 337 border.paintBorder(c, g, x, y, width, height); 338 } 339 return; 340 } 341 342 Rectangle grooveRect = new Rectangle(x + EDGE_SPACING, y + EDGE_SPACING, 343 width - (EDGE_SPACING * 2), 344 height - (EDGE_SPACING * 2)); 345 346 Dimension labelDim = label.getPreferredSize(); 347 int baseline = label.getBaseline(labelDim.width, labelDim.height); 348 int ascent = Math.max(0, baseline); 349 int descent = labelDim.height - ascent; 350 int diff; 351 Insets insets; 352 353 if (border != null) { 354 insets = border.getBorderInsets(c); 355 } else { 356 insets = new Insets(0, 0, 0, 0); 357 } 358 359 diff = Math.max(0, ascent/2 + TEXT_SPACING - EDGE_SPACING); 360 grooveRect.y += diff; 361 grooveRect.height -= diff; 362 compLoc.y = grooveRect.y + insets.top/2 - (ascent + descent) / 2 - 1; 363 364 int justification; 365 if (c.getComponentOrientation().isLeftToRight()) { 366 justification = LEFT; 367 } else { 368 justification = RIGHT; 369 } 370 371 switch (justification) { 372 case LEFT: 373 compLoc.x = grooveRect.x + TEXT_INSET_H + insets.left; 374 break; 375 case RIGHT: 376 compLoc.x = (grooveRect.x + grooveRect.width 377 - (labelDim.width + TEXT_INSET_H + insets.right)); 378 break; 379 } 380 381 // If title is positioned in middle of border AND its fontsize 382 // is greater than the border's thickness, we'll need to paint 383 // the border in sections to leave space for the component's background 384 // to show through the title. 385 // 386 if (border != null) { 387 if (grooveRect.y > compLoc.y - ascent) { 388 Rectangle clipRect = new Rectangle(); 389 390 // save original clip 391 Rectangle saveClip = g.getClipBounds(); 392 393 // paint strip left of text 394 clipRect.setBounds(saveClip); 395 if (computeIntersection(clipRect, x, y, compLoc.x-1-x, height)) { 396 g.setClip(clipRect); 397 border.paintBorder(c, g, grooveRect.x, grooveRect.y, 398 grooveRect.width, grooveRect.height); 399 } 400 401 // paint strip right of text 402 clipRect.setBounds(saveClip); 403 if (computeIntersection(clipRect, compLoc.x+ labelDim.width +1, y, 404 x+width-(compLoc.x+ labelDim.width +1), height)) { 405 g.setClip(clipRect); 406 border.paintBorder(c, g, grooveRect.x, grooveRect.y, 407 grooveRect.width, grooveRect.height); 408 } 409 410 // paint strip below text 411 clipRect.setBounds(saveClip); 412 if (computeIntersection(clipRect, 413 compLoc.x - 1, compLoc.y + ascent + descent, 414 labelDim.width + 2, 415 y + height - compLoc.y - ascent - descent)) { 416 g.setClip(clipRect); 417 border.paintBorder(c, g, grooveRect.x, grooveRect.y, 418 grooveRect.width, grooveRect.height); 419 } 420 421 // restore clip 422 g.setClip(saveClip); 423 424 } else { 425 border.paintBorder(c, g, grooveRect.x, grooveRect.y, 426 grooveRect.width, grooveRect.height); 427 } 428 429 label.setLocation(compLoc); 430 label.setSize(labelDim); 431 } 432 } 433 434 /** 435 * Reinitialize the insets parameter with this Border's current Insets. 436 * @param c the component for which this border insets value applies 437 * @param insets the object to be reinitialized 438 */ 439 public Insets getBorderInsets(Component c, Insets insets) { 440 Border border = getBorder(); 441 if (border != null) { 442 if (border instanceof AbstractBorder) { 443 ((AbstractBorder)border).getBorderInsets(c, insets); 444 } else { 445 // Can't reuse border insets because the Border interface 446 // can't be enhanced. 447 Insets i = border.getBorderInsets(c); 448 insets.top = i.top; 449 insets.right = i.right; 450 insets.bottom = i.bottom; 451 insets.left = i.left; 452 } 453 } else { 454 insets.left = insets.top = insets.right = insets.bottom = 0; 455 } 456 457 insets.left += EDGE_SPACING + TEXT_SPACING; 458 insets.right += EDGE_SPACING + TEXT_SPACING; 459 insets.top += EDGE_SPACING + TEXT_SPACING; 460 insets.bottom += EDGE_SPACING + TEXT_SPACING; 461 462 if (c == null || label == null) { 463 return insets; 464 } 465 466 insets.top += label.getHeight(); 467 468 return insets; 469 } 470 471 /** 472 * Returns the label of the labeled border. 473 */ 474 public JComponent getLabel() { 475 return label; 476 } 477 478 479 /** 480 * Sets the title of the titled border. 481 * param title the title for the border 482 */ 483 public void setLabel(JComponent label) { 484 this.label = label; 485 } 486 487 488 489 /** 490 * Returns the minimum dimensions this border requires 491 * in order to fully display the border and title. 492 * @param c the component where this border will be drawn 493 */ 494 public Dimension getMinimumSize(Component c) { 495 Insets insets = getBorderInsets(c); 496 Dimension minSize = new Dimension(insets.right + insets.left, 497 insets.top + insets.bottom); 498 minSize.width += label.getWidth(); 499 500 return minSize; 501 } 502 503 504 private static boolean computeIntersection(Rectangle dest, 505 int rx, int ry, int rw, int rh) { 506 int x1 = Math.max(rx, dest.x); 507 int x2 = Math.min(rx + rw, dest.x + dest.width); 508 int y1 = Math.max(ry, dest.y); 509 int y2 = Math.min(ry + rh, dest.y + dest.height); 510 dest.x = x1; 511 dest.y = y1; 512 dest.width = x2 - x1; 513 dest.height = y2 - y1; 514 515 if (dest.width <= 0 || dest.height <= 0) { 516 return false; 517 } 518 return true; 519 } 520 } 521 522 523 protected static class FocusBorder extends AbstractBorder implements FocusListener { 524 private Component comp; 525 private Color focusColor; 526 private boolean focusLostTemporarily = false; 527 528 public FocusBorder(Component comp) { 529 this.comp = comp; 530 531 comp.addFocusListener(this); 532 533 // This is the best guess for a L&F specific color 534 focusColor = UIManager.getColor("TabbedPane.focus"); 535 } 536 537 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 538 if (comp.hasFocus() || focusLostTemporarily) { 539 Color color = g.getColor(); 540 g.setColor(focusColor); 541 BasicGraphicsUtils.drawDashedRect(g, x, y, width, height); 542 g.setColor(color); 543 } 544 } 545 546 public Insets getBorderInsets(Component c, Insets insets) { 547 insets.set(2, 2, 2, 2); 548 return insets; 549 } 550 551 552 public void focusGained(FocusEvent e) { 553 comp.repaint(); 554 } 555 556 public void focusLost(FocusEvent e) { 557 // We will still paint focus even if lost temporarily 558 focusLostTemporarily = e.isTemporary(); 559 if (!focusLostTemporarily) { 560 comp.repaint(); 561 } 562 } 563 } 564 }