| /* |
| * Copyright 2000-2013 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.codeInsight.hint; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.intellij.lang.parameterInfo.ParameterInfoHandler; |
| import com.intellij.lang.parameterInfo.ParameterInfoUIContextEx; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.ui.Gray; |
| import com.intellij.ui.JBColor; |
| import com.intellij.ui.ScrollPaneFactory; |
| import com.intellij.ui.SideBorder; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.xml.util.XmlStringUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.TestOnly; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import java.awt.*; |
| import java.util.*; |
| |
| public class ParameterInfoComponent extends JPanel { |
| private final Object[] myObjects; |
| private int myCurrentParameterIndex; |
| |
| private PsiElement myParameterOwner; |
| private Object myHighlighted; |
| @NotNull private final ParameterInfoHandler myHandler; |
| |
| private final OneElementComponent[] myPanels; |
| |
| private static final Color BACKGROUND_COLOR = HintUtil.INFORMATION_COLOR; |
| private static final Color HIGHLIGHTED_BORDER_COLOR = new JBColor(new Color(231, 254, 234), Gray._100); |
| private final Font NORMAL_FONT; |
| private final Font BOLD_FONT; |
| |
| private static final Border BACKGROUND_BORDER = BorderFactory.createLineBorder(BACKGROUND_COLOR); |
| |
| protected int myWidthLimit = 500; |
| |
| private static final Map<ParameterInfoUIContextEx.Flag, String> FLAG_TO_TAG = |
| ImmutableMap.of(ParameterInfoUIContextEx.Flag.HIGHLIGHT, "b", ParameterInfoUIContextEx.Flag.DISABLE, "font color=gray", |
| ParameterInfoUIContextEx.Flag.STRIKEOUT, "strike"); |
| |
| private static final Comparator<TextRange> TEXT_RANGE_COMPARATOR = new Comparator<TextRange>() { |
| @Override |
| public int compare(TextRange o1, TextRange o2) { |
| if (o1.getStartOffset() == o2.getStartOffset()) { |
| return o1.getEndOffset() > o2.getEndOffset() ? 1 : -1; |
| } |
| if (o1.getStartOffset() > o2.getStartOffset()) return 1; |
| if (o1.getEndOffset() > o2.getEndOffset()) return 1; |
| return -1; |
| } |
| }; |
| |
| @TestOnly |
| public static ParameterInfoUIContextEx createContext(Object[] objects, Editor editor, @NotNull ParameterInfoHandler handler) { |
| return new ParameterInfoComponent(objects, editor, handler).new MyParameterContext(); |
| } |
| |
| ParameterInfoComponent(Object[] objects, Editor editor, @NotNull ParameterInfoHandler handler) { |
| super(new BorderLayout()); |
| |
| if (!ApplicationManager.getApplication().isUnitTestMode()) { |
| JComponent editorComponent = editor.getComponent(); |
| JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane(); |
| myWidthLimit = layeredPane.getWidth(); |
| } |
| |
| NORMAL_FONT = UIUtil.getLabelFont(); |
| BOLD_FONT = NORMAL_FONT.deriveFont(Font.BOLD); |
| |
| myObjects = objects; |
| |
| setBackground(BACKGROUND_COLOR); |
| |
| myHandler = handler; |
| myPanels = new OneElementComponent[myObjects.length]; |
| final JPanel panel = new JPanel(new GridBagLayout()); |
| for (int i = 0; i < myObjects.length; i++) { |
| myPanels[i] = new OneElementComponent(); |
| panel.add(myPanels[i], new GridBagConstraints(0, i, 1, 1, 1, 0, |
| GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, |
| new Insets(0, 0, 0, 0), 0, 0)); |
| } |
| |
| final JScrollPane pane = ScrollPaneFactory.createScrollPane(panel); |
| pane.setBorder(null); |
| pane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); |
| add(pane, BorderLayout.CENTER); |
| |
| myCurrentParameterIndex = -1; |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| int size = myPanels.length; |
| final Dimension preferredSize = super.getPreferredSize(); |
| if (size >= 0 && size <= 20) { |
| return preferredSize; |
| } |
| else { |
| return new Dimension(preferredSize.width + 20, 200); |
| } |
| } |
| |
| public Object getHighlighted() { |
| return myHighlighted; |
| } |
| |
| class MyParameterContext implements ParameterInfoUIContextEx { |
| private int i; |
| |
| @Override |
| public String setupUIComponentPresentation(String text, |
| int highlightStartOffset, |
| int highlightEndOffset, |
| boolean isDisabled, |
| boolean strikeout, |
| boolean isDisabledBeforeHighlight, |
| Color background) { |
| final String resultedText = |
| myPanels[i].setup(text, highlightStartOffset, highlightEndOffset, isDisabled, strikeout, isDisabledBeforeHighlight, background); |
| myPanels[i].setBorder(isLastParameterOwner() ? BACKGROUND_BORDER : new SideBorder(new JBColor(JBColor.LIGHT_GRAY, Gray._90), SideBorder.BOTTOM)); |
| return resultedText; |
| } |
| |
| @Override |
| public String setupUIComponentPresentation(final String[] texts, final EnumSet<Flag>[] flags, final Color background) { |
| final String resultedText = myPanels[i].setup(texts, flags, background); |
| myPanels[i].setBorder(isLastParameterOwner() ? BACKGROUND_BORDER : new SideBorder(new JBColor(JBColor.LIGHT_GRAY, Gray._90), SideBorder.BOTTOM)); |
| return resultedText; |
| } |
| |
| @Override |
| public boolean isUIComponentEnabled() { |
| return isEnabled(i); |
| } |
| |
| @Override |
| public void setUIComponentEnabled(boolean enabled) { |
| setEnabled(i, enabled); |
| } |
| |
| public boolean isLastParameterOwner() { |
| return i == myPanels.length - 1; |
| } |
| |
| @Override |
| public int getCurrentParameterIndex() { |
| return myCurrentParameterIndex; |
| } |
| |
| @Override |
| public PsiElement getParameterOwner() { |
| return myParameterOwner; |
| } |
| |
| @Override |
| public Color getDefaultParameterColor() { |
| return myObjects[i].equals(myHighlighted) ? HIGHLIGHTED_BORDER_COLOR : BACKGROUND_COLOR; |
| } |
| } |
| |
| public void update() { |
| MyParameterContext context = new MyParameterContext(); |
| |
| for (int i = 0; i < myObjects.length; i++) { |
| context.i = i; |
| final Object o = myObjects[i]; |
| |
| //noinspection unchecked |
| myHandler.updateUI(o, context); |
| } |
| |
| invalidate(); |
| validate(); |
| repaint(); |
| } |
| |
| public Object[] getObjects() { |
| return myObjects; |
| } |
| |
| void setEnabled(int index, boolean enabled) { |
| myPanels[index].setEnabled(enabled); |
| } |
| |
| boolean isEnabled(int index) { |
| return myPanels[index].isEnabled(); |
| } |
| |
| public void setCurrentParameterIndex(int currentParameterIndex) { |
| myCurrentParameterIndex = currentParameterIndex; |
| } |
| |
| public int getCurrentParameterIndex() { |
| return myCurrentParameterIndex; |
| } |
| |
| public void setParameterOwner(PsiElement element) { |
| myParameterOwner = element; |
| } |
| |
| public PsiElement getParameterOwner() { |
| return myParameterOwner; |
| } |
| |
| public void setHighlightedParameter(Object element) { |
| myHighlighted = element; |
| } |
| |
| private class OneElementComponent extends JPanel { |
| private OneLineComponent[] myOneLineComponents; |
| |
| public OneElementComponent() { |
| super(new GridBagLayout()); |
| myOneLineComponents = new OneLineComponent[0]; //TODO ??? |
| } |
| |
| private String setup(String text, int highlightStartOffset, int highlightEndOffset, boolean isDisabled, boolean strikeout, boolean isDisabledBeforeHighlight, Color background) { |
| StringBuilder buf = new StringBuilder(); |
| removeAll(); |
| |
| String[] lines = UIUtil.splitText(text, getFontMetrics(BOLD_FONT), myWidthLimit, ','); |
| |
| myOneLineComponents = new OneLineComponent[lines.length]; |
| |
| int lineOffset = 0; |
| |
| for (int i = 0; i < lines.length; i++) { |
| String line = escapeString(lines[i]); |
| |
| myOneLineComponents[i] = new OneLineComponent(); |
| |
| TextRange range = null; |
| if (highlightStartOffset >= 0 && highlightEndOffset > lineOffset && highlightStartOffset < lineOffset + line.length()) { |
| int startOffset = Math.max(highlightStartOffset - lineOffset, 0); |
| int endOffset = Math.min(highlightEndOffset - lineOffset, line.length()); |
| range = TextRange.create(startOffset, endOffset); |
| } |
| |
| buf.append(myOneLineComponents[i].setup(line, isDisabled, strikeout, background, range)); |
| |
| if (isDisabledBeforeHighlight) { |
| if (highlightStartOffset < 0 || highlightEndOffset > lineOffset) { |
| myOneLineComponents[i].setDisabledBeforeHighlight(); |
| } |
| } |
| |
| add(myOneLineComponents[i], new GridBagConstraints(0,i,1,1,1,0,GridBagConstraints.WEST,GridBagConstraints.HORIZONTAL,new Insets(0,0,0,0),0,0)); |
| |
| lineOffset += line.length(); |
| } |
| return buf.toString(); |
| } |
| |
| private String escapeString(String line) { |
| return XmlStringUtil.escapeString(line); |
| } |
| |
| public String setup(final String[] texts, final EnumSet<ParameterInfoUIContextEx.Flag>[] flags, final Color background) { |
| StringBuilder buf = new StringBuilder(); |
| removeAll(); |
| final String[] lines = UIUtil.splitText(StringUtil.join(texts), getFontMetrics(BOLD_FONT), myWidthLimit, ','); |
| |
| int index = 0; |
| int curOffset = 0; |
| |
| myOneLineComponents = new OneLineComponent[lines.length]; |
| |
| Map<TextRange, ParameterInfoUIContextEx.Flag> flagsMap = new TreeMap<TextRange, ParameterInfoUIContextEx.Flag>(TEXT_RANGE_COMPARATOR); |
| |
| int added = 0; |
| for (int i = 0; i < texts.length; i++) { |
| String line = escapeString(texts[i]); |
| if (lines.length <= index) break; |
| String text = lines[index]; |
| final int paramCount = StringUtil.split(text, ", ").size(); |
| final EnumSet<ParameterInfoUIContextEx.Flag> flag = flags[i]; |
| if (flag.contains(ParameterInfoUIContextEx.Flag.HIGHLIGHT)) { |
| flagsMap.put(TextRange.create(curOffset, curOffset + line.trim().length()), ParameterInfoUIContextEx.Flag.HIGHLIGHT); |
| } |
| |
| if (flag.contains(ParameterInfoUIContextEx.Flag.DISABLE)) { |
| flagsMap.put(TextRange.create(curOffset, curOffset + line.trim().length()), ParameterInfoUIContextEx.Flag.DISABLE); |
| } |
| |
| if (flag.contains(ParameterInfoUIContextEx.Flag.STRIKEOUT)) { |
| flagsMap.put(TextRange.create(curOffset, curOffset + line.trim().length()), ParameterInfoUIContextEx.Flag.STRIKEOUT); |
| } |
| |
| curOffset += line.length(); |
| if (i == paramCount + added - 1) { |
| myOneLineComponents[index] = new OneLineComponent(); |
| setBackground(background); |
| buf.append(myOneLineComponents[index].setup(escapeString(text), flagsMap, background)); |
| add(myOneLineComponents[index], new GridBagConstraints(0, index, 1, 1, 1, 0, GridBagConstraints.WEST, |
| GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); |
| index += 1; |
| flagsMap.clear(); |
| curOffset = 1; |
| added += paramCount; |
| } |
| } |
| return buf.toString(); |
| } |
| } |
| |
| private class OneLineComponent extends JPanel { |
| JLabel myLabel = new JLabel("", SwingConstants.LEFT); |
| private boolean isDisabledBeforeHighlight = false; |
| |
| private OneLineComponent(){ |
| super(new GridBagLayout()); |
| |
| myLabel.setOpaque(true); |
| myLabel.setFont(NORMAL_FONT); |
| |
| add(myLabel, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.WEST, GridBagConstraints.NONE, |
| new Insets(0, 0, 0, 0), 0, 0)); |
| } |
| |
| private String setup(String text, |
| boolean isDisabled, |
| boolean isStrikeout, |
| Color background, @Nullable TextRange range) { |
| Map<TextRange, ParameterInfoUIContextEx.Flag> flagsMap = new TreeMap<TextRange, ParameterInfoUIContextEx.Flag>(TEXT_RANGE_COMPARATOR); |
| if (range != null) |
| flagsMap.put(range, ParameterInfoUIContextEx.Flag.HIGHLIGHT); |
| if (isDisabled) |
| flagsMap.put(TextRange.create(0, text.length()), ParameterInfoUIContextEx.Flag.DISABLE); |
| if (isStrikeout) |
| flagsMap.put(TextRange.create(0, text.length()), ParameterInfoUIContextEx.Flag.STRIKEOUT); |
| return setup(text, flagsMap, background); |
| } |
| |
| private String setup(@NotNull String text, @NotNull Map<TextRange, ParameterInfoUIContextEx.Flag> flagsMap, @NotNull Color background) { |
| myLabel.setBackground(background); |
| setBackground(background); |
| |
| myLabel.setForeground(JBColor.foreground()); |
| |
| if (flagsMap.isEmpty()) { |
| myLabel.setText(XmlStringUtil.wrapInHtml(text)); |
| } |
| else { |
| String labelText = buildLabelText(text, flagsMap); |
| myLabel.setText(labelText); |
| } |
| |
| //IDEA-95904 Darcula parameter info pop-up colors hard to read |
| if (UIUtil.isUnderDarcula()) { |
| myLabel.setText(myLabel.getText().replace("<b>", "<b color=ffC800>")); |
| } |
| return myLabel.getText(); |
| } |
| private String buildLabelText(@NotNull final String text, @NotNull final Map<TextRange, ParameterInfoUIContextEx.Flag> flagsMap) { |
| final StringBuilder labelText = new StringBuilder(text); |
| final String disabledTag = FLAG_TO_TAG.get(ParameterInfoUIContextEx.Flag.DISABLE); |
| |
| final Map<Integer, Integer> faultMap = new HashMap<Integer, Integer>(); |
| if (isDisabledBeforeHighlight) { |
| final String tag = getTag(disabledTag); |
| labelText.insert(0, tag); |
| faultMap.put(0, tag.length()); |
| } |
| |
| for (Map.Entry<TextRange, ParameterInfoUIContextEx.Flag> entry : flagsMap.entrySet()) { |
| final TextRange highlightRange = entry.getKey(); |
| final ParameterInfoUIContextEx.Flag flag = entry.getValue(); |
| |
| final String tagValue = FLAG_TO_TAG.get(flag); |
| final String tag = getTag(tagValue); |
| |
| int startOffset = highlightRange.getStartOffset(); |
| int endOffset = highlightRange.getEndOffset() + tag.length(); |
| |
| for (Map.Entry<Integer, Integer> entry1 : faultMap.entrySet()) { |
| if (entry1.getKey() < highlightRange.getStartOffset()) { |
| startOffset += entry1.getValue(); |
| } |
| if (entry1.getKey() < highlightRange.getEndOffset()) { |
| endOffset += entry1.getValue(); |
| } |
| } |
| |
| if (flag == ParameterInfoUIContextEx.Flag.HIGHLIGHT && isDisabledBeforeHighlight) { |
| final String disableCloseTag = getClosingTag(disabledTag); |
| labelText.insert(startOffset, disableCloseTag); |
| faultMap.put(highlightRange.getStartOffset(), disableCloseTag.length()); |
| } |
| |
| labelText.insert(startOffset, tag); |
| faultMap.put(highlightRange.getStartOffset(), tag.length()); |
| |
| final String endTag = getClosingTag(tagValue); |
| labelText.insert(endOffset, endTag); |
| faultMap.put(highlightRange.getEndOffset(), endTag.length()); |
| |
| } |
| return XmlStringUtil.wrapInHtml(labelText); |
| } |
| |
| private String getTag(@NotNull final String tagValue) { |
| return "<" + tagValue + ">"; |
| } |
| |
| private String getClosingTag(@NotNull final String tagValue) { |
| return "</" + tagValue + ">"; |
| } |
| |
| public void setDisabledBeforeHighlight() { |
| isDisabledBeforeHighlight = true; |
| } |
| } |
| } |