summaryrefslogtreecommitdiff
path: root/platform/platform-impl/src/com/intellij/util/ui/SwingHelper.java
blob: ac4c1b12373c0726d0573ca7db726ed9c06f9c1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
package com.intellij.util.ui;

import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserFactory;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComponentWithBrowseButton;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.TextComponentAccessor;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.TextFieldWithHistory;
import com.intellij.ui.TextFieldWithHistoryWithBrowseButton;
import com.intellij.util.NotNullProducer;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ComparatorUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.util.Collection;
import java.util.List;
import java.util.Set;

public class SwingHelper {

  private static final Logger LOG = Logger.getInstance(SwingHelper.class);

  /**
   * Creates panel whose content consists of given {@code children} components
   * stacked vertically each on another in a given order.
   *
   * @param childAlignmentX Component.LEFT_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.RIGHT_ALIGNMENT
   * @param children children components
   * @return created panel
   */
  @NotNull
  public static JPanel newVerticalPanel(float childAlignmentX, Component... children) {
    return newGenericBoxPanel(true, childAlignmentX, children);
  }

  @NotNull
  public static JPanel newLeftAlignedVerticalPanel(Component... children) {
    return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
  }

  @NotNull
  public static JPanel newLeftAlignedVerticalPanel(@NotNull Collection<Component> children) {
    return newVerticalPanel(Component.LEFT_ALIGNMENT, children);
  }

  @NotNull
  public static JPanel newVerticalPanel(float childAlignmentX, @NotNull Collection<Component> children) {
    return newVerticalPanel(childAlignmentX, children.toArray(new Component[children.size()]));
  }

  /**
   * Creates panel whose content consists of given {@code children} components horizontally
   * stacked each on another in a given order.
   *
   * @param childAlignmentY Component.TOP_ALIGNMENT, Component.CENTER_ALIGNMENT or Component.BOTTOM_ALIGNMENT
   * @param children children components
   * @return created panel
   */
  @NotNull
  public static JPanel newHorizontalPanel(float childAlignmentY, Component... children) {
    return newGenericBoxPanel(false, childAlignmentY, children);
  }

  @NotNull
  public static JPanel newHorizontalPanel(float childAlignmentY, @NotNull Collection<Component> children) {
    return newHorizontalPanel(childAlignmentY, children.toArray(new Component[children.size()]));
  }

  private static JPanel newGenericBoxPanel(boolean verticalOrientation,
                                           float childAlignment,
                                           Component... children) {
    JPanel panel = new JPanel();
    int axis = verticalOrientation ? BoxLayout.Y_AXIS : BoxLayout.X_AXIS;
    panel.setLayout(new BoxLayout(panel, axis));
    for (Component child : children) {
      panel.add(child, childAlignment);
      if (child instanceof JComponent) {
        JComponent jChild = (JComponent) child;
        if (verticalOrientation) {
          jChild.setAlignmentX(childAlignment);
        } else {
          jChild.setAlignmentY(childAlignment);
        }
      }
    }
    return panel;
  }

  @NotNull
  public static JPanel wrapWithoutStretch(@NotNull JComponent component) {
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
    panel.add(component);
    return panel;
  }

  @NotNull
  public static JPanel wrapWithHorizontalStretch(@NotNull JComponent component) {
    JPanel panel = new JPanel(new BorderLayout(0, 0));
    panel.add(component, BorderLayout.NORTH);
    return panel;
  }

  public static void setPreferredWidthToFitText(@NotNull TextFieldWithHistoryWithBrowseButton component) {
    int childWidth = calcWidthToFitText(component.getChildComponent().getTextEditor(), 35);
    setPreferredWidthForComponentWithBrowseButton(component, childWidth);
  }

  public static void setPreferredWidthToFitText(@NotNull TextFieldWithBrowseButton component) {
    int childWidth = calcWidthToFitText(component.getChildComponent(), 20);
    setPreferredWidthForComponentWithBrowseButton(component, childWidth);
  }

  private static <T extends JComponent> void setPreferredWidthForComponentWithBrowseButton(@NotNull ComponentWithBrowseButton<T> component,
                                                                                           int childPrefWidth) {
    Dimension buttonPrefSize = component.getButton().getPreferredSize();
    setPreferredWidth(component, childPrefWidth + buttonPrefSize.width);
  }

  public static void setPreferredWidthToFitText(@NotNull JTextField textField) {
    setPreferredWidthToFitText(textField, 15);
  }

  public static void setPreferredWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
    setPreferredSizeToFitText(textField, StringUtil.notNullize(textField.getText()), additionalWidth);
  }

  public static void setPreferredWidthToFitText(@NotNull JTextField textField, @NotNull String text) {
    setPreferredSizeToFitText(textField, text, 15);
  }

  private static void setPreferredSizeToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
    int width = calcWidthToFitText(textField, text, additionalWidth);
    setPreferredWidth(textField, width);
  }

  private static int calcWidthToFitText(@NotNull JTextField textField, int additionalWidth) {
    return calcWidthToFitText(textField, textField.getText(), additionalWidth);
  }

  private static int calcWidthToFitText(@NotNull JTextField textField, @NotNull String text, int additionalWidth) {
    return textField.getFontMetrics(textField.getFont()).stringWidth(text) + additionalWidth;
  }

  public static void adjustDialogSizeToFitPreferredSize(@NotNull DialogWrapper dialogWrapper) {
    JRootPane rootPane = dialogWrapper.getRootPane();
    Dimension componentSize = rootPane.getSize();
    Dimension componentPreferredSize = rootPane.getPreferredSize();
    if (componentPreferredSize.width <= componentSize.width && componentPreferredSize.height <= componentSize.height) {
      return;
    }
    int dw = Math.max(0, componentPreferredSize.width - componentSize.width);
    int dh = Math.max(0, componentPreferredSize.height - componentSize.height);

    Dimension oldDialogSize = dialogWrapper.getSize();
    Dimension newDialogSize = new Dimension(oldDialogSize.width + dw, oldDialogSize.height + dh);

    dialogWrapper.setSize(newDialogSize.width, newDialogSize.height);
    rootPane.revalidate();
    rootPane.repaint();

    LOG.info("DialogWrapper '" + dialogWrapper.getTitle() + "' has been resized (added width: " + dw + ", added height: " + dh + ")");
  }

  public static <T> void updateItems(@NotNull JComboBox comboBox,
                                     @NotNull List<T> newItems,
                                     @Nullable T newSelectedItemIfSelectionCannotBePreserved) {
    if (!shouldUpdate(comboBox, newItems)) {
      return;
    }
    Object selectedItem = comboBox.getSelectedItem();
    //noinspection SuspiciousMethodCalls
    if (selectedItem != null && !newItems.contains(selectedItem)) {
      selectedItem = null;
    }
    if (selectedItem == null && newItems.contains(newSelectedItemIfSelectionCannotBePreserved)) {
      selectedItem = newSelectedItemIfSelectionCannotBePreserved;
    }
    comboBox.removeAllItems();
    for (T newItem : newItems) {
      comboBox.addItem(newItem);
    }
    if (selectedItem != null) {
      int count = comboBox.getItemCount();
      for (int i = 0; i < count; i++) {
        Object item = comboBox.getItemAt(i);
        if (selectedItem.equals(item)) {
          comboBox.setSelectedIndex(i);
          break;
        }
      }
    }
  }

  private static <T> boolean shouldUpdate(@NotNull JComboBox comboBox, @NotNull List<T> newItems) {
    int count = comboBox.getItemCount();
    if (newItems.size() != count) {
      return true;
    }
    for (int i = 0; i < count; i++) {
      Object oldItem = comboBox.getItemAt(i);
      T newItem = newItems.get(i);
      if (!ComparatorUtil.equalsNullable(oldItem, newItem)) {
        return true;
      }
    }
    return false;
  }

  public static void setNoBorderCellRendererFor(@NotNull TableColumn column) {
    final TableCellRenderer previous = column.getCellRenderer();
    column.setCellRenderer(new DefaultTableCellRenderer() {
      @Override
      public Component getTableCellRendererComponent(JTable table,
                                                     Object value,
                                                     boolean isSelected,
                                                     boolean hasFocus,
                                                     int row,
                                                     int column) {
        Component component;
        if (previous != null) {
          component = previous.getTableCellRendererComponent(table, value, isSelected, false, row, column);
        }
        else {
          component = super.getTableCellRendererComponent(table, value, isSelected, false, row, column);
        }
        if (component instanceof JComponent) {
          ((JComponent)component).setBorder(null);
        }
        return component;
      }
    });
  }

  public static void addHistoryOnExpansion(@NotNull final TextFieldWithHistory textFieldWithHistory,
                                           @NotNull final NotNullProducer<List<String>> historyProvider) {
    textFieldWithHistory.addPopupMenuListener(new PopupMenuListener() {
      @Override
      public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
        List<String> newHistory = historyProvider.produce();
        Set<String> newHistorySet = ContainerUtil.newHashSet(newHistory);
        List<String> oldHistory = textFieldWithHistory.getHistory();
        List<String> mergedHistory = ContainerUtil.newArrayList();
        for (String item : oldHistory) {
          if (!newHistorySet.contains(item)) {
            mergedHistory.add(item);
          }
        }
        mergedHistory.addAll(newHistory);
        textFieldWithHistory.setHistory(mergedHistory);

        setLongestAsPrototype(textFieldWithHistory, mergedHistory);

        // one-time initialization
        textFieldWithHistory.removePopupMenuListener(this);
      }

      @Override
      public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
      }

      @Override
      public void popupMenuCanceled(PopupMenuEvent e) {
      }
    });
  }

  private static void setLongestAsPrototype(@NotNull JComboBox comboBox, @NotNull List<String> variants) {
    Object prototypeDisplayValue = comboBox.getPrototypeDisplayValue();
    String prototypeDisplayValueStr = null;
    if (prototypeDisplayValue instanceof String) {
      prototypeDisplayValueStr = (String) prototypeDisplayValue;
    }
    else if (prototypeDisplayValue != null) {
      return;
    }
    String longest = StringUtil.notNullize(prototypeDisplayValueStr);
    boolean updated = false;
    for (String s : variants) {
      if (longest.length() < s.length()) {
        longest = s;
        updated = true;
      }
    }
    if (updated) {
      comboBox.setPrototypeDisplayValue(longest);
    }
  }

  public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
                                                          @NotNull TextFieldWithHistoryWithBrowseButton textFieldWithHistoryWithBrowseButton,
                                                          @NotNull String browseDialogTitle,
                                                          @NotNull FileChooserDescriptor fileChooserDescriptor) {
    doInstall(project,
              textFieldWithHistoryWithBrowseButton,
              textFieldWithHistoryWithBrowseButton.getChildComponent().getTextEditor(),
              browseDialogTitle,
              fileChooserDescriptor,
              TextComponentAccessor.TEXT_FIELD_WITH_HISTORY_WHOLE_TEXT);
  }

  public static void installFileCompletionAndBrowseDialog(@Nullable Project project,
                                                          @NotNull TextFieldWithBrowseButton textFieldWithBrowseButton,
                                                          @NotNull String browseDialogTitle,
                                                          @NotNull FileChooserDescriptor fileChooserDescriptor) {
    doInstall(project,
              textFieldWithBrowseButton,
              textFieldWithBrowseButton.getTextField(),
              browseDialogTitle,
              fileChooserDescriptor,
              TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
  }

  private static <T extends JComponent> void doInstall(@Nullable Project project,
                                                       @NotNull ComponentWithBrowseButton<T> componentWithBrowseButton,
                                                       @NotNull JTextField textField,
                                                       @NotNull String browseDialogTitle,
                                                       @NotNull FileChooserDescriptor fileChooserDescriptor,
                                                       @NotNull TextComponentAccessor<T> textComponentAccessor) {
    fileChooserDescriptor = fileChooserDescriptor.withShowHiddenFiles(SystemInfo.isUnix);
    componentWithBrowseButton.addBrowseFolderListener(
      project,
      new ComponentWithBrowseButton.BrowseFolderActionListener<T>(
        browseDialogTitle,
        null,
        componentWithBrowseButton,
        project,
        fileChooserDescriptor,
        textComponentAccessor
      ),
      true
    );
    FileChooserFactory.getInstance().installFileCompletion(
      textField,
      fileChooserDescriptor,
      true,
      project
    );
  }

  @NotNull
  public static HyperlinkLabel createWebHyperlink(@NotNull String url) {
    return createWebHyperlink(url, url);
  }

  @NotNull
  public static HyperlinkLabel createWebHyperlink(@NotNull String text, @NotNull String url) {
    HyperlinkLabel hyperlink = new HyperlinkLabel(text);
    hyperlink.setHyperlinkTarget(url);

    DefaultActionGroup actionGroup = new DefaultActionGroup();
    actionGroup.add(new OpenLinkInBrowser(url));
    actionGroup.add(new CopyLinkAction(url));

    hyperlink.setComponentPopupMenu(ActionManager.getInstance().createActionPopupMenu("web hyperlink", actionGroup).getComponent());
    return hyperlink;
  }

  public static void setPreferredWidth(@NotNull JComponent component, int width) {
    Dimension preferredSize = component.getPreferredSize();
    preferredSize.width = width;
    component.setPreferredSize(preferredSize);
  }

  @NotNull
  public static JEditorPane createHtmlViewer(boolean carryTextOver,
                                             @Nullable Font font,
                                             @Nullable Color background,
                                             @Nullable Color foreground) {
    final JEditorPane textPane;
    if (carryTextOver) {
      textPane = new JEditorPane() {
        @Override
        public Dimension getPreferredSize() {
          // This trick makes text component to carry text over to the next line
          // if the text line width exceeds parent's width
          Dimension dimension = super.getPreferredSize();
          dimension.width = 0;
          return dimension;
        }
      };
    }
    else {
      textPane = new JEditorPane();
    }
    textPane.setFont(font != null ? font : UIUtil.getLabelFont());
    textPane.setContentType(UIUtil.HTML_MIME);
    textPane.setEditable(false);
    textPane.setBackground(background != null ? background : UIUtil.getLabelBackground());
    textPane.setForeground(foreground != null ? foreground : UIUtil.getLabelForeground());
    return textPane;
  }

  public static void setHtml(@NotNull JEditorPane editorPane, @NotNull String bodyInnerHtml) {
    String html = String.format(
      "<html><head>%s</head><body>%s</body></html>",
      UIUtil.getCssFontDeclaration(editorPane.getFont(), null, null, null),
      bodyInnerHtml
    );
    editorPane.setText(html);
  }

  private static class CopyLinkAction extends AnAction {

    private final String myUrl;

    private CopyLinkAction(@NotNull String url) {
      super("Copy Link Address", null, PlatformIcons.COPY_ICON);
      myUrl = url;
    }

    @Override
    public void update(AnActionEvent e) {
      e.getPresentation().setEnabled(true);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      Transferable content = new StringSelection(myUrl);
      CopyPasteManager.getInstance().setContents(content);
    }
  }

  private static class OpenLinkInBrowser extends AnAction {

    private final String myUrl;

    private OpenLinkInBrowser(@NotNull String url) {
      super("Open Link in Browser", null, PlatformIcons.WEB_ICON);
      myUrl = url;
    }

    @Override
    public void update(AnActionEvent e) {
      e.getPresentation().setEnabled(true);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      BrowserUtil.browse(myUrl);
    }
  }
}