2 * Copyright 2000-2009 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.projectRoots.ui;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.fileChooser.FileChooser;
21 import com.intellij.openapi.fileChooser.FileChooserDescriptor;
22 import com.intellij.openapi.fileTypes.FileTypeManager;
23 import com.intellij.openapi.fileTypes.FileTypes;
24 import com.intellij.openapi.project.ProjectBundle;
25 import com.intellij.openapi.projectRoots.SdkModificator;
26 import com.intellij.openapi.roots.OrderRootType;
27 import com.intellij.openapi.util.Computable;
28 import com.intellij.openapi.util.IconLoader;
29 import com.intellij.openapi.vfs.JarFileSystem;
30 import com.intellij.openapi.vfs.LocalFileSystem;
31 import com.intellij.openapi.vfs.VfsUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.vfs.ex.http.HttpFileSystem;
34 import com.intellij.ui.ListUtil;
35 import com.intellij.ui.ScrollPaneFactory;
36 import com.intellij.ui.components.JBList;
37 import com.intellij.util.Icons;
38 import com.intellij.util.containers.HashSet;
39 import com.intellij.util.ui.UIUtil;
40 import gnu.trove.TIntArrayList;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
45 import javax.swing.event.ListSelectionEvent;
46 import javax.swing.event.ListSelectionListener;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.ActionListener;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.List;
58 public class PathEditor {
59 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.ui.PathEditor");
60 public static final Color INVALID_COLOR = new Color(210, 0, 0);
62 protected JPanel myPanel;
63 private JButton myRemoveButton;
64 private JButton myAddButton;
65 private JButton mySpecifyUrlButton;
67 private DefaultListModel myModel;
68 private final Set<VirtualFile> myAllFiles = new HashSet<VirtualFile>();
69 private boolean myModified = false;
70 private boolean myEnabled = false;
71 private static final Icon ICON_INVALID = IconLoader.getIcon("/nodes/ppInvalid.png");
72 private static final Icon ICON_EMPTY = IconLoader.getIcon("/nodes/emptyNode.png");
73 private final String myDisplayName;
74 private final OrderRootType myOrderRootType;
75 private final FileChooserDescriptor myDescriptor;
77 public PathEditor(final String displayName,
78 final OrderRootType orderRootType,
79 final FileChooserDescriptor descriptor) {
80 myDisplayName = displayName;
81 myOrderRootType = orderRootType;
82 myDescriptor = descriptor;
85 protected boolean isShowUrlButton() {
89 protected void onSpecifyUrlButtonClicked() {
92 public String getDisplayName() {
96 protected void setModified(boolean modified) {
97 myModified = modified;
100 public boolean isModified() {
104 public void apply(SdkModificator sdkModificator) {
105 sdkModificator.removeRoots(myOrderRootType);
107 for (int i = 0; i < getRowCount(); i++) {
108 sdkModificator.addRoot(getValueAt(i), myOrderRootType);
114 public VirtualFile[] getRoots() {
115 final int count = getRowCount();
117 return VirtualFile.EMPTY_ARRAY;
119 final VirtualFile[] roots = new VirtualFile[count];
120 for (int i = 0; i < count; i++) {
121 roots[i] = getValueAt(i);
126 public void setPaths(@NotNull List<VirtualFile> paths) {
127 keepSelectionState();
131 for (VirtualFile file : paths) {
139 public void reset(@Nullable SdkModificator modificator) {
140 keepSelectionState();
142 myEnabled = modificator != null;
144 VirtualFile[] files = modificator.getRoots(myOrderRootType);
145 for (VirtualFile file : files) {
153 public JComponent createComponent() {
154 myPanel = new JPanel(new GridBagLayout());
155 Insets anInsets = new Insets(2, 2, 2, 2);
157 myModel = new DefaultListModel();
158 myList = new JBList(myModel);
159 myList.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
160 public void valueChanged(ListSelectionEvent e) {
164 myList.setCellRenderer(new MyCellRenderer());
166 myRemoveButton = new JButton(ProjectBundle.message("button.remove"));
167 myAddButton = new JButton(ProjectBundle.message("button.add"));
168 mySpecifyUrlButton = new JButton(ProjectBundle.message("sdk.paths.specify.url.button"));
170 mySpecifyUrlButton.setVisible(isShowUrlButton());
172 myAddButton.addActionListener(new ActionListener() {
173 public void actionPerformed(ActionEvent e) {
174 final VirtualFile[] added = doAdd();
175 if (added.length > 0) {
179 requestDefaultFocus();
180 setSelectedRoots(added);
183 myRemoveButton.addActionListener(new ActionListener() {
184 public void actionPerformed(ActionEvent e) {
185 List removedItems = ListUtil.removeSelectedItems(myList);
186 itemsRemoved(removedItems);
189 mySpecifyUrlButton.addActionListener(new ActionListener() {
190 public void actionPerformed(ActionEvent e) {
191 onSpecifyUrlButtonClicked();
195 JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList);
196 scrollPane.setPreferredSize(new Dimension(500, 500));
198 .add(scrollPane, new GridBagConstraints(0, 0, 1, 8, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, anInsets, 0, 0));
199 myPanel.add(myAddButton,
200 new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
201 myPanel.add(myRemoveButton,
202 new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
203 myPanel.add(mySpecifyUrlButton,
204 new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
205 myPanel.add(Box.createRigidArea(new Dimension(mySpecifyUrlButton.getPreferredSize().width, 4)),
206 new GridBagConstraints(1, 5, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, anInsets, 0, 0));
210 private void itemsRemoved(List removedItems) {
211 myAllFiles.removeAll(removedItems);
212 if (removedItems.size() > 0) {
216 requestDefaultFocus();
219 private VirtualFile[] doAdd() {
220 VirtualFile[] files = FileChooser.chooseFiles(myPanel, myDescriptor);
221 files = adjustAddedFileSet(myPanel, files);
222 List<VirtualFile> added = new ArrayList<VirtualFile>(files.length);
223 for (VirtualFile vFile : files) {
224 if (addElement(vFile)) {
228 return VfsUtil.toVirtualFileArray(added);
232 * Implement this method to adjust adding behavior, this method is called right after the files
233 * or directories are selected for added. This method allows adding UI that modify file set.
235 * The default implementation returns a value passed the parameter files and does nothing.
237 * @param component a component that could be used as a parent.
238 * @param files a selected file set
239 * @return adjusted file set
241 protected VirtualFile[] adjustAddedFileSet(final Component component, final VirtualFile[] files) {
245 protected void updateButtons() {
246 Object[] values = getSelectedRoots();
247 myRemoveButton.setEnabled((values.length > 0) && myEnabled);
248 myAddButton.setEnabled(myEnabled);
249 mySpecifyUrlButton.setEnabled(myEnabled && !isUrlInserted());
250 mySpecifyUrlButton.setVisible(isShowUrlButton());
253 private boolean isUrlInserted() {
254 if (getRowCount() > 0) {
255 return ((VirtualFile)myModel.lastElement()).getFileSystem() instanceof HttpFileSystem;
260 protected void requestDefaultFocus() {
261 if (myList != null) {
262 myList.requestFocus();
266 public void addPaths(VirtualFile... paths) {
267 boolean added = false;
268 keepSelectionState();
269 for (final VirtualFile path : paths) {
270 if (addElement(path)) {
280 public void removePaths(VirtualFile... paths) {
281 final Set<VirtualFile> pathsSet = new java.util.HashSet<VirtualFile>(Arrays.asList(paths));
282 int size = getRowCount();
283 final TIntArrayList indicesToRemove = new TIntArrayList(paths.length);
284 for (int idx = 0; idx < size; idx++) {
285 VirtualFile path = getValueAt(idx);
286 if (pathsSet.contains(path)) {
287 indicesToRemove.add(idx);
290 final List list = ListUtil.removeIndices(myList, indicesToRemove.toNativeArray());
295 * Method adds element only if it is not added yet.
297 protected boolean addElement(VirtualFile item) {
301 if (myAllFiles.contains(item)) {
304 if (isUrlInserted()) {
305 myModel.insertElementAt(item, myModel.size() - 1);
308 myModel.addElement(item);
310 myAllFiles.add(item);
314 protected void setSelectedRoots(Object[] roots) {
315 ArrayList<Object> rootsList = new ArrayList<Object>(roots.length);
316 for (Object root : roots) {
321 myList.getSelectionModel().clearSelection();
322 int rowCount = myModel.getSize();
323 for (int i = 0; i < rowCount; i++) {
324 Object currObject = myModel.get(i);
325 LOG.assertTrue(currObject != null);
326 if (rootsList.contains(currObject)) {
327 myList.getSelectionModel().addSelectionInterval(i, i);
332 private void keepSelectionState() {
333 final Object[] selectedItems = getSelectedRoots();
335 SwingUtilities.invokeLater(new Runnable() {
337 if (selectedItems != null) {
338 setSelectedRoots(selectedItems);
344 protected Object[] getSelectedRoots() {
345 return myList.getSelectedValues();
348 private int getRowCount() {
349 return myModel.getSize();
352 private VirtualFile getValueAt(int row) {
353 return (VirtualFile)myModel.get(row);
356 public void clearList() {
362 private static boolean isJarFile(final VirtualFile file) {
363 return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
364 public Boolean compute() {
365 VirtualFile tempFile = file;
366 if ((file.getFileSystem() instanceof JarFileSystem) && file.getParent() == null) {
367 //[myakovlev] It was bug - directories with *.jar extensions was saved as files of JarFileSystem.
368 // so we can not just return true, we should filter such directories.
369 String path = file.getPath().substring(0, file.getPath().length() - JarFileSystem.JAR_SEPARATOR.length());
370 tempFile = LocalFileSystem.getInstance().findFileByPath(path);
372 if (tempFile != null && !tempFile.isDirectory()) {
373 return Boolean.valueOf(FileTypeManager.getInstance().getFileTypeByFile(tempFile).equals(FileTypes.ARCHIVE));
375 return Boolean.FALSE;
382 * @return icon for displaying parameter (ProjectRoot or VirtualFile)
383 * If parameter is not ProjectRoot or VirtualFile, returns empty icon "/nodes/emptyNode.png"
385 private static Icon getIconForRoot(Object projectRoot) {
386 if (projectRoot instanceof VirtualFile) {
387 final VirtualFile file = (VirtualFile)projectRoot;
388 if (!file.isValid()) {
391 else if (isHttpRoot(file)) {
392 return Icons.WEB_ICON;
395 return isJarFile(file) ? Icons.JAR_ICON : Icons.FILE_ICON;
401 private static boolean isHttpRoot(VirtualFile virtualFileOrProjectRoot) {
402 if (virtualFileOrProjectRoot != null) {
403 return (virtualFileOrProjectRoot.getFileSystem() instanceof HttpFileSystem);
408 private final class MyCellRenderer extends DefaultListCellRenderer {
409 private String getPresentableString(final Object value) {
410 return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
411 public String compute() {
412 //noinspection HardCodedStringLiteral
413 return (value instanceof VirtualFile) ? ((VirtualFile)value).getPresentableUrl() : "UNKNOWN OBJECT";
418 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
419 super.getListCellRendererComponent(list, getPresentableString(value), index, isSelected, cellHasFocus);
421 setForeground(UIUtil.getListSelectionForeground());
424 if (value instanceof VirtualFile) {
425 VirtualFile file = (VirtualFile)value;
426 if (!file.isValid()) {
427 setForeground(INVALID_COLOR);
431 setIcon(getIconForRoot(value));