2 * Copyright 2000-2015 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.openapi.ui;
18 import com.intellij.openapi.Disposable;
19 import com.intellij.openapi.actionSystem.AnActionEvent;
20 import com.intellij.openapi.actionSystem.CustomShortcutSet;
21 import com.intellij.openapi.actionSystem.ShortcutSet;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.fileChooser.FileChooser;
25 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
26 import com.intellij.openapi.keymap.KeymapUtil;
27 import com.intellij.openapi.project.DumbAwareAction;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.SystemInfo;
30 import com.intellij.openapi.util.io.FileUtil;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.openapi.vfs.LocalFileSystem;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.ui.GuiUtils;
35 import com.intellij.ui.UIBundle;
36 import com.intellij.util.ui.UIUtil;
37 import com.intellij.util.ui.accessibility.ScreenReader;
38 import org.jetbrains.annotations.Nls;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
44 import java.awt.event.ActionEvent;
45 import java.awt.event.ActionListener;
46 import java.awt.event.InputEvent;
47 import java.awt.event.KeyEvent;
49 public class ComponentWithBrowseButton<Comp extends JComponent> extends JPanel implements Disposable {
50 private static final Logger LOG = Logger.getInstance(ComponentWithBrowseButton.class);
52 private final Comp myComponent;
53 private final FixedSizeButton myBrowseButton;
54 private boolean myButtonEnabled = true;
56 public ComponentWithBrowseButton(Comp component, @Nullable ActionListener browseActionListener) {
57 super(new BorderLayout(SystemInfo.isMac ? 0 : 2, 0));
59 myComponent = component;
60 // required! otherwise JPanel will occasionally gain focus instead of the component
62 add(myComponent, BorderLayout.CENTER);
64 myBrowseButton = new FixedSizeButton(myComponent);
65 if (browseActionListener != null) {
66 myBrowseButton.addActionListener(browseActionListener);
68 add(centerComponentVertically(myBrowseButton), BorderLayout.EAST);
70 myBrowseButton.setToolTipText(UIBundle.message("component.with.browse.button.browse.button.tooltip.text"));
71 // FixedSizeButton isn't focusable but it should be selectable via keyboard.
72 if (ApplicationManager.getApplication() != null) { // avoid crash at design time
73 new MyDoClickAction(myBrowseButton).registerShortcut(myComponent);
75 if (ScreenReader.isActive()) {
76 myBrowseButton.setFocusable(true);
77 myBrowseButton.getAccessibleContext().setAccessibleName("Browse");
82 private static JPanel centerComponentVertically(@NotNull Component component) {
83 JPanel panel = new JPanel(new GridBagLayout());
84 panel.add(component, new GridBagConstraints());
88 public final Comp getChildComponent() {
92 public void setTextFieldPreferredWidth(final int charCount) {
93 final Comp comp = getChildComponent();
94 Dimension size = GuiUtils.getSizeByChars(charCount, comp);
95 comp.setPreferredSize(size);
96 final Dimension preferredSize = myBrowseButton.getPreferredSize();
97 setPreferredSize(new Dimension(size.width + preferredSize.width + 2, UIUtil.isUnderAquaLookAndFeel() ? preferredSize.height : preferredSize.height + 2));
101 public void setEnabled(boolean enabled) {
102 super.setEnabled(enabled);
103 myBrowseButton.setEnabled(enabled && myButtonEnabled);
104 myComponent.setEnabled(enabled);
107 public void setButtonEnabled(boolean buttonEnabled) {
108 myButtonEnabled = buttonEnabled;
109 setEnabled(isEnabled());
112 public void setButtonIcon(Icon icon) {
113 myBrowseButton.setIcon(icon);
117 * Adds specified <code>listener</code> to the browse button.
119 public void addActionListener(ActionListener listener){
120 myBrowseButton.addActionListener(listener);
123 public void removeActionListener(ActionListener listener) {
124 myBrowseButton.removeActionListener(listener);
127 public void addBrowseFolderListener(@Nullable @Nls(capitalization = Nls.Capitalization.Title) String title,
128 @Nullable @Nls(capitalization = Nls.Capitalization.Sentence) String description,
129 @Nullable Project project,
130 FileChooserDescriptor fileChooserDescriptor,
131 TextComponentAccessor<Comp> accessor) {
132 addActionListener(new BrowseFolderActionListener<>(title, description, this, project, fileChooserDescriptor, accessor));
136 * @deprecated use {@link #addBrowseFolderListener(String, String, Project, FileChooserDescriptor, TextComponentAccessor)} instead
138 public void addBrowseFolderListener(@Nullable @Nls(capitalization = Nls.Capitalization.Title) String title,
139 @Nullable @Nls(capitalization = Nls.Capitalization.Sentence) String description,
140 @Nullable Project project,
141 FileChooserDescriptor fileChooserDescriptor,
142 TextComponentAccessor<Comp> accessor, boolean autoRemoveOnHide) {
143 addBrowseFolderListener(title, description, project, fileChooserDescriptor, accessor);
147 * @deprecated use {@link #addActionListener(ActionListener)} instead
149 @SuppressWarnings("UnusedParameters")
150 public void addBrowseFolderListener(@Nullable Project project, final BrowseFolderActionListener<Comp> actionListener) {
151 addActionListener(actionListener);
155 * @deprecated use {@link #addActionListener(ActionListener)} instead
157 @SuppressWarnings("UnusedParameters")
158 public void addBrowseFolderListener(@Nullable Project project, final BrowseFolderActionListener<Comp> actionListener, boolean autoRemoveOnHide) {
159 addActionListener(actionListener);
163 public void dispose() {
164 ActionListener[] listeners = myBrowseButton.getActionListeners();
165 for (ActionListener listener : listeners) {
166 myBrowseButton.removeActionListener(listener);
170 public FixedSizeButton getButton() {
171 return myBrowseButton;
175 * Do not use this class directly it is public just to hack other implementation of controls similar to TextFieldWithBrowseButton.
177 public static final class MyDoClickAction extends DumbAwareAction {
178 private final FixedSizeButton myBrowseButton;
179 public MyDoClickAction(FixedSizeButton browseButton) {
180 myBrowseButton = browseButton;
184 public void update(AnActionEvent e) {
185 e.getPresentation().setEnabled(myBrowseButton.isVisible() && myBrowseButton.isEnabled());
189 public void actionPerformed(AnActionEvent e){
190 myBrowseButton.doClick();
193 public void registerShortcut(JComponent textField) {
194 ShortcutSet shiftEnter = new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK));
195 registerCustomShortcutSet(shiftEnter, textField);
196 myBrowseButton.setToolTipText(KeymapUtil.getShortcutsText(shiftEnter.getShortcuts()));
199 public static void addTo(FixedSizeButton browseButton, JComponent aComponent) {
200 new MyDoClickAction(browseButton).registerShortcut(aComponent);
204 public static class BrowseFolderActionListener<T extends JComponent> implements ActionListener {
205 private final String myTitle;
206 private final String myDescription;
207 protected ComponentWithBrowseButton<T> myTextComponent;
208 private final TextComponentAccessor<T> myAccessor;
209 private Project myProject;
210 protected final FileChooserDescriptor myFileChooserDescriptor;
212 public BrowseFolderActionListener(@Nullable @Nls(capitalization = Nls.Capitalization.Title) String title,
213 @Nullable @Nls(capitalization = Nls.Capitalization.Sentence) String description,
214 ComponentWithBrowseButton<T> textField,
215 @Nullable Project project,
216 FileChooserDescriptor fileChooserDescriptor,
217 TextComponentAccessor<T> accessor) {
218 if (fileChooserDescriptor != null && fileChooserDescriptor.isChooseMultiple()) {
219 LOG.error("multiple selection not supported");
220 fileChooserDescriptor = new FileChooserDescriptor(fileChooserDescriptor) {
222 public boolean isChooseMultiple() {
229 myDescription = description;
230 myTextComponent = textField;
232 myFileChooserDescriptor = fileChooserDescriptor;
233 myAccessor = accessor;
237 protected Project getProject() {
241 protected void setProject(@Nullable Project project) {
246 public void actionPerformed(ActionEvent e) {
247 FileChooserDescriptor fileChooserDescriptor = myFileChooserDescriptor;
248 if (myTitle != null || myDescription != null) {
249 fileChooserDescriptor = (FileChooserDescriptor)myFileChooserDescriptor.clone();
250 if (myTitle != null) {
251 fileChooserDescriptor.setTitle(myTitle);
253 if (myDescription != null) {
254 fileChooserDescriptor.setDescription(myDescription);
258 FileChooser.chooseFile(fileChooserDescriptor, getProject(), myTextComponent, getInitialFile(), this::onFileChosen);
262 protected VirtualFile getInitialFile() {
263 String directoryName = getComponentText();
264 if (StringUtil.isEmptyOrSpaces(directoryName)) {
268 directoryName = FileUtil.toSystemIndependentName(directoryName);
269 VirtualFile path = LocalFileSystem.getInstance().findFileByPath(expandPath(directoryName));
270 while (path == null && directoryName.length() > 0) {
271 int pos = directoryName.lastIndexOf('/');
273 directoryName = directoryName.substring(0, pos);
274 path = LocalFileSystem.getInstance().findFileByPath(directoryName);
280 protected String expandPath(@NotNull String path) {
284 protected String getComponentText() {
285 return myAccessor.getText(myTextComponent.getChildComponent()).trim();
289 protected String chosenFileToResultingText(@NotNull VirtualFile chosenFile) {
290 return chosenFile.getPresentableUrl();
293 protected void onFileChosen(@NotNull VirtualFile chosenFile) {
294 myAccessor.setText(myTextComponent.getChildComponent(), chosenFileToResultingText(chosenFile));
299 public final void requestFocus() {
300 myComponent.requestFocus();
303 @SuppressWarnings("deprecation")
305 public final void setNextFocusableComponent(Component aComponent) {
306 super.setNextFocusableComponent(aComponent);
307 myComponent.setNextFocusableComponent(aComponent);
310 private KeyEvent myCurrentEvent = null;
313 protected final boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
314 if (condition == WHEN_FOCUSED && myCurrentEvent != e) {
317 myComponent.dispatchEvent(e);
320 myCurrentEvent = null;
323 if (e.isConsumed()) return true;
324 return super.processKeyBinding(ks, e, condition, pressed);