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.diagnostic.Logger;
24 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
25 import com.intellij.openapi.fileChooser.FileChooserFactory;
26 import com.intellij.openapi.ide.CopyPasteManager;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.*;
29 import com.intellij.openapi.util.SystemInfo;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.ui.HyperlinkLabel;
32 import com.intellij.ui.TextFieldWithHistory;
33 import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
34 import com.intellij.util.NotNullProducer;
35 import com.intellij.util.PlatformIcons;
36 import com.intellij.util.containers.ComparatorUtil;
37 import com.intellij.util.containers.ContainerUtil;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
42 import javax.swing.event.PopupMenuEvent;
43 import javax.swing.event.PopupMenuListener;
44 import javax.swing.table.DefaultTableCellRenderer;
45 import javax.swing.table.TableCellRenderer;
46 import javax.swing.table.TableColumn;
48 import java.awt.datatransfer.StringSelection;
49 import java.awt.datatransfer.Transferable;
50 import java.awt.event.ActionListener;
51 import java.util.Collection;
52 import java.util.List;
55 public class SwingHelper {
57 private static final Logger LOG = Logger.getInstance(SwingHelper.class);
60 * Creates panel whose content consists of given {@code children} components
61 * stacked vertically each on another in a given order.
63 * @param childAlignmentX Component.LEFT_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.RIGHT_ALIGNMENT
64 * @param children children components
65 * @return created panel
68 public static JPanel newVerticalPanel(float childAlignmentX, Component... children) {
69 return newGenericBoxPanel(true, childAlignmentX, children);
73 public static JPanel newLeftAlignedVerticalPanel(Component... children) {
74 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
78 public static JPanel newLeftAlignedVerticalPanel(@NotNull Collection<Component> children) {
79 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
83 public static JPanel newVerticalPanel(float childAlignmentX, @NotNull Collection<Component> children) {
84 return newVerticalPanel(childAlignmentX, children.toArray(new Component[children.size()]));
88 * Creates panel whose content consists of given {@code children} components horizontally
89 * stacked each on another in a given order.
91 * @param childAlignmentY Component.TOP_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.BOTTOM_ALIGNMENT
92 * @param children children components
93 * @return created panel
96 public static JPanel newHorizontalPanel(float childAlignmentY, Component... children) {
97 return newGenericBoxPanel(false, childAlignmentY, children);
101 public static JPanel newHorizontalPanel(float childAlignmentY, @NotNull Collection<Component> children) {
102 return newHorizontalPanel(childAlignmentY, children.toArray(new Component[children.size()]));
105 private static JPanel newGenericBoxPanel(boolean verticalOrientation,
106 float childAlignment,
107 Component... children) {
108 JPanel panel = new JPanel();
109 int axis = verticalOrientation ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
110 panel.setLayout(new BoxLayout(panel, axis));
111 for (Component child : children) {
112 panel.add(child, childAlignment);
113 if (child instanceof JComponent) {
114 JComponent jChild = (JComponent)child;
115 if (verticalOrientation) {
116 jChild.setAlignmentX(childAlignment);
119 jChild.setAlignmentY(childAlignment);
127 public static JPanel wrapWithoutStretch(@NotNull JComponent component) {
128 JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
129 panel.add(component);
134 public static JPanel wrapWithHorizontalStretch(@NotNull JComponent component) {
135 JPanel panel = new JPanel(new BorderLayout(0, 0));
136 panel.add(component, BorderLayout.NORTH);
140 public static void setPreferredWidthToFitText(@NotNull TextFieldWithHistoryWithBrowseButton component) {
141 int childWidth = calcWidthToFitText(component.getChildComponent().getTextEditor(), 35);
142 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
145 public static void setPreferredWidthToFitText(@NotNull TextFieldWithBrowseButton component) {
146 int childWidth = calcWidthToFitText(component.getChildComponent(), 20);
147 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
150 private static <T extends JComponent> void setPreferredWidthForComponentWithBrowseButton(@NotNull ComponentWithBrowseButton<T> component,
151 int childPrefWidth) {
152 Dimension buttonPrefSize = component.getButton().getPreferredSize();
153 setPreferredWidth(component, childPrefWidth + buttonPrefSize.width);
156 public static void setPreferredWidthToFitText(@NotNull JTextField textField) {
157 setPreferredWidthToFitText(textField, 15);
160 public static void setPreferredWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
161 setPreferredSizeToFitText(textField, StringUtil.notNullize(textField.getText()), additionalWidth);
164 public static void setPreferredWidthToFitText(@NotNull JTextField textField, @NotNull String text) {
165 setPreferredSizeToFitText(textField, text, 15);
168 private static void setPreferredSizeToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
169 int width = calcWidthToFitText(textField, text, additionalWidth);
170 setPreferredWidth(textField, width);
173 private static int calcWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
174 return calcWidthToFitText(textField, textField.getText(), additionalWidth);
177 private static int calcWidthToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
178 return textField.getFontMetrics(textField.getFont()).stringWidth(text) + additionalWidth;
181 public static void adjustDialogSizeToFitPreferredSize(@NotNull DialogWrapper dialogWrapper) {
182 JRootPane rootPane = dialogWrapper.getRootPane();
183 Dimension componentSize = rootPane.getSize();
184 Dimension componentPreferredSize = rootPane.getPreferredSize();
185 if (componentPreferredSize.width <= componentSize.width && componentPreferredSize.height <= componentSize.height) {
188 int dw = Math.max(0, componentPreferredSize.width - componentSize.width);
189 int dh = Math.max(0, componentPreferredSize.height - componentSize.height);
191 Dimension oldDialogSize = dialogWrapper.getSize();
192 Dimension newDialogSize = new Dimension(oldDialogSize.width + dw, oldDialogSize.height + dh);
194 dialogWrapper.setSize(newDialogSize.width, newDialogSize.height);
195 rootPane.revalidate();
198 LOG.info("DialogWrapper '" + dialogWrapper.getTitle() + "' has been re-sized (added width: " + dw + ", added height: " + dh + ")");
201 public static <T> void updateItems(@NotNull JComboBox comboBox,
202 @NotNull List<T> newItems,
203 @Nullable T newSelectedItemIfSelectionCannotBePreserved) {
204 if (!shouldUpdate(comboBox, newItems)) {
207 Object selectedItem = comboBox.getSelectedItem();
208 //noinspection SuspiciousMethodCalls
209 if (selectedItem != null && !newItems.contains(selectedItem)) {
212 if (selectedItem == null && newItems.contains(newSelectedItemIfSelectionCannotBePreserved)) {
213 selectedItem = newSelectedItemIfSelectionCannotBePreserved;
215 comboBox.removeAllItems();
216 for (T newItem : newItems) {
217 comboBox.addItem(newItem);
219 if (selectedItem != null) {
220 int count = comboBox.getItemCount();
221 for (int i = 0; i < count; i++) {
222 Object item = comboBox.getItemAt(i);
223 if (selectedItem.equals(item)) {
224 comboBox.setSelectedIndex(i);
231 private static <T> boolean shouldUpdate(@NotNull JComboBox comboBox, @NotNull List<T> newItems) {
232 int count = comboBox.getItemCount();
233 if (newItems.size() != count) {
236 for (int i = 0; i < count; i++) {
237 Object oldItem = comboBox.getItemAt(i);
238 T newItem = newItems.get(i);
239 if (!ComparatorUtil.equalsNullable(oldItem, newItem)) {
246 public static void setNoBorderCellRendererFor(@NotNull TableColumn column) {
247 final TableCellRenderer previous = column.getCellRenderer();
248 column.setCellRenderer(new DefaultTableCellRenderer() {
250 public Component getTableCellRendererComponent(JTable table,
257 if (previous != null) {
258 component = previous.getTableCellRendererComponent(table, value, isSelected, false, row, column);
261 component = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
263 if (component instanceof JComponent) {
264 ((JComponent)component).setBorder(null);
271 public static void addHistoryOnExpansion(@NotNull final TextFieldWithHistory textFieldWithHistory,
272 @NotNull final NotNullProducer<List<String>> historyProvider) {
273 textFieldWithHistory.addPopupMenuListener(new PopupMenuListener() {
275 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
276 List<String> history = historyProvider.produce();
277 setHistory(textFieldWithHistory, ContainerUtil.notNullize(history), true);
278 // one-time initialization
279 textFieldWithHistory.removePopupMenuListener(this);
283 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
287 public void popupMenuCanceled(PopupMenuEvent e) {
292 public static void setHistory(@NotNull TextFieldWithHistory textFieldWithHistory,
293 @NotNull List<String> history,
294 boolean mergeWithPrevHistory) {
295 Set<String> newHistorySet = ContainerUtil.newHashSet(history);
296 List<String> prevHistory = textFieldWithHistory.getHistory();
297 List<String> mergedHistory = ContainerUtil.newArrayList();
298 if (mergeWithPrevHistory) {
299 for (String item : prevHistory) {
300 if (!newHistorySet.contains(item)) {
301 mergedHistory.add(item);
306 String currentText = textFieldWithHistory.getText();
307 if (StringUtil.isNotEmpty(currentText) && !newHistorySet.contains(currentText)) {
308 mergedHistory.add(currentText);
311 mergedHistory.addAll(history);
312 textFieldWithHistory.setHistory(mergedHistory);
313 setLongestAsPrototype(textFieldWithHistory, mergedHistory);
316 private static void setLongestAsPrototype(@NotNull JComboBox comboBox, @NotNull List<String> variants) {
317 Object prototypeDisplayValue = comboBox.getPrototypeDisplayValue();
318 String prototypeDisplayValueStr = null;
319 if (prototypeDisplayValue instanceof String) {
320 prototypeDisplayValueStr = (String)prototypeDisplayValue;
322 else if (prototypeDisplayValue != null) {
325 String longest = StringUtil.notNullize(prototypeDisplayValueStr);
326 boolean updated = false;
327 for (String s : variants) {
328 if (longest.length() < s.length()) {
334 comboBox.setPrototypeDisplayValue(longest);
338 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
339 @NotNull TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton,
340 @NotNull String browseDialogTitle,
341 @NotNull FileChooserDescriptor fileChooserDescriptor) {
343 textFieldWithHistoryWithBrowseButton,
344 textFieldWithHistoryWithBrowseButton.getChildComponent().getTextEditor(),
346 fileChooserDescriptor,
347 TextComponentAccessor.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT);
350 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
351 @NotNull TextFieldWithBrowseButton textFieldWithBrowseButton,
352 @NotNull String browseDialogTitle,
353 @NotNull FileChooserDescriptor fileChooserDescriptor) {
355 textFieldWithBrowseButton,
356 textFieldWithBrowseButton.getTextField(),
358 fileChooserDescriptor,
359 TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
362 private static <T extends JComponent> void doInstall(@Nullable Project project,
363 @NotNull ComponentWithBrowseButton<T> componentWithBrowseButton,
364 @NotNull JTextField textField,
365 @NotNull String browseDialogTitle,
366 @NotNull FileChooserDescriptor fileChooserDescriptor,
367 @NotNull TextComponentAccessor<T> textComponentAccessor) {
368 fileChooserDescriptor = fileChooserDescriptor.withShowHiddenFiles(SystemInfo.isUnix);
369 componentWithBrowseButton.addBrowseFolderListener(
371 new ComponentWithBrowseButton.BrowseFolderActionListener<T>(
374 componentWithBrowseButton,
376 fileChooserDescriptor,
377 textComponentAccessor
381 FileChooserFactory.getInstance().installFileCompletion(
383 fileChooserDescriptor,
390 public static HyperlinkLabel createWebHyperlink(@NotNull String url) {
391 return createWebHyperlink(url, url);
395 public static HyperlinkLabel createWebHyperlink(@NotNull String text, @NotNull String url) {
396 HyperlinkLabel hyperlink = new HyperlinkLabel(text);
397 hyperlink.setHyperlinkTarget(url);
399 DefaultActionGroup actionGroup = new DefaultActionGroup();
400 actionGroup.add(new OpenLinkInBrowser(url));
401 actionGroup.add(new CopyLinkAction(url));
403 hyperlink.setComponentPopupMenu(ActionManager.getInstance().createActionPopupMenu("web hyperlink", actionGroup).getComponent());
407 public static void setPreferredWidth(@NotNull JComponent component, int width) {
408 Dimension preferredSize = component.getPreferredSize();
409 preferredSize.width = width;
410 component.setPreferredSize(preferredSize);
413 public static class HtmlViewerBuilder {
414 private boolean myCarryTextOver;
415 private String myDisabledHtml;
417 private Color myBackground;
418 private Color myForeground;
420 public JEditorPane create() {
421 final JEditorPane textPane = new JEditorPane() {
422 private boolean myEnabled = true;
423 private String myEnabledHtml;
426 public Dimension getPreferredSize() {
427 // This trick makes text component to carry text over to the next line
428 // if the text line width exceeds parent's width
429 Dimension dimension = super.getPreferredSize();
430 if (myCarryTextOver) {
437 public void setText(String t) {
438 if (myDisabledHtml != null) {
447 public void setEnabled(boolean enabled) {
448 if (myDisabledHtml != null) {
451 setText(myEnabledHtml);
453 setText(myDisabledHtml);
455 super.setEnabled(true);
457 super.setEnabled(enabled);
461 textPane.setFont(myFont != null ? myFont : UIUtil.getLabelFont());
462 textPane.setContentType(UIUtil.HTML_MIME);
463 textPane.setEditable(false);
464 if (myBackground != null) {
465 textPane.setBackground(myBackground);
468 textPane.setOpaque(false);
470 textPane.setForeground(myForeground != null ? myForeground : UIUtil.getLabelForeground());
471 textPane.setFocusable(false);
475 public HtmlViewerBuilder setCarryTextOver(boolean carryTextOver) {
476 myCarryTextOver = carryTextOver;
480 public HtmlViewerBuilder setDisabledHtml(String disabledHtml) {
481 myDisabledHtml = disabledHtml;
485 public HtmlViewerBuilder setFont(Font font) {
490 public HtmlViewerBuilder setBackground(Color background) {
491 myBackground = background;
495 public HtmlViewerBuilder setForeground(Color foreground) {
496 myForeground = foreground;
502 public static JEditorPane createHtmlViewer(boolean carryTextOver,
504 @Nullable Color background,
505 @Nullable Color foreground) {
506 final JEditorPane textPane;
508 textPane = new JEditorPane() {
510 public Dimension getPreferredSize() {
511 // This trick makes text component to carry text over to the next line
512 // if the text line width exceeds parent's width
513 Dimension dimension = super.getPreferredSize();
520 textPane = new JEditorPane();
522 textPane.setFont(font != null ? font : UIUtil.getLabelFont());
523 textPane.setContentType(UIUtil.HTML_MIME);
524 textPane.setEditable(false);
525 if (background != null) {
526 textPane.setBackground(background);
529 textPane.setOpaque(false);
531 textPane.setForeground(foreground != null ? foreground : UIUtil.getLabelForeground());
532 textPane.setFocusable(false);
536 public static void setHtml(@NotNull JEditorPane editorPane,
537 @NotNull String bodyInnerHtml,
538 @Nullable Color foregroundColor) {
539 String html = String.format(
540 "<html><head>%s</head><body>%s</body></html>",
541 UIUtil.getCssFontDeclaration(editorPane.getFont(), foregroundColor, null, null),
544 editorPane.setText(html);
548 public static TextFieldWithHistoryWithBrowseButton createTextFieldWithHistoryWithBrowseButton(@Nullable Project project,
549 @NotNull String browseDialogTitle,
550 @NotNull FileChooserDescriptor fileChooserDescriptor,
551 @Nullable NotNullProducer<List<String>> historyProvider) {
552 TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton = new TextFieldWithHistoryWithBrowseButton();
553 TextFieldWithHistory textFieldWithHistory = textFieldWithHistoryWithBrowseButton.getChildComponent();
554 textFieldWithHistory.setHistorySize(-1);
555 textFieldWithHistory.setMinimumAndPreferredWidth(0);
556 if (historyProvider != null) {
557 addHistoryOnExpansion(textFieldWithHistory, historyProvider);
559 installFileCompletionAndBrowseDialog(
561 textFieldWithHistoryWithBrowseButton,
563 fileChooserDescriptor
565 return textFieldWithHistoryWithBrowseButton;
569 public static <C extends JComponent> ComponentWithBrowseButton<C> wrapWithInfoButton(@NotNull final C component,
570 @NotNull String infoButtonTooltip,
571 @NotNull ActionListener listener) {
572 ComponentWithBrowseButton<C> comp = new ComponentWithBrowseButton<C>(component, listener);
573 FixedSizeButton uiHelpButton = comp.getButton();
574 uiHelpButton.setToolTipText(infoButtonTooltip);
575 uiHelpButton.setIcon(UIUtil.getBalloonInformationIcon());
576 uiHelpButton.setHorizontalAlignment(SwingConstants.CENTER);
577 uiHelpButton.setVerticalAlignment(SwingConstants.CENTER);
581 private static class CopyLinkAction extends AnAction {
583 private final String myUrl;
585 private CopyLinkAction(@NotNull String url) {
586 super("Copy Link Address", null, PlatformIcons.COPY_ICON);
591 public void update(AnActionEvent e) {
592 e.getPresentation().setEnabled(true);
596 public void actionPerformed(AnActionEvent e) {
597 Transferable content = new StringSelection(myUrl);
598 CopyPasteManager.getInstance().setContents(content);
602 private static class OpenLinkInBrowser extends AnAction {
604 private final String myUrl;
606 private OpenLinkInBrowser(@NotNull String url) {
607 super("Open Link in Browser", null, PlatformIcons.WEB_ICON);
612 public void update(AnActionEvent e) {
613 e.getPresentation().setEnabled(true);
617 public void actionPerformed(AnActionEvent e) {
618 BrowserUtil.browse(myUrl);