| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.intellij.ui; |
| |
| import com.intellij.openapi.editor.markup.EffectType; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.util.ui.UIUtil; |
| import gnu.trove.TIntObjectHashMap; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.util.ArrayList; |
| |
| /** |
| * @author Eugene Belyaev |
| */ |
| public class HighlightableComponent extends JComponent { |
| protected String myText = ""; |
| protected Icon myIcon; |
| protected int myIconTextGap; |
| protected ArrayList<HighlightedRegion> myHighlightedRegions; |
| protected TIntObjectHashMap<FontMetrics> myFontMetrics; |
| protected boolean myIsSelected; |
| protected boolean myHasFocus; |
| protected boolean myPaintUnfocusedSelection = false; |
| private boolean myDoNotHighlight = false; |
| |
| public HighlightableComponent() { |
| myIconTextGap = 4; |
| myFontMetrics = new TIntObjectHashMap<FontMetrics>(); |
| setText(""); |
| fillFontMetricsMap(); |
| setOpaque(true); |
| } |
| |
| protected void fillFontMetricsMap() { |
| Font font = getFont(); |
| if (font != null){ |
| myFontMetrics.put(Font.PLAIN, getFontMetrics(font.deriveFont(Font.PLAIN))); |
| myFontMetrics.put(Font.BOLD, getFontMetrics(font.deriveFont(Font.BOLD))); |
| myFontMetrics.put(Font.ITALIC, getFontMetrics(font.deriveFont(Font.ITALIC))); |
| myFontMetrics.put(Font.BOLD | Font.ITALIC, getFontMetrics(font.deriveFont(Font.BOLD | Font.ITALIC))); |
| } |
| } |
| |
| public void setText(String text) { |
| if (text == null) { |
| text = ""; |
| } |
| myText = text; |
| myHighlightedRegions = new ArrayList<HighlightedRegion>(4); |
| } |
| |
| public void setIcon(Icon icon) { |
| myIcon = icon; |
| } |
| |
| public void setFont(Font font) { |
| if (!font.equals(getFont())){ |
| super.setFont(font); |
| fillFontMetricsMap(); |
| } |
| } |
| |
| public void addHighlighter(int startOffset, int endOffset, TextAttributes attributes) { |
| addHighlighter(0, startOffset, endOffset, attributes); |
| } |
| |
| private void addHighlighter(int startIndex, int startOffset, int endOffset, TextAttributes attributes) { |
| if (startOffset < 0) startOffset = 0; |
| if (endOffset > myText.length()) endOffset = myText.length(); |
| |
| if (startOffset >= endOffset) return; |
| |
| if (myHighlightedRegions.size() == 0){ |
| myHighlightedRegions.add(new HighlightedRegion(startOffset, endOffset, attributes)); |
| } |
| else{ |
| for(int i = startIndex; i < myHighlightedRegions.size(); i++){ |
| HighlightedRegion hRegion = myHighlightedRegions.get(i); |
| |
| // must be before |
| if (startOffset < hRegion.startOffset && endOffset <= hRegion.startOffset){ |
| myHighlightedRegions.add(i, new HighlightedRegion(startOffset, endOffset, attributes)); |
| break; |
| } |
| |
| // must be after |
| if (startOffset >= hRegion.endOffset){ |
| if (i == myHighlightedRegions.size() - 1){ |
| myHighlightedRegions.add(new HighlightedRegion(startOffset, endOffset, attributes)); |
| break; |
| } |
| } |
| |
| // must be before and overlap |
| if (startOffset < hRegion.startOffset && endOffset > hRegion.startOffset){ |
| |
| if (endOffset < hRegion.endOffset){ |
| myHighlightedRegions.add(i, new HighlightedRegion(startOffset, hRegion.startOffset, attributes)); |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(hRegion.startOffset, endOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| hRegion.startOffset = endOffset; |
| break; |
| } |
| |
| if (endOffset == hRegion.endOffset){ |
| myHighlightedRegions.remove(hRegion); |
| myHighlightedRegions.add(i, new HighlightedRegion(startOffset, hRegion.startOffset, attributes)); |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(hRegion.startOffset, endOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| break; |
| } |
| |
| if (endOffset > hRegion.endOffset){ |
| myHighlightedRegions.remove(hRegion); |
| myHighlightedRegions.add(i, new HighlightedRegion(startOffset, hRegion.startOffset, attributes)); |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(hRegion.startOffset, hRegion.endOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| |
| if (i < myHighlightedRegions.size() - 1){ |
| addHighlighter(i + 1, hRegion.endOffset, endOffset, attributes); |
| } |
| else{ |
| myHighlightedRegions.add(i + 2, new HighlightedRegion(hRegion.endOffset, endOffset, attributes)); |
| } |
| break; |
| } |
| } |
| |
| // must be after and overlap or full overlap |
| if (startOffset >= hRegion.startOffset && startOffset < hRegion.endOffset){ |
| |
| int oldEndOffset = hRegion.endOffset; |
| |
| hRegion.endOffset = startOffset; |
| |
| if (endOffset < oldEndOffset){ |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(startOffset, endOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| myHighlightedRegions.add(i + 2, new HighlightedRegion(endOffset, oldEndOffset, hRegion.textAttributes)); |
| |
| if (startOffset == hRegion.startOffset){ |
| myHighlightedRegions.remove(hRegion); |
| } |
| |
| break; |
| } |
| |
| if (endOffset == oldEndOffset){ |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(startOffset, oldEndOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| |
| if (startOffset == hRegion.startOffset){ |
| myHighlightedRegions.remove(hRegion); |
| } |
| |
| break; |
| } |
| |
| if (endOffset > oldEndOffset){ |
| myHighlightedRegions.add(i + 1, new HighlightedRegion(startOffset, oldEndOffset, TextAttributes.merge(hRegion.textAttributes, attributes))); |
| if (i < myHighlightedRegions.size() - 1){ |
| addHighlighter(i + 1, oldEndOffset, endOffset, attributes); |
| } |
| else{ |
| myHighlightedRegions.add(i + 2, new HighlightedRegion(hRegion.endOffset, endOffset, attributes)); |
| } |
| |
| if (startOffset == hRegion.startOffset){ |
| myHighlightedRegions.remove(hRegion); |
| } |
| |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| public void setIconTextGap(int gap) { |
| myIconTextGap = Math.max(gap, 2); |
| } |
| |
| public int getIconTextGap() { |
| return myIconTextGap; |
| } |
| |
| private Color myEnforcedBackground = null; |
| protected void enforceBackgroundOutsideText(Color bg) { |
| myEnforcedBackground = bg; |
| } |
| |
| protected void setDoNotHighlight(final boolean b) { |
| myDoNotHighlight = b; |
| } |
| |
| protected void paintComponent(Graphics g) { |
| |
| // determine color of background |
| |
| Color bgColor; |
| Color fgColor; |
| boolean paintHighlightsBackground; |
| boolean paintHighlightsForeground; |
| if (myIsSelected && (myHasFocus || myPaintUnfocusedSelection)) { |
| bgColor = UIUtil.getTreeSelectionBackground(); |
| fgColor = UIUtil.getTreeSelectionForeground(); |
| paintHighlightsBackground = false; |
| paintHighlightsForeground = false; |
| } |
| else { |
| bgColor = myEnforcedBackground == null ? UIUtil.getTreeTextBackground() : myEnforcedBackground; |
| fgColor = getForeground(); |
| paintHighlightsBackground = isOpaque(); |
| paintHighlightsForeground = true; |
| } |
| |
| if (myDoNotHighlight) { |
| paintHighlightsForeground = false; |
| } |
| |
| // paint background |
| |
| int textOffset = getTextOffset(); |
| int offset = textOffset; |
| |
| if (isOpaque()) { |
| g.setColor(getBackground()); |
| g.fillRect(0,0,textOffset-2,getHeight()); |
| g.setColor(bgColor); |
| g.fillRect(textOffset-2, 0, getWidth(), getHeight()); |
| } |
| |
| // paint icon |
| |
| if (myIcon != null) { |
| myIcon.paintIcon(this, g, 0, (getHeight() - myIcon.getIconHeight()) / 2); |
| } |
| |
| // paint text |
| |
| applyRenderingHints(g); |
| FontMetrics defFontMetrics = getFontMetrics(getFont()); |
| |
| if (myText == null) { |
| myText = ""; |
| } |
| // center text inside the component: |
| final int yOffset = (getHeight() - defFontMetrics.getMaxAscent() - defFontMetrics.getMaxDescent()) / 2 + defFontMetrics.getMaxAscent() - 1; |
| if (myHighlightedRegions.size() == 0){ |
| g.setColor(fgColor); |
| g.drawString(myText, textOffset, yOffset/*defFontMetrics.getMaxAscent()*/); |
| } |
| else{ |
| int endIndex = 0; |
| for (HighlightedRegion hRegion : myHighlightedRegions) { |
| |
| String text = myText.substring(endIndex, hRegion.startOffset); |
| endIndex = hRegion.endOffset; |
| |
| // draw plain text |
| |
| if (text.length() != 0) { |
| g.setColor(fgColor); |
| g.setFont(defFontMetrics.getFont()); |
| |
| g.drawString(text, offset, yOffset/*defFontMetrics.getMaxAscent()*/); |
| |
| offset += defFontMetrics.stringWidth(text); |
| } |
| |
| FontMetrics fontMetrics = myFontMetrics.get(hRegion.textAttributes.getFontType()); |
| |
| text = myText.substring(hRegion.startOffset, hRegion.endOffset); |
| |
| // paint highlight background |
| |
| if (hRegion.textAttributes.getBackgroundColor() != null && paintHighlightsBackground) { |
| g.setColor(hRegion.textAttributes.getBackgroundColor()); |
| g.fillRect(offset, 0, fontMetrics.stringWidth(text), fontMetrics.getHeight() + fontMetrics.getLeading()); |
| } |
| |
| // draw highlight text |
| |
| if (hRegion.textAttributes.getForegroundColor() != null && paintHighlightsForeground) { |
| g.setColor(hRegion.textAttributes.getForegroundColor()); |
| } |
| else { |
| g.setColor(fgColor); |
| } |
| |
| g.setFont(fontMetrics.getFont()); |
| g.drawString(text, offset, yOffset/*fontMetrics.getMaxAscent()*/); |
| |
| // draw highlight underscored line |
| |
| if (hRegion.textAttributes.getEffectColor() != null) { |
| g.setColor(hRegion.textAttributes.getEffectColor()); |
| int y = yOffset/*fontMetrics.getMaxAscent()*/ + 2; |
| UIUtil.drawLine(g, offset, y, offset + fontMetrics.stringWidth(text) - 1, y); |
| } |
| |
| // draw highlight border |
| |
| if (hRegion.textAttributes.getEffectColor() != null && hRegion.textAttributes.getEffectType() == EffectType.BOXED) { |
| g.setColor(hRegion.textAttributes.getEffectColor()); |
| g.drawRect(offset, 0, fontMetrics.stringWidth(text) - 1, fontMetrics.getHeight() + fontMetrics.getLeading() - 1); |
| } |
| |
| offset += fontMetrics.stringWidth(text); |
| } |
| |
| String text = myText.substring(endIndex, myText.length()); |
| |
| if (text.length() != 0){ |
| g.setColor(fgColor); |
| g.setFont(defFontMetrics.getFont()); |
| |
| g.drawString(text, offset, yOffset/*defFontMetrics.getMaxAscent()*/); |
| } |
| } |
| |
| // paint border |
| |
| if (myIsSelected){ |
| g.setColor(UIUtil.getTreeSelectionBorderColor()); |
| UIUtil.drawDottedRectangle(g, textOffset - 2, 0, getWidth() - 1, getHeight() - 1); |
| } |
| |
| super.paintComponent(g); |
| } |
| |
| protected void applyRenderingHints(Graphics g) { |
| UIUtil.applyRenderingHints(g); |
| } |
| |
| private int getTextOffset() { |
| if (myIcon == null){ |
| return 2; |
| } |
| return myIcon.getIconWidth() + myIconTextGap; |
| } |
| |
| @Nullable |
| public HighlightedRegion findRegionByX(int x) { |
| FontMetrics defFontMetrics = getFontMetrics(getFont()); |
| |
| int width = getTextOffset(); |
| if (width > x) return null; |
| |
| if (myText.length() != 0 && myHighlightedRegions.size() != 0) { |
| int endIndex = 0; |
| for (HighlightedRegion hRegion : myHighlightedRegions) { |
| width += defFontMetrics.stringWidth(myText.substring(endIndex, hRegion.startOffset)); |
| endIndex = hRegion.endOffset; |
| if (width > x) return null; |
| |
| String text = getRegionText(hRegion); |
| FontMetrics fontMetrics = myFontMetrics.get(hRegion.textAttributes.getFontType()); |
| width += fontMetrics.stringWidth(text); |
| if (width > x) return hRegion; |
| } |
| } |
| return null; |
| } |
| |
| public Dimension getPreferredSize() { |
| FontMetrics defFontMetrics = getFontMetrics(getFont()); |
| |
| int width = getTextOffset(); |
| |
| if (myText.length() != 0){ |
| if (myHighlightedRegions.size() == 0){ |
| width += defFontMetrics.stringWidth(myText); |
| } |
| else{ |
| int endIndex = 0; |
| for (HighlightedRegion hRegion : myHighlightedRegions) { |
| width += defFontMetrics.stringWidth(myText.substring(endIndex, hRegion.startOffset)); |
| endIndex = hRegion.endOffset; |
| |
| String text = getRegionText(hRegion); |
| FontMetrics fontMetrics = myFontMetrics.get(hRegion.textAttributes.getFontType()); |
| width += fontMetrics.stringWidth(text); |
| } |
| width += defFontMetrics.stringWidth(myText.substring(endIndex, myText.length())); |
| } |
| } |
| |
| int height = defFontMetrics.getHeight() + defFontMetrics.getLeading(); |
| |
| if (myIcon != null){ |
| height = Math.max(myIcon.getIconHeight() + defFontMetrics.getLeading(), height); |
| } |
| |
| return new Dimension(width + 2, height); |
| } |
| |
| private String getRegionText(HighlightedRegion hRegion) { |
| String text; |
| if (hRegion.endOffset > myText.length()) { |
| if (hRegion.startOffset < myText.length()) { |
| text = myText.substring(hRegion.startOffset); |
| } |
| else { |
| text = ""; |
| } |
| } |
| else { |
| text = myText.substring(hRegion.startOffset, hRegion.endOffset); |
| } |
| return text; |
| } |
| } |