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>&quot;TitledBorder.border&quot;
 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 }