2 * Copyright 2000-2014 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.util.ui;
18 import com.intellij.ide.BrowserUtil;
19 import com.intellij.openapi.actionSystem.ActionManager;
20 import com.intellij.openapi.actionSystem.AnAction;
21 import com.intellij.openapi.actionSystem.AnActionEvent;
22 import com.intellij.openapi.actionSystem.DefaultActionGroup;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.application.ModalityState;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
27 import com.intellij.openapi.fileChooser.FileChooserFactory;
28 import com.intellij.openapi.ide.CopyPasteManager;
29 import com.intellij.openapi.options.ex.SingleConfigurableEditor;
30 import com.intellij.openapi.options.newEditor.SettingsDialog;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.ui.*;
33 import com.intellij.openapi.util.SystemInfo;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.ui.HyperlinkLabel;
36 import com.intellij.ui.TextFieldWithHistory;
37 import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
38 import com.intellij.util.Consumer;
39 import com.intellij.util.NotNullProducer;
40 import com.intellij.util.ObjectUtils;
41 import com.intellij.util.PlatformIcons;
42 import com.intellij.util.containers.ComparatorUtil;
43 import com.intellij.util.containers.ContainerUtil;
44 import com.intellij.util.ui.update.UiNotifyConnector;
45 import org.jetbrains.annotations.Nls;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
50 import javax.swing.event.HyperlinkEvent;
51 import javax.swing.event.HyperlinkListener;
52 import javax.swing.event.PopupMenuEvent;
53 import javax.swing.event.PopupMenuListener;
54 import javax.swing.table.DefaultTableCellRenderer;
55 import javax.swing.table.TableCellRenderer;
56 import javax.swing.table.TableColumn;
57 import javax.swing.text.AttributeSet;
58 import javax.swing.text.BadLocationException;
59 import javax.swing.text.Document;
60 import javax.swing.text.Element;
61 import javax.swing.text.html.HTML;
62 import javax.swing.text.html.HTMLDocument;
64 import java.awt.datatransfer.StringSelection;
65 import java.awt.datatransfer.Transferable;
66 import java.awt.event.ActionListener;
68 import java.util.List;
70 public class SwingHelper {
72 private static final Logger LOG = Logger.getInstance(SwingHelper.class);
73 private static final String DIALOG_RESIZED_TO_FIT_TEXT = "INTELLIJ_DIALOG_RESIZED_TO_FIT_TEXT";
76 * Creates panel whose content consists of given {@code children} components
77 * stacked vertically each on another in a given order.
79 * @param childAlignmentX Component.LEFT_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.RIGHT_ALIGNMENT
80 * @param children children components
81 * @return created panel
84 public static JPanel newVerticalPanel(float childAlignmentX, Component... children) {
85 return newGenericBoxPanel(true, childAlignmentX, children);
89 public static JPanel newLeftAlignedVerticalPanel(Component... children) {
90 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
94 public static JPanel newLeftAlignedVerticalPanel(@NotNull Collection<Component> children) {
95 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
99 public static JPanel newVerticalPanel(float childAlignmentX, @NotNull Collection<Component> children) {
100 return newVerticalPanel(childAlignmentX, children.toArray(new Component[children.size()]));
104 * Creates panel whose content consists of given {@code children} components horizontally
105 * stacked each on another in a given order.
107 * @param childAlignmentY Component.TOP_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.BOTTOM_ALIGNMENT
108 * @param children children components
109 * @return created panel
112 public static JPanel newHorizontalPanel(float childAlignmentY, Component... children) {
113 return newGenericBoxPanel(false, childAlignmentY, children);
116 private static JPanel newGenericBoxPanel(boolean verticalOrientation,
117 float childAlignment,
118 Component... children) {
119 JPanel panel = new JPanel();
120 int axis = verticalOrientation ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
121 panel.setLayout(new BoxLayout(panel, axis));
122 for (Component child : children) {
123 panel.add(child, childAlignment);
124 if (child instanceof JComponent) {
125 JComponent jChild = (JComponent)child;
126 if (verticalOrientation) {
127 jChild.setAlignmentX(childAlignment);
130 jChild.setAlignmentY(childAlignment);
138 public static JPanel wrapWithoutStretch(@NotNull JComponent component) {
139 JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
140 panel.add(component);
145 public static JPanel wrapWithHorizontalStretch(@NotNull JComponent component) {
146 JPanel panel = new JPanel(new BorderLayout(0, 0));
147 panel.add(component, BorderLayout.NORTH);
151 public static void setPreferredWidthToFitText(@NotNull TextFieldWithHistoryWithBrowseButton component) {
152 int childWidth = calcWidthToFitText(component.getChildComponent().getTextEditor(), JBUI.scale(32));
153 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
156 public static void setPreferredWidthToFitText(@NotNull TextFieldWithBrowseButton component) {
157 int childWidth = calcWidthToFitText(component.getChildComponent(), JBUI.scale(20));
158 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
161 private static <T extends JComponent> void setPreferredWidthForComponentWithBrowseButton(@NotNull ComponentWithBrowseButton<T> component,
162 int childPrefWidth) {
163 Dimension buttonPrefSize = component.getButton().getPreferredSize();
164 setPreferredWidth(component, childPrefWidth + buttonPrefSize.width);
167 public static void setPreferredWidthToFitText(@NotNull JTextField textField) {
168 setPreferredWidthToFitText(textField, JBUI.scale(15));
171 public static void setPreferredWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
172 setPreferredSizeToFitText(textField, StringUtil.notNullize(textField.getText()), additionalWidth);
175 public static void setPreferredWidthToFitText(@NotNull JTextField textField, @NotNull String text) {
176 setPreferredSizeToFitText(textField, text, JBUI.scale(15));
179 private static void setPreferredSizeToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
180 int width = calcWidthToFitText(textField, text, additionalWidth);
181 setPreferredWidth(textField, width);
184 private static int calcWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
185 return calcWidthToFitText(textField, textField.getText(), additionalWidth);
188 private static int calcWidthToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
189 return textField.getFontMetrics(textField.getFont()).stringWidth(text) + additionalWidth;
192 public static void adjustDialogSizeToFitPreferredSize(@NotNull DialogWrapper dialogWrapper) {
193 JRootPane rootPane = dialogWrapper.getRootPane();
194 Dimension componentSize = rootPane.getSize();
195 Dimension componentPreferredSize = rootPane.getPreferredSize();
196 if (componentPreferredSize.width <= componentSize.width && componentPreferredSize.height <= componentSize.height) {
199 int dw = Math.max(0, componentPreferredSize.width - componentSize.width);
200 int dh = Math.max(0, componentPreferredSize.height - componentSize.height);
202 Dimension oldDialogSize = dialogWrapper.getSize();
203 Dimension newDialogSize = new Dimension(oldDialogSize.width + dw, oldDialogSize.height + dh);
205 dialogWrapper.setSize(newDialogSize.width, newDialogSize.height);
206 rootPane.revalidate();
209 LOG.info("DialogWrapper '" + dialogWrapper.getTitle() + "' has been re-sized (added width: " + dw + ", added height: " + dh + ")");
212 public static void resizeDialogToFitTextFor(@NotNull final JComponent... components) {
213 if (components.length == 0) return;
214 doWithDialogWrapper(components[0], new Consumer<DialogWrapper>() {
216 public void consume(final DialogWrapper dialogWrapper) {
217 if (dialogWrapper instanceof SettingsDialog || dialogWrapper instanceof SingleConfigurableEditor) {
218 for (Component component : components) {
219 if (component instanceof TextFieldWithHistoryWithBrowseButton) {
220 setPreferredWidthToFitText((TextFieldWithHistoryWithBrowseButton)component);
222 else if (component instanceof TextFieldWithBrowseButton) {
223 setPreferredWidthToFitText((TextFieldWithBrowseButton)component);
225 else if (component instanceof JTextField) {
226 setPreferredWidthToFitText((JTextField)component);
229 ApplicationManager.getApplication().invokeLater(new Runnable() {
232 adjustDialogSizeToFitPreferredSize(dialogWrapper);
234 }, ModalityState.any());
240 private static void doWithDialogWrapper(@NotNull final JComponent component, @NotNull final Consumer<DialogWrapper> consumer) {
241 UIUtil.invokeLaterIfNeeded(new Runnable() {
244 if (component.getClientProperty(DIALOG_RESIZED_TO_FIT_TEXT) != null) {
247 component.putClientProperty(DIALOG_RESIZED_TO_FIT_TEXT, true);
248 DialogWrapper dialogWrapper = DialogWrapper.findInstance(component);
249 if (dialogWrapper != null) {
250 consumer.consume(dialogWrapper);
253 UiNotifyConnector.doWhenFirstShown(component, new Runnable() {
256 DialogWrapper dialogWrapper = DialogWrapper.findInstance(component);
257 if (dialogWrapper != null) {
258 consumer.consume(dialogWrapper);
267 public static <T> void updateItems(@NotNull JComboBox<T> comboBox,
268 @NotNull List<T> newItems,
269 @Nullable T newSelectedItemIfSelectionCannotBePreserved) {
270 if (!shouldUpdate(comboBox, newItems)) {
273 Object itemToSelect = comboBox.getSelectedItem();
274 boolean preserveSelection = true;
275 //noinspection SuspiciousMethodCalls
276 if (!newItems.contains(itemToSelect)) {
277 if (newItems.contains(newSelectedItemIfSelectionCannotBePreserved)) {
278 itemToSelect = newSelectedItemIfSelectionCannotBePreserved;
282 preserveSelection = false;
285 comboBox.removeAllItems();
286 for (T newItem : newItems) {
287 comboBox.addItem(newItem);
289 if (preserveSelection) {
290 int count = comboBox.getItemCount();
291 for (int i = 0; i < count; i++) {
292 Object item = comboBox.getItemAt(i);
293 if (ComparatorUtil.equalsNullable(itemToSelect, item)) {
294 comboBox.setSelectedIndex(i);
301 private static <T> boolean shouldUpdate(@NotNull JComboBox<T> comboBox, @NotNull List<T> newItems) {
302 int count = comboBox.getItemCount();
303 if (newItems.size() != count) {
306 for (int i = 0; i < count; i++) {
307 Object oldItem = comboBox.getItemAt(i);
308 T newItem = newItems.get(i);
309 if (!ComparatorUtil.equalsNullable(oldItem, newItem)) {
316 public static void setNoBorderCellRendererFor(@NotNull TableColumn column) {
317 final TableCellRenderer previous = column.getCellRenderer();
318 column.setCellRenderer(new DefaultTableCellRenderer() {
320 public Component getTableCellRendererComponent(JTable table,
327 if (previous != null) {
328 component = previous.getTableCellRendererComponent(table, value, isSelected, false, row, column);
331 component = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
333 if (component instanceof JComponent) {
334 ((JComponent)component).setBorder(null);
341 public static void addHistoryOnExpansion(@NotNull final TextFieldWithHistory textFieldWithHistory,
342 @NotNull final NotNullProducer<List<String>> historyProvider) {
343 textFieldWithHistory.addPopupMenuListener(new PopupMenuListener() {
345 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
346 List<String> history = historyProvider.produce();
347 setHistory(textFieldWithHistory, ContainerUtil.notNullize(history), true);
348 // one-time initialization
349 textFieldWithHistory.removePopupMenuListener(this);
353 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
357 public void popupMenuCanceled(PopupMenuEvent e) {
362 public static void setHistory(@NotNull TextFieldWithHistory textFieldWithHistory,
363 @NotNull List<String> history,
364 boolean mergeWithPrevHistory) {
365 Set<String> newHistorySet = ContainerUtil.newHashSet(history);
366 List<String> prevHistory = textFieldWithHistory.getHistory();
367 List<String> mergedHistory = ContainerUtil.newArrayListWithCapacity(history.size());
368 if (mergeWithPrevHistory) {
369 for (String item : prevHistory) {
370 if (!newHistorySet.contains(item)) {
371 mergedHistory.add(item);
375 mergedHistory.addAll(history);
376 String oldText = StringUtil.notNullize(textFieldWithHistory.getText());
377 String oldSelectedItem = ObjectUtils.tryCast(textFieldWithHistory.getSelectedItem(), String.class);
378 if (!mergedHistory.contains(oldSelectedItem)) {
379 oldSelectedItem = null;
381 textFieldWithHistory.setHistory(mergedHistory);
382 setLongestAsPrototype(textFieldWithHistory, mergedHistory);
383 if (oldSelectedItem != null) {
384 textFieldWithHistory.setSelectedItem(oldSelectedItem);
386 if (!oldText.equals(oldSelectedItem)) {
387 textFieldWithHistory.setText(oldText);
391 private static void setLongestAsPrototype(@NotNull JComboBox comboBox, @NotNull List<String> variants) {
392 Object prototypeDisplayValue = comboBox.getPrototypeDisplayValue();
393 String prototypeDisplayValueStr = null;
394 if (prototypeDisplayValue instanceof String) {
395 prototypeDisplayValueStr = (String)prototypeDisplayValue;
397 else if (prototypeDisplayValue != null) {
400 String longest = StringUtil.notNullize(prototypeDisplayValueStr);
401 boolean updated = false;
402 for (String s : variants) {
403 if (longest.length() < s.length()) {
409 comboBox.setPrototypeDisplayValue(longest);
413 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
414 @NotNull TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton,
415 @NotNull @Nls(capitalization = Nls.Capitalization.Title) String browseDialogTitle,
416 @NotNull FileChooserDescriptor fileChooserDescriptor) {
418 textFieldWithHistoryWithBrowseButton,
419 textFieldWithHistoryWithBrowseButton.getChildComponent().getTextEditor(),
421 fileChooserDescriptor,
422 TextComponentAccessor.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT);
425 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
426 @NotNull TextFieldWithBrowseButton textFieldWithBrowseButton,
427 @NotNull @Nls(capitalization = Nls.Capitalization.Title) String browseDialogTitle,
428 @NotNull FileChooserDescriptor fileChooserDescriptor) {
430 textFieldWithBrowseButton,
431 textFieldWithBrowseButton.getTextField(),
433 fileChooserDescriptor,
434 TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
437 private static <T extends JComponent> void doInstall(@Nullable Project project,
438 @NotNull ComponentWithBrowseButton<T> componentWithBrowseButton,
439 @NotNull JTextField textField,
440 @NotNull @Nls(capitalization = Nls.Capitalization.Title) String browseDialogTitle,
441 @NotNull FileChooserDescriptor fileChooserDescriptor,
442 @NotNull TextComponentAccessor<T> textComponentAccessor) {
443 fileChooserDescriptor = fileChooserDescriptor.withShowHiddenFiles(SystemInfo.isUnix);
444 componentWithBrowseButton.addBrowseFolderListener(
446 new ComponentWithBrowseButton.BrowseFolderActionListener<T>(
449 componentWithBrowseButton,
451 fileChooserDescriptor,
452 textComponentAccessor
456 FileChooserFactory.getInstance().installFileCompletion(
458 fileChooserDescriptor,
465 public static HyperlinkLabel createWebHyperlink(@NotNull String url) {
466 return createWebHyperlink(url, url);
470 public static HyperlinkLabel createWebHyperlink(@NotNull String text, @NotNull String url) {
471 HyperlinkLabel hyperlink = new HyperlinkLabel(text);
472 hyperlink.setHyperlinkTarget(url);
474 DefaultActionGroup actionGroup = new DefaultActionGroup();
475 actionGroup.add(new OpenLinkInBrowser(url));
476 actionGroup.add(new CopyLinkAction(url));
478 hyperlink.setComponentPopupMenu(ActionManager.getInstance().createActionPopupMenu("web hyperlink", actionGroup).getComponent());
482 public static void setPreferredWidth(@NotNull Component component, int width) {
483 Dimension preferredSize = component.getPreferredSize();
484 preferredSize.width = width;
485 component.setPreferredSize(preferredSize);
488 public static boolean scrollToReference(JEditorPane view, String reference) {
489 reference = StringUtil.trimStart(reference, "#");
490 List<String> toCheck = Arrays.asList("a", "h1", "h2", "h3", "h4");
491 Document document = view.getDocument();
492 if (document instanceof HTMLDocument) {
493 List<Element> list = new ArrayList<Element>();
494 for (Element root : document.getRootElements()) {
495 getAllElements(root, list, toCheck);
497 for (Element element : list) {
498 AttributeSet attributes = element.getAttributes();
499 String nm = (String)attributes.getAttribute(HTML.Attribute.NAME);
500 if (nm == null) nm = (String)attributes.getAttribute(HTML.Attribute.ID);
501 if ((nm != null) && nm.equals(reference)) {
503 int pos = element.getStartOffset();
504 Rectangle r = view.modelToView(pos);
506 Rectangle vis = view.getVisibleRect();
508 r.height = vis.height;
509 view.scrollRectToVisible(r);
513 catch (BadLocationException ex) {
522 private static void getAllElements(Element root, List<Element> list, List<String> toCheck) {
523 if (toCheck.contains(root.getName().toLowerCase(Locale.US))) {
526 for (int i = 0; i < root.getElementCount(); i++) {
527 getAllElements(root.getElement(i), list, toCheck);
531 public static class HtmlViewerBuilder {
532 private boolean myCarryTextOver;
533 private String myDisabledHtml;
535 private Color myBackground;
536 private Color myForeground;
538 public JEditorPane create() {
539 final JEditorPane textPane = new JEditorPane() {
540 private boolean myEnabled = true;
541 private String myEnabledHtml;
544 public Dimension getPreferredSize() {
545 // This trick makes text component to carry text over to the next line
546 // if the text line width exceeds parent's width
547 Dimension dimension = super.getPreferredSize();
548 if (myCarryTextOver) {
555 public void setText(String t) {
556 if (myDisabledHtml != null) {
565 public void setEnabled(boolean enabled) {
566 if (myDisabledHtml != null) {
569 setText(myEnabledHtml);
571 setText(myDisabledHtml);
573 super.setEnabled(true);
575 super.setEnabled(enabled);
579 textPane.setFont(myFont != null ? myFont : UIUtil.getLabelFont());
580 textPane.setContentType(UIUtil.HTML_MIME);
581 textPane.setEditable(false);
582 if (myBackground != null) {
583 textPane.setBackground(myBackground);
586 textPane.setOpaque(false);
588 textPane.setForeground(myForeground != null ? myForeground : UIUtil.getLabelForeground());
589 textPane.setFocusable(false);
593 public HtmlViewerBuilder setCarryTextOver(boolean carryTextOver) {
594 myCarryTextOver = carryTextOver;
598 public HtmlViewerBuilder setDisabledHtml(String disabledHtml) {
599 myDisabledHtml = disabledHtml;
603 public HtmlViewerBuilder setFont(Font font) {
608 public HtmlViewerBuilder setBackground(Color background) {
609 myBackground = background;
613 public HtmlViewerBuilder setForeground(Color foreground) {
614 myForeground = foreground;
620 public static JEditorPane createHtmlViewer(boolean lineWrap,
622 @Nullable Color background,
623 @Nullable Color foreground) {
624 final JEditorPane textPane;
626 textPane = new JEditorPane() {
628 public Dimension getPreferredSize() {
629 // This trick makes text component to carry text over to the next line
630 // if the text line width exceeds parent's width
631 Dimension dimension = super.getPreferredSize();
638 textPane = new JEditorPane();
640 textPane.setFont(font != null ? font : UIUtil.getLabelFont());
641 textPane.setContentType(UIUtil.HTML_MIME);
642 textPane.setEditable(false);
643 if (background != null) {
644 textPane.setBackground(background);
647 textPane.setOpaque(false);
649 textPane.setForeground(foreground != null ? foreground : UIUtil.getLabelForeground());
650 textPane.setFocusable(false);
654 public static void setHtml(@NotNull JEditorPane editorPane,
655 @NotNull String bodyInnerHtml,
656 @Nullable Color foregroundColor) {
657 String html = String.format(
658 "<html><head>%s</head><body>%s</body></html>",
659 UIUtil.getCssFontDeclaration(editorPane.getFont(), foregroundColor, null, null),
662 editorPane.setText(html);
666 public static TextFieldWithHistoryWithBrowseButton createTextFieldWithHistoryWithBrowseButton(@Nullable Project project,
667 @NotNull String browseDialogTitle,
668 @NotNull FileChooserDescriptor fileChooserDescriptor,
669 @Nullable NotNullProducer<List<String>> historyProvider) {
670 TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton = new TextFieldWithHistoryWithBrowseButton();
671 TextFieldWithHistory textFieldWithHistory = textFieldWithHistoryWithBrowseButton.getChildComponent();
672 textFieldWithHistory.setHistorySize(-1);
673 textFieldWithHistory.setMinimumAndPreferredWidth(0);
674 if (historyProvider != null) {
675 addHistoryOnExpansion(textFieldWithHistory, historyProvider);
677 installFileCompletionAndBrowseDialog(
679 textFieldWithHistoryWithBrowseButton,
681 fileChooserDescriptor
683 return textFieldWithHistoryWithBrowseButton;
687 public static <C extends JComponent> ComponentWithBrowseButton<C> wrapWithInfoButton(@NotNull final C component,
688 @NotNull String infoButtonTooltip,
689 @NotNull ActionListener listener) {
690 ComponentWithBrowseButton<C> comp = new ComponentWithBrowseButton<C>(component, listener);
691 FixedSizeButton uiHelpButton = comp.getButton();
692 uiHelpButton.setToolTipText(infoButtonTooltip);
693 uiHelpButton.setIcon(UIUtil.getBalloonInformationIcon());
694 uiHelpButton.setHorizontalAlignment(SwingConstants.CENTER);
695 uiHelpButton.setVerticalAlignment(SwingConstants.CENTER);
699 private static class CopyLinkAction extends AnAction {
701 private final String myUrl;
703 private CopyLinkAction(@NotNull String url) {
704 super("Copy Link Address", null, PlatformIcons.COPY_ICON);
709 public void update(AnActionEvent e) {
710 e.getPresentation().setEnabled(true);
714 public void actionPerformed(AnActionEvent e) {
715 Transferable content = new StringSelection(myUrl);
716 CopyPasteManager.getInstance().setContents(content);
720 private static class OpenLinkInBrowser extends AnAction {
722 private final String myUrl;
724 private OpenLinkInBrowser(@NotNull String url) {
725 super("Open Link in Browser", null, PlatformIcons.WEB_ICON);
730 public void update(AnActionEvent e) {
731 e.getPresentation().setEnabled(true);
735 public void actionPerformed(AnActionEvent e) {
736 BrowserUtil.browse(myUrl);
740 public final static String ELLIPSIS = "...";
741 public static final String ERROR_STR = "www";
742 public static String truncateStringWithEllipsis(final String text, final int maxWidth, final FontMetrics fm) {
743 return truncateStringWithEllipsis(text, maxWidth, new WidthCalculator() {
745 public int stringWidth(String s) {
746 return fm.stringWidth(s);
750 public int charWidth(char c) {
751 return fm.charWidth(c);
756 public interface WidthCalculator {
757 int stringWidth(final String s);
758 int charWidth(final char c);
761 public static String truncateStringWithEllipsis(@NotNull final String text, final int maxWidth, final WidthCalculator fm) {
762 final int error = fm.stringWidth(ERROR_STR);
763 final int wholeWidth = fm.stringWidth(text) + error;
764 if (wholeWidth <= maxWidth || text.isEmpty()) return text;
765 final int ellipsisWidth = fm.stringWidth(ELLIPSIS) + error; // plus some reserve
766 if (ellipsisWidth >= maxWidth) return ELLIPSIS;
768 final int availableWidth = maxWidth - ellipsisWidth;
769 int currentLen = (int)Math.floor(availableWidth / (((double) wholeWidth) / text.length()));
771 final String currentSubstring = text.substring(0, currentLen);
772 int realWidth = fm.stringWidth(currentSubstring);
774 if (realWidth >= availableWidth) {
776 for (int i = currentLen - 1; i >= 0; i--) {
777 if ((realWidth - delta) < availableWidth) return text.substring(0, i) + ELLIPSIS;
778 delta += fm.charWidth(currentSubstring.charAt(i));
780 return text.substring(0, 1) + ELLIPSIS;
783 for (int i = currentLen; i < text.length(); i++) {
784 if ((realWidth + delta) >= availableWidth) return text.substring(0, i) + ELLIPSIS;
785 delta += fm.charWidth(text.charAt(i));
787 return text.substring(0, currentLen) + ELLIPSIS;
791 public static JEditorPane createHtmlLabel(@NotNull final String innerHtml, @Nullable String disabledHtml,
792 @Nullable final Consumer<String> hyperlinkListener) {
793 disabledHtml = disabledHtml == null ? innerHtml : disabledHtml;
794 final Font font = UIUtil.getLabelFont();
795 String html = String.format(
796 "<html><head>%s</head><body>%s</body></html>",
797 UIUtil.getCssFontDeclaration(font, UIUtil.getInactiveTextColor(), null, null),
800 String disabled = String.format(
801 "<html><head>%s</head><body>%s</body></html>",
802 UIUtil.getCssFontDeclaration(font, UIUtil.getInactiveTextColor(), null, null),
806 final JEditorPane pane = new SwingHelper.HtmlViewerBuilder()
807 .setCarryTextOver(false)
808 .setFont(UIUtil.getLabelFont())
809 .setDisabledHtml(disabled)
812 pane.addHyperlinkListener(
813 new HyperlinkListener() {
814 public void hyperlinkUpdate(HyperlinkEvent e) {
815 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
816 if (hyperlinkListener != null) hyperlinkListener.consume(e.getURL() == null ? "" : e.getURL().toString());
817 else BrowserUtil.browse(e.getURL());