public JavaFxHtmlPanel() { // System.setProperty("prism.lcdtext", "false"); // System.setProperty("prism.text", "t2k"); myPanelWrapper = new JPanel(new BorderLayout()); myPanelWrapper.setBackground(JBColor.background()); ApplicationManager.getApplication() .invokeLater( () -> PlatformImpl.startup( () -> { myWebView = new WebView(); updateFontSmoothingType( myWebView, MarkdownApplicationSettings.getInstance() .getMarkdownPreviewSettings() .isUseGrayscaleRendering()); myWebView.setContextMenuEnabled(false); final WebEngine engine = myWebView.getEngine(); engine.getLoadWorker().stateProperty().addListener(myBridgeSettingListener); engine .getLoadWorker() .stateProperty() .addListener(myScrollPreservingListener); final Scene scene = new Scene(myWebView); ApplicationManager.getApplication() .invokeLater( () -> { myPanel = new JFXPanelWrapper(); myPanel.setScene(scene); setHtml(""); for (Runnable action : myInitActions) { Platform.runLater(action); } myInitActions.clear(); myPanelWrapper.add(myPanel, BorderLayout.CENTER); myPanelWrapper.repaint(); }); })); subscribeForGrayscaleSetting(); }
/** * @author peter * @author Konstantin Bulenkov */ public class LookupCellRenderer implements ListCellRenderer { private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.lookup.impl.LookupCellRenderer"); // TODO[kb]: move all these awesome constants to Editor's Fonts & Colors settings private static final int AFTER_TAIL = 10; private static final int AFTER_TYPE = 6; private Icon myEmptyIcon = EmptyIcon.create(5); private final Font myNormalFont; private final Font myBoldFont; private final FontMetrics myNormalMetrics; private final FontMetrics myBoldMetrics; public static final Color BACKGROUND_COLOR = new JBColor(new Color(235, 244, 254), JBColor.background()); private static final Color FOREGROUND_COLOR = JBColor.foreground(); private static final Color GRAYED_FOREGROUND_COLOR = new JBColor(Gray._160, Gray._110); private static final Color SELECTED_BACKGROUND_COLOR = new Color(0, 82, 164); private static final Color SELECTED_NON_FOCUSED_BACKGROUND_COLOR = new JBColor(new Color(110, 142, 162), new Color(85, 88, 90)); private static final Color SELECTED_FOREGROUND_COLOR = new JBColor(JBColor.WHITE, JBColor.foreground()); private static final Color SELECTED_GRAYED_FOREGROUND_COLOR = new JBColor(JBColor.WHITE, JBColor.foreground()); static final Color PREFIX_FOREGROUND_COLOR = new JBColor(new Color(176, 0, 176), new Color(209, 122, 214)); private static final Color SELECTED_PREFIX_FOREGROUND_COLOR = new JBColor(new Color(249, 236, 204), new Color(209, 122, 214)); private final LookupImpl myLookup; private final SimpleColoredComponent myNameComponent; private final SimpleColoredComponent myTailComponent; private final SimpleColoredComponent myTypeLabel; private final LookupPanel myPanel; private final Map<Integer, Boolean> mySelected = new HashMap<Integer, Boolean>(); private static final String ELLIPSIS = "\u2026"; private int myMaxWidth = -1; public LookupCellRenderer(LookupImpl lookup) { EditorColorsScheme scheme = lookup.getEditor().getColorsScheme(); myNormalFont = scheme.getFont(EditorFontType.PLAIN); myBoldFont = scheme.getFont(EditorFontType.BOLD); myLookup = lookup; myNameComponent = new MySimpleColoredComponent(); myNameComponent.setIpad(new Insets(0, 0, 0, 0)); myTailComponent = new MySimpleColoredComponent(); myTailComponent.setIpad(new Insets(0, 0, 0, 0)); myTypeLabel = new MySimpleColoredComponent(); myTypeLabel.setIpad(new Insets(0, 0, 0, 0)); myPanel = new LookupPanel(); myPanel.add(myNameComponent, BorderLayout.WEST); myPanel.add(myTailComponent, BorderLayout.CENTER); myTailComponent.setBorder(new EmptyBorder(0, 0, 0, AFTER_TAIL)); myPanel.add(myTypeLabel, BorderLayout.EAST); myTypeLabel.setBorder(new EmptyBorder(0, 0, 0, AFTER_TYPE)); myNormalMetrics = myLookup.getEditor().getComponent().getFontMetrics(myNormalFont); myBoldMetrics = myLookup.getEditor().getComponent().getFontMetrics(myBoldFont); } private boolean myIsSelected = false; @Override public Component getListCellRendererComponent( final JList list, Object value, int index, boolean isSelected, boolean hasFocus) { boolean nonFocusedSelection = isSelected && myLookup.getFocusDegree() == LookupImpl.FocusDegree.SEMI_FOCUSED; if (!myLookup.isFocused()) { isSelected = false; } myIsSelected = isSelected; final LookupElement item = (LookupElement) value; final Color foreground = getForegroundColor(isSelected); final Color background = nonFocusedSelection ? SELECTED_NON_FOCUSED_BACKGROUND_COLOR : isSelected ? SELECTED_BACKGROUND_COLOR : BACKGROUND_COLOR; int allowedWidth = list.getWidth() - AFTER_TAIL - AFTER_TYPE - getIconIndent(); FontMetrics normalMetrics = getRealFontMetrics(item, false); FontMetrics boldMetrics = getRealFontMetrics(item, true); final LookupElementPresentation presentation = new RealLookupElementPresentation( isSelected ? getMaxWidth() : allowedWidth, normalMetrics, boldMetrics, myLookup); AccessToken token = ReadAction.start(); try { if (item.isValid()) { try { item.renderElement(presentation); } catch (Exception e) { LOG.error(e); } catch (Error e) { LOG.error(e); } } else { presentation.setItemTextForeground(JBColor.RED); presentation.setItemText("Invalid"); } } finally { token.finish(); } myNameComponent.clear(); myNameComponent.setIcon(augmentIcon(presentation.getIcon(), myEmptyIcon)); myNameComponent.setBackground(background); allowedWidth -= setItemTextLabel( item, new JBColor( isSelected ? SELECTED_FOREGROUND_COLOR : presentation.getItemTextForeground(), foreground), isSelected, presentation, allowedWidth); Font customFont = myLookup.getCustomFont(item, false); myTailComponent.setFont(customFont != null ? customFont : myNormalFont); myTypeLabel.setFont(customFont != null ? customFont : myNormalFont); myTypeLabel.clear(); if (allowedWidth > 0) { allowedWidth -= setTypeTextLabel( item, background, foreground, presentation, isSelected ? getMaxWidth() : allowedWidth, isSelected, nonFocusedSelection, normalMetrics); } myTailComponent.clear(); myTailComponent.setBackground(background); if (isSelected || allowedWidth >= 0) { setTailTextLabel( isSelected, presentation, foreground, isSelected ? getMaxWidth() : allowedWidth, nonFocusedSelection, normalMetrics); } if (mySelected.containsKey(index)) { if (!isSelected && mySelected.get(index)) { myPanel.setUpdateExtender(true); } } mySelected.put(index, isSelected); final double w = myNameComponent.getPreferredSize().getWidth() + myTailComponent.getPreferredSize().getWidth() + myTypeLabel.getPreferredSize().getWidth(); boolean useBoxLayout = isSelected && w > list.getWidth() && ((JBList) list).getExpandableItemsHandler().isEnabled(); if (useBoxLayout != myPanel.getLayout() instanceof BoxLayout) { myPanel.removeAll(); if (useBoxLayout) { myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.X_AXIS)); myPanel.add(myNameComponent); myPanel.add(myTailComponent); myPanel.add(myTypeLabel); } else { myPanel.setLayout(new BorderLayout()); myPanel.add(myNameComponent, BorderLayout.WEST); myPanel.add(myTailComponent, BorderLayout.CENTER); myPanel.add(myTypeLabel, BorderLayout.EAST); } } return myPanel; } private static Color getForegroundColor(boolean isSelected) { return isSelected ? SELECTED_FOREGROUND_COLOR : FOREGROUND_COLOR; } private int getMaxWidth() { if (myMaxWidth < 0) { final Point p = myLookup.getComponent().getLocationOnScreen(); final Rectangle rectangle = ScreenUtil.getScreenRectangle(p); myMaxWidth = rectangle.x + rectangle.width - p.x - 111; } return myMaxWidth; } private void setTailTextLabel( boolean isSelected, LookupElementPresentation presentation, Color foreground, int allowedWidth, boolean nonFocusedSelection, FontMetrics fontMetrics) { int style = getStyle(false, presentation.isStrikeout(), false); for (LookupElementPresentation.TextFragment fragment : presentation.getTailFragments()) { if (allowedWidth < 0) { return; } String trimmed = trimLabelText(fragment.text, allowedWidth, fontMetrics); myTailComponent.append( trimmed, new SimpleTextAttributes( style, getTailTextColor(isSelected, fragment, foreground, nonFocusedSelection))); allowedWidth -= RealLookupElementPresentation.getStringWidth(trimmed, fontMetrics); } } private String trimLabelText(@Nullable String text, int maxWidth, FontMetrics metrics) { if (text == null || StringUtil.isEmpty(text)) { return ""; } final int strWidth = RealLookupElementPresentation.getStringWidth(text, metrics); if (strWidth <= maxWidth || myIsSelected) { return text; } if (RealLookupElementPresentation.getStringWidth(ELLIPSIS, metrics) > maxWidth) { return ""; } int i = 0; int j = text.length(); while (i + 1 < j) { int mid = (i + j) / 2; final String candidate = text.substring(0, mid) + ELLIPSIS; final int width = RealLookupElementPresentation.getStringWidth(candidate, metrics); if (width <= maxWidth) { i = mid; } else { j = mid; } } return text.substring(0, i) + ELLIPSIS; } private static Color getTypeTextColor( LookupElement item, Color foreground, LookupElementPresentation presentation, boolean selected, boolean nonFocusedSelection) { if (nonFocusedSelection) { return foreground; } return presentation.isTypeGrayed() ? getGrayedForeground(selected) : item instanceof EmptyLookupItem ? JBColor.foreground() : foreground; } private static Color getTailTextColor( boolean isSelected, LookupElementPresentation.TextFragment fragment, Color defaultForeground, boolean nonFocusedSelection) { if (nonFocusedSelection) { return defaultForeground; } if (fragment.isGrayed()) { return getGrayedForeground(isSelected); } if (!isSelected) { final Color tailForeground = fragment.getForegroundColor(); if (tailForeground != null) { return tailForeground; } } return defaultForeground; } public static Color getGrayedForeground(boolean isSelected) { return isSelected ? SELECTED_GRAYED_FOREGROUND_COLOR : GRAYED_FOREGROUND_COLOR; } private int setItemTextLabel( LookupElement item, final Color foreground, final boolean selected, LookupElementPresentation presentation, int allowedWidth) { boolean bold = presentation.isItemTextBold(); Font customItemFont = myLookup.getCustomFont(item, bold); myNameComponent.setFont( customItemFont != null ? customItemFont : bold ? myBoldFont : myNormalFont); int style = getStyle(bold, presentation.isStrikeout(), presentation.isItemTextUnderlined()); final FontMetrics metrics = getRealFontMetrics(item, bold); final String name = trimLabelText(presentation.getItemText(), allowedWidth, metrics); int used = RealLookupElementPresentation.getStringWidth(name, metrics); renderItemName(item, foreground, selected, style, name, myNameComponent); return used; } private FontMetrics getRealFontMetrics(LookupElement item, boolean bold) { Font customFont = myLookup.getCustomFont(item, bold); if (customFont != null) { return myLookup.getEditor().getComponent().getFontMetrics(customFont); } return bold ? myBoldMetrics : myNormalMetrics; } @SimpleTextAttributes.StyleAttributeConstant private static int getStyle(boolean bold, boolean strikeout, boolean underlined) { int style = bold ? SimpleTextAttributes.STYLE_BOLD : SimpleTextAttributes.STYLE_PLAIN; if (strikeout) { style |= SimpleTextAttributes.STYLE_STRIKEOUT; } if (underlined) { style |= SimpleTextAttributes.STYLE_UNDERLINE; } return style; } private void renderItemName( LookupElement item, Color foreground, boolean selected, @SimpleTextAttributes.StyleAttributeConstant int style, String name, final SimpleColoredComponent nameComponent) { final SimpleTextAttributes base = new SimpleTextAttributes(style, foreground); final String prefix = item instanceof EmptyLookupItem ? "" : myLookup.itemPattern(item); if (prefix.length() > 0) { Iterable<TextRange> ranges = getMatchingFragments(prefix, name); if (ranges != null) { SimpleTextAttributes highlighted = new SimpleTextAttributes( style, selected ? SELECTED_PREFIX_FOREGROUND_COLOR : PREFIX_FOREGROUND_COLOR); SpeedSearchUtil.appendColoredFragments(nameComponent, name, ranges, base, highlighted); return; } } nameComponent.append(name, base); } public static FList<TextRange> getMatchingFragments(String prefix, String name) { return new MinusculeMatcher("*" + prefix, NameUtil.MatchingCaseSensitivity.NONE) .matchingFragments(name); } private int setTypeTextLabel( LookupElement item, final Color background, Color foreground, final LookupElementPresentation presentation, int allowedWidth, boolean selected, boolean nonFocusedSelection, FontMetrics normalMetrics) { final String givenText = presentation.getTypeText(); final String labelText = trimLabelText( StringUtil.isEmpty(givenText) ? "" : " " + givenText, allowedWidth, normalMetrics); int used = RealLookupElementPresentation.getStringWidth(labelText, normalMetrics); final Icon icon = presentation.getTypeIcon(); if (icon != null) { myTypeLabel.setIcon(icon); used += icon.getIconWidth(); } Color sampleBackground = background; Object o = item.isValid() ? item.getObject() : null; //noinspection deprecation if (o instanceof LookupValueWithUIHint && StringUtil.isEmpty(labelText)) { //noinspection deprecation Color proposedBackground = ((LookupValueWithUIHint) o).getColorHint(); if (proposedBackground != null) { sampleBackground = proposedBackground; } myTypeLabel.append(" "); used += normalMetrics.stringWidth("WW"); } else { myTypeLabel.append(labelText); } myTypeLabel.setBackground(sampleBackground); myTypeLabel.setForeground( getTypeTextColor(item, foreground, presentation, selected, nonFocusedSelection)); return used; } public static Icon augmentIcon(@Nullable Icon icon, @NotNull Icon standard) { if (icon == null) { return standard; } if (icon.getIconHeight() < standard.getIconHeight() || icon.getIconWidth() < standard.getIconWidth()) { final LayeredIcon layeredIcon = new LayeredIcon(2); layeredIcon.setIcon(icon, 0, 0, (standard.getIconHeight() - icon.getIconHeight()) / 2); layeredIcon.setIcon(standard, 1); return layeredIcon; } return icon; } @Nullable Font getFontAbleToDisplay(LookupElementPresentation p) { String sampleString = p.getItemText() + p.getTailText() + p.getTypeText(); // assume a single font can display all lookup item chars Set<Font> fonts = ContainerUtil.newHashSet(); for (int i = 0; i < sampleString.length(); i++) { fonts.add( EditorUtil.fontForChar(sampleString.charAt(i), Font.PLAIN, myLookup.getEditor()) .getFont()); } eachFont: for (Font font : fonts) { if (font.equals(myNormalFont)) continue; for (int i = 0; i < sampleString.length(); i++) { if (!font.canDisplay(sampleString.charAt(i))) { continue eachFont; } } return font; } return null; } int updateMaximumWidth(final LookupElementPresentation p, LookupElement item) { final Icon icon = p.getIcon(); if (icon != null && (icon.getIconWidth() > myEmptyIcon.getIconWidth() || icon.getIconHeight() > myEmptyIcon.getIconHeight())) { myEmptyIcon = new EmptyIcon( Math.max(icon.getIconWidth(), myEmptyIcon.getIconWidth()), Math.max(icon.getIconHeight(), myEmptyIcon.getIconHeight())); } return RealLookupElementPresentation.calculateWidth( p, getRealFontMetrics(item, false), getRealFontMetrics(item, true)) + AFTER_TAIL + AFTER_TYPE; } public int getIconIndent() { return myNameComponent.getIconTextGap() + myEmptyIcon.getIconWidth(); } private static class MySimpleColoredComponent extends SimpleColoredComponent { private MySimpleColoredComponent() { setFocusBorderAroundIcon(true); } @Override protected void applyAdditionalHints(Graphics g) { GraphicsUtil.setupAntialiasing(g); } } private class LookupPanel extends JPanel { boolean myUpdateExtender; public LookupPanel() { super(new BorderLayout()); } public void setUpdateExtender(boolean updateExtender) { myUpdateExtender = updateExtender; } @Override public void paint(Graphics g) { if (!myLookup.isFocused() && myLookup.isCompletion()) { ((Graphics2D) g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f)); } super.paint(g); } } }