When checking for canPaint() && isDispatchThread there should be check if we're print...
[fedora-idea.git] / platform / platform-api / src / com / intellij / ui / SimpleColoredComponent.java
blob3c30a2c3e74afea81dc01e0cc9ce59e7caac640e
1 /*
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;
24 import javax.swing.*;
25 import javax.swing.border.Border;
26 import java.awt.*;
27 import java.util.ArrayList;
29 /**
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;
43 /**
44 * Component's icon. It can be <code>null</code>.
46 private Icon myIcon;
47 /**
48 * Internal padding
50 private Insets myIpad;
51 /**
52 * Gap between icon and text. It is used only if icon is defined.
54 private int myIconTextGap;
55 /**
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
58 * in focused JList.
60 private boolean myPaintFocusBorder;
61 /**
62 * Defines whether the focus border around the text extends to icon or not
64 private boolean myFocusBorderAroundIcon;
65 /**
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);
83 myIconTextGap = 2;
84 myBorder = new MyBorder();
85 setOpaque(true);
88 public final void append(@NotNull String fragment) {
89 append(fragment, SimpleTextAttributes.REGULAR_ATTRIBUTES);
92 /**
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);
108 if (isMainText) {
109 myMainTextLastIndex = myFragments.size() - 1;
112 revalidate();
113 repaint();
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);
127 revalidate();
128 repaint();
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) {
142 myIcon = null;
143 myPaintFocusBorder = false;
144 myFragments.clear();
145 myAttributes.clear();
146 myFragmentTags = null;
147 myMainTextLastIndex = -1;
148 myAlignIndex = -1;
149 myAlignWidth = -1;
151 revalidate();
152 repaint();
156 * @return component's icon. This method returns <code>null</code>
157 * if there is no icon.
159 public final Icon getIcon() {
160 return myIcon;
164 * Sets a new component icon
166 public final void setIcon(final Icon icon) {
167 myIcon = icon;
168 revalidate();
169 repaint();
173 * @return "leave" (internal) internal paddings of the component
175 public Insets getIpad() {
176 return myIpad;
180 * Sets specified internal paddings
182 public void setIpad(final Insets ipad) {
183 myIpad = ipad;
185 revalidate();
186 repaint();
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;
209 revalidate();
210 repaint();
214 * Sets whether focus border is painted or not
216 protected final void setPaintFocusBorder(final boolean paintFocusBorder) {
217 myPaintFocusBorder = paintFocusBorder;
219 repaint();
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;
229 repaint();
232 public boolean isIconOpaque() {
233 return myIconOpaque;
236 public void setIconOpaque(final boolean iconOpaque) {
237 myIconOpaque = iconOpaque;
239 repaint();
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);
251 return null;
254 public final synchronized Dimension computePreferredSize(final boolean mainTextOnly) {
255 // Calculate width
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();
266 if (font == null) {
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;
287 // Calculate height
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);
297 else {
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) {
326 return i;
328 curX += curWidth;
329 if (i == myAlignIndex && curX < myAlignWidth) {
330 curX = myAlignWidth;
333 return -1;
336 protected void paintComponent(final Graphics g) {
337 try {
338 doPaint(g);
340 catch (RuntimeException e) {
341 LOG.error(logSwingPath(), e);
342 throw e;
346 protected synchronized void doPaint(final Graphics g) {
347 checkCanPaint(g);
348 int xOffset = 0;
350 // Paint icon and its background
351 final Icon icon = myIcon; // guard against concurrent modification (IDEADEV-12635)
352 if (icon != null) {
353 final Container parent = getParent();
354 Color iconBackgroundColor = null;
355 if (isIconOpaque()) {
356 if (parent != null && !myFocusBorderAroundIcon && !UIUtil.isFullRowSelectionLAF()) {
357 iconBackgroundColor = parent.getBackground();
359 else {
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;
374 if (isOpaque()) {
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
381 if (xOffset == 0) {
382 xOffset = myIpad.left;
385 int textStart = xOffset;
386 xOffset += myBorder.getBorderInsets(this).left;
388 // Paint text
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());
396 g.setFont(font);
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) {
404 g.setColor(bgColor);
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();
412 if (!isEnabled()) {
413 color = UIUtil.getTextInactiveTextColor();
415 g.setColor(color);
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);
425 // 2. Waved effect
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());
453 else {
454 myBorder.paintBorder(this, g, textStart, 0, getWidth() - textStart, getHeight());
459 private static void checkCanPaint(Graphics g) {
460 if (UIUtil.isPrinting(g)) return;
462 /* wtf??
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()) {
480 buffer.append('\n');
481 buffer.append(c.toString());
483 return buffer.toString();
486 protected void setBorderInsets(Insets insets) {
487 myBorder.setInsets(insets);
489 revalidate();
490 repaint();
493 private static final class MyBorder implements Border {
494 private Insets myInsets;
496 public MyBorder() {
497 myInsets = new Insets(1, 1, 1, 1);
500 public void setInsets(final Insets insets) {
501 myInsets = 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) {
510 return myInsets;
513 public boolean isBorderOpaque() {
514 return true;
518 @Override
519 public String toString() {
520 StringBuffer result = new StringBuffer();
521 for (String each : myFragments) {
522 result.append(each);
525 return result.toString();