2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.ui
;
18 import com
.intellij
.openapi
.application
.Application
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.util
.ui
.UIUtil
;
22 import org
.jetbrains
.annotations
.NotNull
;
25 import javax
.swing
.border
.Border
;
27 import java
.util
.ArrayList
;
30 * This is high performance Swing component which represents an icon
31 * with a colored text. The text consists of fragments. Each
32 * text fragment has its own color (foreground) and font style.
34 * @author Vladimir Kondratyev
36 public class SimpleColoredComponent
extends JComponent
{
37 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ui.SimpleColoredComponent");
39 private final ArrayList
<String
> myFragments
;
40 private final ArrayList
<SimpleTextAttributes
> myAttributes
;
41 private ArrayList
<Object
> myFragmentTags
= null;
44 * Component's icon. It can be <code>null</code>.
50 private Insets myIpad
;
52 * Gap between icon and text. It is used only if icon is defined.
54 private int myIconTextGap
;
56 * Defines whether the focus border around the text is painted or not.
57 * For example, text can have a border if the component represents a selected item
60 private boolean myPaintFocusBorder
;
62 * Defines whether the focus border around the text extends to icon or not
64 private boolean myFocusBorderAroundIcon
;
66 * This is the border around the text. For example, text can have a border
67 * if the component represents a selected item in a focused JList.
68 * Border can be <code>null</code>.
70 private final MyBorder myBorder
;
72 private int myMainTextLastIndex
= -1;
74 private int myAlignIndex
;
75 private int myAlignWidth
;
77 private boolean myIconOpaque
= true;
79 public SimpleColoredComponent() {
80 myFragments
= new ArrayList
<String
>(3);
81 myAttributes
= new ArrayList
<SimpleTextAttributes
>(3);
82 myIpad
= new Insets(1, 2, 1, 2);
84 myBorder
= new MyBorder();
88 public final void append(@NotNull String fragment
) {
89 append(fragment
, SimpleTextAttributes
.REGULAR_ATTRIBUTES
);
93 * Appends string fragments to existing ones. Appended string
94 * will have specified <code>attributes</code>.
96 public final void append(@NotNull final String fragment
, @NotNull final SimpleTextAttributes attributes
) {
97 append(fragment
, attributes
, myMainTextLastIndex
< 0);
101 * Appends string fragments to existing ones. Appended string
102 * will have specified <code>attributes</code>.
104 public void append(@NotNull final String fragment
, @NotNull final SimpleTextAttributes attributes
, boolean isMainText
) {
105 synchronized (this) {
106 myFragments
.add(fragment
);
107 myAttributes
.add(attributes
);
109 myMainTextLastIndex
= myFragments
.size() - 1;
116 public void append(@NotNull final String fragment
, @NotNull final SimpleTextAttributes attributes
, Object tag
) {
117 synchronized (this) {
118 append(fragment
, attributes
);
119 if (myFragmentTags
== null) {
120 myFragmentTags
= new ArrayList
<Object
>();
122 while(myFragmentTags
.size() < myFragments
.size()-1) {
123 myFragmentTags
.add(null);
125 myFragmentTags
.add(tag
);
131 public synchronized void appendAlign(int alignWidth
) {
132 myAlignIndex
= myFragments
.size()-1;
133 myAlignWidth
= alignWidth
;
137 * Clear all special attributes of <code>SimpleColoredComponent</code>.
138 * They are icon, text fragments and their attributes, "paint focus border".
140 public void clear() {
141 synchronized (this) {
143 myPaintFocusBorder
= false;
145 myAttributes
.clear();
146 myFragmentTags
= null;
147 myMainTextLastIndex
= -1;
156 * @return component's icon. This method returns <code>null</code>
157 * if there is no icon.
159 public final Icon
getIcon() {
164 * Sets a new component icon
166 public final void setIcon(final Icon icon
) {
173 * @return "leave" (internal) internal paddings of the component
175 public Insets
getIpad() {
180 * Sets specified internal paddings
182 public void setIpad(final Insets ipad
) {
190 * @return gap between icon and text
192 public int getIconTextGap() {
193 return myIconTextGap
;
197 * Sets a new gap between icon and text
199 * @throws java.lang.IllegalArgumentException
200 * if the <code>iconTextGap</code>
201 * has a negative value
203 public void setIconTextGap(final int iconTextGap
) {
204 if (iconTextGap
< 0) {
205 throw new IllegalArgumentException("wrong iconTextGap: " + iconTextGap
);
207 myIconTextGap
= iconTextGap
;
214 * Sets whether focus border is painted or not
216 protected final void setPaintFocusBorder(final boolean paintFocusBorder
) {
217 myPaintFocusBorder
= paintFocusBorder
;
223 * Sets whether focus border extends to icon or not. If so then
224 * component also extends the selection.
226 protected final void setFocusBorderAroundIcon(final boolean focusBorderAroundIcon
) {
227 myFocusBorderAroundIcon
= focusBorderAroundIcon
;
232 public boolean isIconOpaque() {
236 public void setIconOpaque(final boolean iconOpaque
) {
237 myIconOpaque
= iconOpaque
;
242 public Dimension
getPreferredSize() {
243 return computePreferredSize(false);
247 public synchronized Object
getFragmentTag(int index
) {
248 if (myFragmentTags
!= null && index
< myFragmentTags
.size()) {
249 return myFragmentTags
.get(index
);
254 public final synchronized Dimension
computePreferredSize(final boolean mainTextOnly
) {
256 int width
= myIpad
.left
;
258 if (myIcon
!= null) {
259 width
+= myIcon
.getIconWidth() + myIconTextGap
;
262 final Insets borderInsets
= myBorder
.getBorderInsets(this);
263 width
+= borderInsets
.left
;
265 Font font
= getFont();
267 font
= UIManager
.getFont("Label.font");
270 LOG
.assertTrue(font
!= null);
272 for (int i
= 0; i
< myAttributes
.size(); i
++) {
273 SimpleTextAttributes attributes
= myAttributes
.get(i
);
274 if (font
.getStyle() != attributes
.getStyle()) { // derive font only if it is necessary
275 font
= font
.deriveFont(attributes
.getStyle());
277 final FontMetrics metrics
= getFontMetrics(font
);
278 width
+= metrics
.stringWidth(myFragments
.get(i
));
279 if (i
== myAlignIndex
&& width
< myAlignWidth
) {
280 width
= myAlignWidth
;
283 if (mainTextOnly
&& myMainTextLastIndex
>= 0 && i
== myMainTextLastIndex
) break;
285 width
+= myIpad
.right
+ borderInsets
.right
;
288 int height
= myIpad
.top
+ myIpad
.bottom
;
290 final FontMetrics metrics
= getFontMetrics(font
);
291 int textHeight
= metrics
.getHeight();
292 textHeight
+= borderInsets
.top
+ borderInsets
.bottom
;
294 if (myIcon
!= null) {
295 height
+= Math
.max(myIcon
.getIconHeight(), textHeight
);
298 height
+= textHeight
;
301 // Take into accound that the component itself can have a border
302 final Insets insets
= getInsets();
303 width
+= insets
.left
+ insets
.right
;
304 height
+= insets
.top
+ insets
.bottom
;
306 return new Dimension(width
, height
);
309 public int findFragmentAt(int x
) {
310 int curX
= myIpad
.left
;
311 if (myIcon
!= null) {
312 curX
+= myIcon
.getIconWidth() + myIconTextGap
;
315 Font font
= getFont();
316 LOG
.assertTrue(font
!= null);
318 for (int i
= 0; i
< myAttributes
.size(); i
++) {
319 SimpleTextAttributes attributes
= myAttributes
.get(i
);
320 if (font
.getStyle() != attributes
.getStyle()) { // derive font only if it is necessary
321 font
= font
.deriveFont(attributes
.getStyle());
323 final FontMetrics metrics
= getFontMetrics(font
);
324 final int curWidth
= metrics
.stringWidth(myFragments
.get(i
));
325 if (x
>= curX
&& x
< curX
+ curWidth
) {
329 if (i
== myAlignIndex
&& curX
< myAlignWidth
) {
336 protected void paintComponent(final Graphics g
) {
340 catch (RuntimeException e
) {
341 LOG
.error(logSwingPath(), e
);
346 protected synchronized void doPaint(final Graphics g
) {
350 // Paint icon and its background
351 final Icon icon
= myIcon
; // guard against concurrent modification (IDEADEV-12635)
353 final Container parent
= getParent();
354 Color iconBackgroundColor
= null;
355 if (isIconOpaque()) {
356 if (parent
!= null && !myFocusBorderAroundIcon
&& !UIUtil
.isFullRowSelectionLAF()) {
357 iconBackgroundColor
= parent
.getBackground();
360 iconBackgroundColor
= getBackground();
364 if (iconBackgroundColor
!= null) {
365 g
.setColor(iconBackgroundColor
);
366 g
.fillRect(0, 0, icon
.getIconWidth() + myIpad
.left
+ myIconTextGap
, getHeight());
369 icon
.paintIcon(this, g
, myIpad
.left
, (getHeight() - icon
.getIconHeight()) / 2);
371 xOffset
+= myIpad
.left
+ icon
.getIconWidth() + myIconTextGap
;
375 // Paint text background
376 g
.setColor(getBackground());
377 g
.fillRect(xOffset
, 0, getWidth() - xOffset
, getHeight());
380 // If there is no icon, then we have to add left internal padding
382 xOffset
= myIpad
.left
;
385 int textStart
= xOffset
;
386 xOffset
+= myBorder
.getBorderInsets(this).left
;
389 UIUtil
.applyRenderingHints(g
);
390 for (int i
= 0; i
< myFragments
.size(); i
++) {
391 final SimpleTextAttributes attributes
= myAttributes
.get(i
);
392 Font font
= getFont();
393 if (font
.getStyle() != attributes
.getStyle()) { // derive font only if it is necessary
394 font
= font
.deriveFont(attributes
.getStyle());
397 final FontMetrics metrics
= getFontMetrics(font
);
399 final String fragment
= myFragments
.get(i
);
400 final int fragmentWidth
= metrics
.stringWidth(fragment
);
402 final Color bgColor
= attributes
.getBgColor();
403 if (isOpaque() && bgColor
!= null) {
405 g
.fillRect(xOffset
, 0, fragmentWidth
, getHeight());
408 Color color
= attributes
.getFgColor();
409 if (color
== null) { // in case if color is not defined we have to get foreground color from Swing hierarchy
410 color
= getForeground();
413 color
= UIUtil
.getTextInactiveTextColor();
417 final int textBaseline
= (getHeight() - metrics
.getHeight()) / 2 + metrics
.getAscent();
418 g
.drawString(fragment
, xOffset
, textBaseline
);
420 // 1. Strikeout effect
421 if (attributes
.isStrikeout()) {
422 final int strikeOutAt
= textBaseline
+ (metrics
.getDescent() - metrics
.getAscent()) / 2;
423 UIUtil
.drawLine(g
, xOffset
, strikeOutAt
, xOffset
+ fragmentWidth
, strikeOutAt
);
426 if (attributes
.isWaved()) {
427 if (attributes
.getWaveColor() != null) {
428 g
.setColor(attributes
.getWaveColor());
430 final int wavedAt
= textBaseline
+ 1;
431 for (int x
= xOffset
; x
<= xOffset
+ fragmentWidth
; x
+= 4) {
432 UIUtil
.drawLine(g
, x
, wavedAt
, x
+ 2, wavedAt
+ 2);
433 UIUtil
.drawLine(g
, x
+ 3, wavedAt
+ 1, x
+ 4, wavedAt
);
436 if (attributes
.isUnderline()) {
437 final int underlineAt
= textBaseline
+ 1;
438 UIUtil
.drawLine(g
, xOffset
, underlineAt
, xOffset
+ fragmentWidth
, underlineAt
);
442 xOffset
+= fragmentWidth
;
443 if (i
== myAlignIndex
&& xOffset
< myAlignWidth
) {
444 xOffset
= myAlignWidth
;
448 // Paint focus border around the text and icon (if necessary)
449 if (myPaintFocusBorder
) {
450 if (myFocusBorderAroundIcon
|| icon
== null) {
451 myBorder
.paintBorder(this, g
, 0, 0, getWidth(), getHeight());
454 myBorder
.paintBorder(this, g
, textStart
, 0, getWidth() - textStart
, getHeight());
459 private static void checkCanPaint(Graphics g
) {
460 if (UIUtil
.isPrinting(g
)) return;
463 if (!isDisplayable()) {
464 LOG.assertTrue(false, logSwingPath());
467 final Application application
= ApplicationManager
.getApplication();
468 if (application
!= null) {
469 application
.assertIsDispatchThread();
471 else if (!SwingUtilities
.isEventDispatchThread()) {
472 throw new RuntimeException(Thread
.currentThread().toString());
476 private String
logSwingPath() {
477 //noinspection HardCodedStringLiteral
478 final StringBuilder buffer
= new StringBuilder("Components hierarchy:\n");
479 for (Container c
= this; c
!= null; c
= c
.getParent()) {
481 buffer
.append(c
.toString());
483 return buffer
.toString();
486 protected void setBorderInsets(Insets insets
) {
487 myBorder
.setInsets(insets
);
493 private static final class MyBorder
implements Border
{
494 private Insets myInsets
;
497 myInsets
= new Insets(1, 1, 1, 1);
500 public void setInsets(final Insets insets
) {
504 public void paintBorder(final Component c
, final Graphics g
, final int x
, final int y
, final int width
, final int height
) {
505 g
.setColor(Color
.BLACK
);
506 UIUtil
.drawDottedRectangle(g
, x
, y
, x
+ width
- 1, y
+ height
- 1);
509 public Insets
getBorderInsets(final Component c
) {
513 public boolean isBorderOpaque() {
519 public String
toString() {
520 StringBuffer result
= new StringBuffer();
521 for (String each
: myFragments
) {
525 return result
.toString();