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.ComponentWithBrowseButton;
29 import com.intellij.openapi.ui.DialogWrapper;
30 import com.intellij.openapi.ui.TextComponentAccessor;
31 import com.intellij.openapi.ui.TextFieldWithBrowseButton;
32 import com.intellij.openapi.util.SystemInfo;
33 import com.intellij.openapi.util.text.StringUtil;
34 import com.intellij.ui.HyperlinkLabel;
35 import com.intellij.ui.TextFieldWithHistory;
36 import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
37 import com.intellij.util.NotNullProducer;
38 import com.intellij.util.PlatformIcons;
39 import com.intellij.util.containers.ComparatorUtil;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
45 import javax.swing.event.PopupMenuEvent;
46 import javax.swing.event.PopupMenuListener;
47 import javax.swing.table.DefaultTableCellRenderer;
48 import javax.swing.table.TableCellRenderer;
49 import javax.swing.table.TableColumn;
51 import java.awt.datatransfer.StringSelection;
52 import java.awt.datatransfer.Transferable;
53 import java.util.Collection;
54 import java.util.List;
57 public class SwingHelper {
59 private static final Logger LOG = Logger.getInstance(SwingHelper.class);
62 * Creates panel whose content consists of given {@code children} components
63 * stacked vertically each on another in a given order.
65 * @param childAlignmentX Component.LEFT_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.RIGHT_ALIGNMENT
66 * @param children children components
67 * @return created panel
70 public static JPanel newVerticalPanel(float childAlignmentX, Component... children) {
71 return newGenericBoxPanel(true, childAlignmentX, children);
75 public static JPanel newLeftAlignedVerticalPanel(Component... children) {
76 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
80 public static JPanel newLeftAlignedVerticalPanel(@NotNull Collection<Component> children) {
81 return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
85 public static JPanel newVerticalPanel(float childAlignmentX, @NotNull Collection<Component> children) {
86 return newVerticalPanel(childAlignmentX, children.toArray(new Component[children.size()]));
90 * Creates panel whose content consists of given {@code children} components horizontally
91 * stacked each on another in a given order.
93 * @param childAlignmentY Component.TOP_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.BOTTOM_ALIGNMENT
94 * @param children children components
95 * @return created panel
98 public static JPanel newHorizontalPanel(float childAlignmentY, Component... children) {
99 return newGenericBoxPanel(false, childAlignmentY, children);
103 public static JPanel newHorizontalPanel(float childAlignmentY, @NotNull Collection<Component> children) {
104 return newHorizontalPanel(childAlignmentY, children.toArray(new Component[children.size()]));
107 private static JPanel newGenericBoxPanel(boolean verticalOrientation,
108 float childAlignment,
109 Component... children) {
110 JPanel panel = new JPanel();
111 int axis = verticalOrientation ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
112 panel.setLayout(new BoxLayout(panel, axis));
113 for (Component child : children) {
114 panel.add(child, childAlignment);
115 if (child instanceof JComponent) {
116 JComponent jChild = (JComponent)child;
117 if (verticalOrientation) {
118 jChild.setAlignmentX(childAlignment);
121 jChild.setAlignmentY(childAlignment);
129 public static JPanel wrapWithoutStretch(@NotNull JComponent component) {
130 JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
131 panel.add(component);
136 public static JPanel wrapWithHorizontalStretch(@NotNull JComponent component) {
137 JPanel panel = new JPanel(new BorderLayout(0, 0));
138 panel.add(component, BorderLayout.NORTH);
142 public static void setPreferredWidthToFitText(@NotNull TextFieldWithHistoryWithBrowseButton component) {
143 int childWidth = calcWidthToFitText(component.getChildComponent().getTextEditor(), 35);
144 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
147 public static void setPreferredWidthToFitText(@NotNull TextFieldWithBrowseButton component) {
148 int childWidth = calcWidthToFitText(component.getChildComponent(), 20);
149 setPreferredWidthForComponentWithBrowseButton(component, childWidth);
152 private static <T extends JComponent> void setPreferredWidthForComponentWithBrowseButton(@NotNull ComponentWithBrowseButton<T> component,
153 int childPrefWidth) {
154 Dimension buttonPrefSize = component.getButton().getPreferredSize();
155 setPreferredWidth(component, childPrefWidth + buttonPrefSize.width);
158 public static void setPreferredWidthToFitText(@NotNull JTextField textField) {
159 setPreferredWidthToFitText(textField, 15);
162 public static void setPreferredWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
163 setPreferredSizeToFitText(textField, StringUtil.notNullize(textField.getText()), additionalWidth);
166 public static void setPreferredWidthToFitText(@NotNull JTextField textField, @NotNull String text) {
167 setPreferredSizeToFitText(textField, text, 15);
170 private static void setPreferredSizeToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
171 int width = calcWidthToFitText(textField, text, additionalWidth);
172 setPreferredWidth(textField, width);
175 private static int calcWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
176 return calcWidthToFitText(textField, textField.getText(), additionalWidth);
179 private static int calcWidthToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
180 return textField.getFontMetrics(textField.getFont()).stringWidth(text) + additionalWidth;
183 public static void adjustDialogSizeToFitPreferredSize(@NotNull DialogWrapper dialogWrapper) {
184 JRootPane rootPane = dialogWrapper.getRootPane();
185 Dimension componentSize = rootPane.getSize();
186 Dimension componentPreferredSize = rootPane.getPreferredSize();
187 if (componentPreferredSize.width <= componentSize.width && componentPreferredSize.height <= componentSize.height) {
190 int dw = Math.max(0, componentPreferredSize.width - componentSize.width);
191 int dh = Math.max(0, componentPreferredSize.height - componentSize.height);
193 Dimension oldDialogSize = dialogWrapper.getSize();
194 Dimension newDialogSize = new Dimension(oldDialogSize.width + dw, oldDialogSize.height + dh);
196 dialogWrapper.setSize(newDialogSize.width, newDialogSize.height);
197 rootPane.revalidate();
200 LOG.info("DialogWrapper '" + dialogWrapper.getTitle() + "' has been resized (added width: " + dw + ", added height: " + dh + ")");
203 public static <T> void updateItems(@NotNull JComboBox comboBox,
204 @NotNull List<T> newItems,
205 @Nullable T newSelectedItemIfSelectionCannotBePreserved) {
206 if (!shouldUpdate(comboBox, newItems)) {
209 Object selectedItem = comboBox.getSelectedItem();
210 //noinspection SuspiciousMethodCalls
211 if (selectedItem != null && !newItems.contains(selectedItem)) {
214 if (selectedItem == null && newItems.contains(newSelectedItemIfSelectionCannotBePreserved)) {
215 selectedItem = newSelectedItemIfSelectionCannotBePreserved;
217 comboBox.removeAllItems();
218 for (T newItem : newItems) {
219 comboBox.addItem(newItem);
221 if (selectedItem != null) {
222 int count = comboBox.getItemCount();
223 for (int i = 0; i < count; i++) {
224 Object item = comboBox.getItemAt(i);
225 if (selectedItem.equals(item)) {
226 comboBox.setSelectedIndex(i);
233 private static <T> boolean shouldUpdate(@NotNull JComboBox comboBox, @NotNull List<T> newItems) {
234 int count = comboBox.getItemCount();
235 if (newItems.size() != count) {
238 for (int i = 0; i < count; i++) {
239 Object oldItem = comboBox.getItemAt(i);
240 T newItem = newItems.get(i);
241 if (!ComparatorUtil.equalsNullable(oldItem, newItem)) {
248 public static void setNoBorderCellRendererFor(@NotNull TableColumn column) {
249 final TableCellRenderer previous = column.getCellRenderer();
250 column.setCellRenderer(new DefaultTableCellRenderer() {
252 public Component getTableCellRendererComponent(JTable table,
259 if (previous != null) {
260 component = previous.getTableCellRendererComponent(table, value, isSelected, false, row, column);
263 component = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
265 if (component instanceof JComponent) {
266 ((JComponent)component).setBorder(null);
273 public static void addHistoryOnExpansion(@NotNull final TextFieldWithHistory textFieldWithHistory,
274 @NotNull final NotNullProducer<List<String>> historyProvider) {
275 textFieldWithHistory.addPopupMenuListener(new PopupMenuListener() {
277 public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
278 setHistory(textFieldWithHistory, historyProvider.produce(), true);
279 // one-time initialization
280 textFieldWithHistory.removePopupMenuListener(this);
284 public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
288 public void popupMenuCanceled(PopupMenuEvent e) {
293 public static void setHistory(@NotNull TextFieldWithHistory textFieldWithHistory,
294 @NotNull List<String> history,
295 boolean mergeWithPrevHistory) {
296 Set<String> newHistorySet = ContainerUtil.newHashSet(history);
297 List<String> prevHistory = textFieldWithHistory.getHistory();
298 List<String> mergedHistory = ContainerUtil.newArrayList();
299 if (mergeWithPrevHistory) {
300 for (String item : prevHistory) {
301 if (!newHistorySet.contains(item)) {
302 mergedHistory.add(item);
307 String currentText = textFieldWithHistory.getText();
308 if (StringUtil.isNotEmpty(currentText) && !newHistorySet.contains(currentText)) {
309 mergedHistory.add(currentText);
312 mergedHistory.addAll(history);
313 textFieldWithHistory.setHistory(mergedHistory);
314 setLongestAsPrototype(textFieldWithHistory, mergedHistory);
317 private static void setLongestAsPrototype(@NotNull JComboBox comboBox, @NotNull List<String> variants) {
318 Object prototypeDisplayValue = comboBox.getPrototypeDisplayValue();
319 String prototypeDisplayValueStr = null;
320 if (prototypeDisplayValue instanceof String) {
321 prototypeDisplayValueStr = (String)prototypeDisplayValue;
323 else if (prototypeDisplayValue != null) {
326 String longest = StringUtil.notNullize(prototypeDisplayValueStr);
327 boolean updated = false;
328 for (String s : variants) {
329 if (longest.length() < s.length()) {
335 comboBox.setPrototypeDisplayValue(longest);
339 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
340 @NotNull TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton,
341 @NotNull String browseDialogTitle,
342 @NotNull FileChooserDescriptor fileChooserDescriptor) {
344 textFieldWithHistoryWithBrowseButton,
345 textFieldWithHistoryWithBrowseButton.getChildComponent().getTextEditor(),
347 fileChooserDescriptor,
348 TextComponentAccessor.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT);
351 public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
352 @NotNull TextFieldWithBrowseButton textFieldWithBrowseButton,
353 @NotNull String browseDialogTitle,
354 @NotNull FileChooserDescriptor fileChooserDescriptor) {
356 textFieldWithBrowseButton,
357 textFieldWithBrowseButton.getTextField(),
359 fileChooserDescriptor,
360 TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
363 private static <T extends JComponent> void doInstall(@Nullable Project project,
364 @NotNull ComponentWithBrowseButton<T> componentWithBrowseButton,
365 @NotNull JTextField textField,
366 @NotNull String browseDialogTitle,
367 @NotNull FileChooserDescriptor fileChooserDescriptor,
368 @NotNull TextComponentAccessor<T> textComponentAccessor) {
369 fileChooserDescriptor = fileChooserDescriptor.withShowHiddenFiles(SystemInfo.isUnix);
370 componentWithBrowseButton.addBrowseFolderListener(
372 new ComponentWithBrowseButton.BrowseFolderActionListener<T>(
375 componentWithBrowseButton,
377 fileChooserDescriptor,
378 textComponentAccessor
382 FileChooserFactory.getInstance().installFileCompletion(
384 fileChooserDescriptor,
391 public static HyperlinkLabel createWebHyperlink(@NotNull String url) {
392 return createWebHyperlink(url, url);
396 public static HyperlinkLabel createWebHyperlink(@NotNull String text, @NotNull String url) {
397 HyperlinkLabel hyperlink = new HyperlinkLabel(text);
398 hyperlink.setHyperlinkTarget(url);
400 DefaultActionGroup actionGroup = new DefaultActionGroup();
401 actionGroup.add(new OpenLinkInBrowser(url));
402 actionGroup.add(new CopyLinkAction(url));
404 hyperlink.setComponentPopupMenu(ActionManager.getInstance().createActionPopupMenu("web hyperlink", actionGroup).getComponent());
408 public static void setPreferredWidth(@NotNull JComponent component, int width) {
409 Dimension preferredSize = component.getPreferredSize();
410 preferredSize.width = width;
411 component.setPreferredSize(preferredSize);
414 public static class HtmlViewerBuilder {
415 private boolean myCarryTextOver;
416 private String myDisabledHtml;
418 private Color myBackground;
419 private Color myForeground;
421 public JEditorPane create() {
422 final JEditorPane textPane = new JEditorPane() {
423 private boolean myEnabled = true;
424 private String myEnabledHtml;
427 public Dimension getPreferredSize() {
428 // This trick makes text component to carry text over to the next line
429 // if the text line width exceeds parent's width
430 Dimension dimension = super.getPreferredSize();
431 if (myCarryTextOver) {
438 public void setText(String t) {
439 if (myDisabledHtml != null) {
448 public void setEnabled(boolean enabled) {
449 if (myDisabledHtml != null) {
452 setText(myEnabledHtml);
454 setText(myDisabledHtml);
456 super.setEnabled(true);
458 super.setEnabled(enabled);
462 textPane.setFont(myFont != null ? myFont : UIUtil.getLabelFont());
463 textPane.setContentType(UIUtil.HTML_MIME);
464 textPane.setEditable(false);
465 if (myBackground != null) {
466 textPane.setBackground(myBackground);
469 textPane.setOpaque(false);
471 textPane.setForeground(myForeground != null ? myForeground : UIUtil.getLabelForeground());
472 textPane.setFocusable(false);
476 public HtmlViewerBuilder setCarryTextOver(boolean carryTextOver) {
477 myCarryTextOver = carryTextOver;
481 public HtmlViewerBuilder setDisabledHtml(String disabledHtml) {
482 myDisabledHtml = disabledHtml;
486 public HtmlViewerBuilder setFont(Font font) {
491 public HtmlViewerBuilder setBackground(Color background) {
492 myBackground = background;
496 public HtmlViewerBuilder setForeground(Color foreground) {
497 myForeground = foreground;
503 public static JEditorPane createHtmlViewer(boolean carryTextOver,
505 @Nullable Color background,
506 @Nullable Color foreground) {
507 final JEditorPane textPane;
509 textPane = new JEditorPane() {
511 public Dimension getPreferredSize() {
512 // This trick makes text component to carry text over to the next line
513 // if the text line width exceeds parent's width
514 Dimension dimension = super.getPreferredSize();
521 textPane = new JEditorPane();
523 textPane.setFont(font != null ? font : UIUtil.getLabelFont());
524 textPane.setContentType(UIUtil.HTML_MIME);
525 textPane.setEditable(false);
526 if (background != null) {
527 textPane.setBackground(background);
530 textPane.setOpaque(false);
532 textPane.setForeground(foreground != null ? foreground : UIUtil.getLabelForeground());
533 textPane.setFocusable(false);
537 public static void setHtml(@NotNull JEditorPane editorPane,
538 @NotNull String bodyInnerHtml,
539 @Nullable Color foregroundColor) {
540 String html = String.format(
541 "<html><head>%s</head><body>%s</body></html>",
542 UIUtil.getCssFontDeclaration(editorPane.getFont(), foregroundColor, null, null),
545 editorPane.setText(html);
549 public static TextFieldWithHistoryWithBrowseButton createTextFieldWithHistoryWithBrowseButton(@Nullable Project project,
550 @NotNull String browseDialogTitle,
551 @NotNull FileChooserDescriptor fileChooserDescriptor,
552 @Nullable NotNullProducer<List<String>> historyProvider) {
553 TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton = new TextFieldWithHistoryWithBrowseButton();
554 TextFieldWithHistory textFieldWithHistory = textFieldWithHistoryWithBrowseButton.getChildComponent();
555 textFieldWithHistory.setHistorySize(-1);
556 textFieldWithHistory.setMinimumAndPreferredWidth(0);
557 if (historyProvider != null) {
558 addHistoryOnExpansion(textFieldWithHistory, historyProvider);
560 installFileCompletionAndBrowseDialog(
562 textFieldWithHistoryWithBrowseButton,
564 fileChooserDescriptor
566 return textFieldWithHistoryWithBrowseButton;
569 private static class CopyLinkAction extends AnAction {
571 private final String myUrl;
573 private CopyLinkAction(@NotNull String url) {
574 super("Copy Link Address", null, PlatformIcons.COPY_ICON);
579 public void update(AnActionEvent e) {
580 e.getPresentation().setEnabled(true);
584 public void actionPerformed(AnActionEvent e) {
585 Transferable content = new StringSelection(myUrl);
586 CopyPasteManager.getInstance().setContents(content);
590 private static class OpenLinkInBrowser extends AnAction {
592 private final String myUrl;
594 private OpenLinkInBrowser(@NotNull String url) {
595 super("Open Link in Browser", null, PlatformIcons.WEB_ICON);
600 public void update(AnActionEvent e) {
601 e.getPresentation().setEnabled(true);
605 public void actionPerformed(AnActionEvent e) {
606 BrowserUtil.browse(myUrl);