1 /*
2  * Copyright (c) 2004, 2013, 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 
37 import static javax.swing.SwingConstants.*;
38 
39 import static sun.tools.jconsole.JConsole.*;
40 
41 @SuppressWarnings("serial")
42 public class BorderedComponent extends JPanel implements ActionListener {
43     JButton moreOrLessButton;
44     String valueLabelStr;
45     JLabel label;
46     JComponent comp;
47     boolean collapsed = false;
48 
49     private Icon collapseIcon;
50     private Icon expandIcon;
51 
getImage(String name)52     private static Image getImage(String name) {
53         Toolkit tk = Toolkit.getDefaultToolkit();
54         name = "resources/" + name + ".png";
55         return tk.getImage(BorderedComponent.class.getResource(name));
56     }
57 
BorderedComponent(String text)58     public BorderedComponent(String text) {
59         this(text, null, false);
60     }
61 
BorderedComponent(String text, JComponent comp)62     public BorderedComponent(String text, JComponent comp) {
63         this(text, comp, false);
64     }
65 
BorderedComponent(String text, JComponent comp, boolean collapsible)66     public BorderedComponent(String text, JComponent comp, boolean collapsible) {
67         super(null);
68 
69         this.comp = comp;
70 
71         // Only add border if text is not null
72         if (text != null) {
73             TitledBorder border;
74             if (collapsible) {
75                 final JLabel textLabel = new JLabel(text);
76                 JPanel borderLabel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0)) {
77                     public int getBaseline(int w, int h) {
78                         Dimension dim = textLabel.getPreferredSize();
79                         return textLabel.getBaseline(dim.width, dim.height) + textLabel.getY();
80                     }
81                 };
82                 borderLabel.add(textLabel);
83                 border = new LabeledBorder(borderLabel);
84                 textLabel.setForeground(border.getTitleColor());
85 
86                 if (IS_WIN) {
87                     collapseIcon = new ImageIcon(getImage("collapse-winlf"));
88                     expandIcon = new ImageIcon(getImage("expand-winlf"));
89                 } else {
90                     collapseIcon = new ArrowIcon(SOUTH, textLabel);
91                     expandIcon = new ArrowIcon(EAST, textLabel);
92                 }
93 
94                 moreOrLessButton = new JButton(collapseIcon);
95                 moreOrLessButton.setContentAreaFilled(false);
96                 moreOrLessButton.setBorderPainted(false);
97                 moreOrLessButton.setMargin(new Insets(0, 0, 0, 0));
98                 moreOrLessButton.addActionListener(this);
99                 String toolTip =
100                     Messages.BORDERED_COMPONENT_MORE_OR_LESS_BUTTON_TOOLTIP;
101                 moreOrLessButton.setToolTipText(toolTip);
102                 borderLabel.add(moreOrLessButton);
103                 borderLabel.setSize(borderLabel.getPreferredSize());
104                 add(borderLabel);
105             } else {
106                 border = new TitledBorder(text);
107             }
108             setBorder(new CompoundBorder(new FocusBorder(this), border));
109         } else {
110             setBorder(new FocusBorder(this));
111         }
112         if (comp != null) {
113             add(comp);
114         }
115     }
116 
setComponent(JComponent comp)117     public void setComponent(JComponent comp) {
118         if (this.comp != null) {
119             remove(this.comp);
120         }
121         this.comp = comp;
122         if (!collapsed) {
123             LayoutManager lm = getLayout();
124             if (lm instanceof BorderLayout) {
125                 add(comp, BorderLayout.CENTER);
126             } else {
127                 add(comp);
128             }
129         }
130         revalidate();
131     }
132 
setValueLabel(String str)133     public void setValueLabel(String str) {
134         this.valueLabelStr = str;
135         if (label != null) {
136             label.setText(Resources.format(Messages.CURRENT_VALUE,
137                                            valueLabelStr));
138         }
139     }
140 
actionPerformed(ActionEvent ev)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 
getMinimumSize()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 
doLayout()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 
ArrowIcon(int direction, JLabel textLabel)219         public ArrowIcon(int direction, JLabel textLabel) {
220             this.direction = direction;
221             this.textLabel = textLabel;
222         }
223 
paintIcon(Component c, Graphics g, int x, int y)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 
getIconWidth()244         public int getIconWidth() {
245             return getIconHeight();
246         }
247 
getIconHeight()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 constructor 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          */
LabeledBorder(JComponent label)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          */
LabeledBorder(Border border)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          */
LabeledBorder(Border border, JComponent label)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          */
paintBorder(Component c, Graphics g, int x, int y, int width, int height)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          */
getBorderInsets(Component c, Insets insets)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          */
getLabel()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          */
setLabel(JComponent label)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          */
getMinimumSize(Component c)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 
computeIntersection(Rectangle dest, int rx, int ry, int rw, int rh)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 
FocusBorder(Component comp)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 
paintBorder(Component c, Graphics g, int x, int y, int width, int height)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 
getBorderInsets(Component c, Insets insets)546         public Insets getBorderInsets(Component c, Insets insets) {
547             insets.set(2, 2, 2, 2);
548             return insets;
549         }
550 
551 
focusGained(FocusEvent e)552         public void focusGained(FocusEvent e) {
553             comp.repaint();
554         }
555 
focusLost(FocusEvent e)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 }
565