runnerw works on windows
[idea/community.git] / platform / lang-impl / src / com / intellij / openapi / projectRoots / ui / PathEditor.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.projectRoots.ui;
17
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.Nullable;
42
43 import javax.swing.*;
44 import javax.swing.event.ListSelectionEvent;
45 import javax.swing.event.ListSelectionListener;
46 import java.awt.*;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.List;
52 import java.util.Set;
53
54 /**
55  * @author MYakovlev
56  */
57 public class PathEditor {
58   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.projectRoots.ui.PathEditor");
59   public static final Color INVALID_COLOR = new Color(210, 0, 0);
60
61   protected JPanel myPanel;
62   private JButton myRemoveButton;
63   private JButton myAddButton;
64   private JButton mySpecifyUrlButton;
65   private JList myList;
66   private DefaultListModel myModel;
67   private final Set<VirtualFile> myAllFiles = new HashSet<VirtualFile>();
68   private boolean myModified = false;
69   private boolean myEnabled = false;
70   private static final Icon ICON_INVALID = IconLoader.getIcon("/nodes/ppInvalid.png");
71   private static final Icon ICON_EMPTY = IconLoader.getIcon("/nodes/emptyNode.png");
72   private final String myDisplayName;
73   private final OrderRootType myOrderRootType;
74   private final FileChooserDescriptor myDescriptor;
75
76   public PathEditor(final String displayName,
77                     final OrderRootType orderRootType,
78                     final FileChooserDescriptor descriptor) {
79     myDisplayName = displayName;
80     myOrderRootType = orderRootType;
81     myDescriptor = descriptor;
82   }
83
84   protected boolean isShowUrlButton() {
85     return false;
86   }
87
88   protected void onSpecifyUrlButtonClicked() {
89   }
90
91   public String getDisplayName() {
92     return myDisplayName;
93   }
94
95   protected void setModified(boolean modified){
96     myModified = modified;
97   }
98
99   public boolean isModified(){
100     return myModified;
101   }
102
103   public void apply(SdkModificator sdkModificator) {
104     sdkModificator.removeRoots(myOrderRootType);
105     // add all items
106     for (int i = 0; i < getRowCount(); i++){
107       sdkModificator.addRoot(getValueAt(i), myOrderRootType);
108     }
109     setModified(false);
110     updateButtons();
111   }
112
113   public VirtualFile[] getRoots() {
114     final int count = getRowCount();
115     if (count == 0) {
116       return VirtualFile.EMPTY_ARRAY;
117     }
118     final VirtualFile[] roots = new VirtualFile[count];
119     for (int i = 0; i < count; i++){
120       roots[i] = getValueAt(i);
121     }
122     return roots;
123   }
124
125   public void reset(@Nullable SdkModificator modificator) {
126     keepSelectionState();
127     clearList();
128     myEnabled = modificator != null;
129     if(myEnabled){
130       VirtualFile[] files = modificator.getRoots(myOrderRootType);
131       for (VirtualFile file : files) {
132         addElement(file);
133       }
134     }
135     setModified(false);
136     updateButtons();
137   }
138
139   public JComponent createComponent(){
140     myPanel = new JPanel(new GridBagLayout());
141     Insets anInsets = new Insets(2, 2, 2, 2);
142
143     myModel = new DefaultListModel();
144     myList = new JBList(myModel);
145     myList.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
146       public void valueChanged(ListSelectionEvent e){
147         updateButtons();
148       }
149     });
150     myList.setCellRenderer(new MyCellRenderer());
151
152     myRemoveButton = new JButton(ProjectBundle.message("button.remove"));
153     myAddButton = new JButton(ProjectBundle.message("button.add"));
154     mySpecifyUrlButton = new JButton(ProjectBundle.message("sdk.paths.specify.url.button"));
155
156     mySpecifyUrlButton.setVisible(isShowUrlButton());
157
158     myAddButton.addActionListener(new ActionListener(){
159       public void actionPerformed(ActionEvent e){
160         final VirtualFile[] added = doAdd();
161         if (added.length > 0){
162           setModified(true);
163         }
164         updateButtons();
165         requestDefaultFocus();
166         setSelectedRoots(added);
167       }
168     });
169     myRemoveButton.addActionListener(new ActionListener(){
170       public void actionPerformed(ActionEvent e){
171         List removedItems = ListUtil.removeSelectedItems(myList);
172         itemsRemoved(removedItems);
173       }
174     });
175     mySpecifyUrlButton.addActionListener(new ActionListener(){
176       public void actionPerformed(ActionEvent e){
177         onSpecifyUrlButtonClicked();
178       }
179     });
180
181     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myList);
182     scrollPane.setPreferredSize(new Dimension(500, 500));
183     myPanel.add(scrollPane, new GridBagConstraints(0, 0, 1, 8, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, anInsets, 0, 0));
184     myPanel.add(myAddButton, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
185     myPanel.add(myRemoveButton, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
186     myPanel.add(mySpecifyUrlButton, new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, anInsets, 0, 0));
187     myPanel.add(Box.createRigidArea(new Dimension(mySpecifyUrlButton.getPreferredSize().width, 4)), new GridBagConstraints(1, 5, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, anInsets, 0, 0));
188     return myPanel;
189   }
190
191   private void itemsRemoved(List removedItems) {
192     myAllFiles.removeAll(removedItems);
193     if (removedItems.size() > 0){
194       setModified(true);
195     }
196     updateButtons();
197     requestDefaultFocus();
198   }
199
200   private VirtualFile[] doAdd(){
201     VirtualFile[] files = FileChooser.chooseFiles(myPanel, myDescriptor);
202     files = adjustAddedFileSet(myPanel, files);
203     List<VirtualFile> added = new ArrayList<VirtualFile>(files.length);
204     for (VirtualFile vFile : files) {
205       if (addElement(vFile)) {
206         added.add(vFile);
207       }
208     }
209     return VfsUtil.toVirtualFileArray(added);
210   }
211
212   /**
213    * Implement this method to adjust adding behavior, this method is called right after the files
214    * or directories are selected for added. This method allows adding UI that modify file set.
215    *
216    * The default implementation returns a value passed the parameter files and does nothing.
217    *
218    * @param component a component that could be used as a parent.
219    * @param files a selected file set
220    * @return adjusted file set
221    */
222   protected VirtualFile[] adjustAddedFileSet(final Component component, final VirtualFile[] files) {
223     return files;
224   }
225
226   protected void updateButtons(){
227     Object[] values = getSelectedRoots();
228     myRemoveButton.setEnabled((values.length > 0) && myEnabled);
229     myAddButton.setEnabled(myEnabled);
230     mySpecifyUrlButton.setEnabled(myEnabled && !isUrlInserted());
231     mySpecifyUrlButton.setVisible(isShowUrlButton());
232   }
233
234   private boolean isUrlInserted(){
235     if(getRowCount() > 0){
236       return ((VirtualFile)myModel.lastElement()).getFileSystem() instanceof HttpFileSystem;
237     }
238     return false;
239   }
240
241   protected void requestDefaultFocus(){
242     if (myList != null){
243       myList.requestFocus();
244     }
245   }
246
247   public void addPaths(VirtualFile... paths){
248     boolean added = false;
249     keepSelectionState();
250     for (final VirtualFile path : paths) {
251       if (addElement(path)) {
252         added = true;
253       }
254     }
255     if (added){
256       setModified(true);
257       updateButtons();
258     }
259   }
260
261   public void removePaths(VirtualFile... paths) {
262     final Set<VirtualFile> pathsSet = new java.util.HashSet<VirtualFile>(Arrays.asList(paths));
263     int size = getRowCount();
264     final TIntArrayList indicesToRemove = new TIntArrayList(paths.length);
265     for (int idx = 0; idx < size; idx++) {
266       VirtualFile path = getValueAt(idx);
267       if (pathsSet.contains(path)) {
268         indicesToRemove.add(idx);
269       }
270     }
271     final List list = ListUtil.removeIndices(myList, indicesToRemove.toNativeArray());
272     itemsRemoved(list);
273   }
274
275   /** Method adds element only if it is not added yet. */
276   protected boolean addElement(VirtualFile item){
277     if(item == null){
278       return false;
279     }
280     if (myAllFiles.contains(item)){
281       return false;
282     }
283     if(isUrlInserted()){
284       myModel.insertElementAt(item, myModel.size() - 1);
285     }
286     else{
287       myModel.addElement(item);
288     }
289     myAllFiles.add(item);
290     return true;
291   }
292
293   protected void setSelectedRoots(Object[] roots){
294     ArrayList<Object> rootsList = new ArrayList<Object>(roots.length);
295     for (Object root : roots) {
296       if (root != null) {
297         rootsList.add(root);
298       }
299     }
300     myList.getSelectionModel().clearSelection();
301     int rowCount = myModel.getSize();
302     for (int i = 0; i < rowCount; i++){
303       Object currObject = myModel.get(i);
304       LOG.assertTrue(currObject != null);
305       if (rootsList.contains(currObject)){
306         myList.getSelectionModel().addSelectionInterval(i, i);
307       }
308     }
309   }
310
311   private void keepSelectionState(){
312     final Object[] selectedItems = getSelectedRoots();
313
314     SwingUtilities.invokeLater(new Runnable(){
315       public void run(){
316         if (selectedItems != null){
317           setSelectedRoots(selectedItems);
318         }
319       }
320     });
321   }
322
323   protected Object[] getSelectedRoots(){
324     return myList.getSelectedValues();
325   }
326
327   private int getRowCount(){
328     return myModel.getSize();
329   }
330
331   private VirtualFile getValueAt(int row){
332     return (VirtualFile)myModel.get(row);
333   }
334
335   public void clearList(){
336     myModel.clear();
337     myAllFiles.clear();
338     setModified(true);
339   }
340
341   private static boolean isJarFile(final VirtualFile file){
342     return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
343       public Boolean compute() {
344         VirtualFile tempFile = file;
345         if ((file.getFileSystem() instanceof JarFileSystem) && file.getParent() == null){
346           //[myakovlev] It was bug - directories with *.jar extensions was saved as files of JarFileSystem.
347           //    so we can not just return true, we should filter such directories.
348           String path = file.getPath().substring(0, file.getPath().length() - JarFileSystem.JAR_SEPARATOR.length());
349           tempFile = LocalFileSystem.getInstance().findFileByPath(path);
350         }
351         if (tempFile != null && !tempFile.isDirectory()){
352           return Boolean.valueOf(FileTypeManager.getInstance().getFileTypeByFile(tempFile).equals(FileTypes.ARCHIVE));
353         }
354         return Boolean.FALSE;
355
356       }
357     } ).booleanValue();
358   }
359
360   /**
361    * @return icon for displaying parameter (ProjectRoot or VirtualFile)
362    * If parameter is not ProjectRoot or VirtualFile, returns empty icon "/nodes/emptyNode.png"
363    */
364   private static Icon getIconForRoot(Object projectRoot){
365     if (projectRoot instanceof VirtualFile){
366       final VirtualFile file = (VirtualFile)projectRoot;
367       if (!file.isValid()){
368         return ICON_INVALID;
369       }
370       else if (isHttpRoot(file)){
371         return Icons.WEB_ICON;
372       }
373       else{
374         return isJarFile(file) ? Icons.JAR_ICON : Icons.FILE_ICON;
375       }
376     }
377     return ICON_EMPTY;
378   }
379
380   private static boolean isHttpRoot(VirtualFile virtualFileOrProjectRoot){
381     if(virtualFileOrProjectRoot != null){
382       return (virtualFileOrProjectRoot.getFileSystem() instanceof HttpFileSystem);
383     }
384     return false;
385   }
386
387   private final class MyCellRenderer extends DefaultListCellRenderer{
388     private String getPresentableString(final Object value){
389       return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
390         public String compute() {
391           //noinspection HardCodedStringLiteral
392           return (value instanceof VirtualFile)? ((VirtualFile)value).getPresentableUrl() : "UNKNOWN OBJECT";
393         }
394       });
395     }
396
397     public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){
398       super.getListCellRendererComponent(list, getPresentableString(value), index, isSelected, cellHasFocus);
399       if (isSelected){
400         setForeground(UIUtil.getListSelectionForeground());
401       }
402       else{
403         if (value instanceof VirtualFile){
404           VirtualFile file = (VirtualFile)value;
405           if (!file.isValid()){
406             setForeground(INVALID_COLOR);
407           }
408         }
409       }
410       setIcon(getIconForRoot(value));
411       return this;
412     }
413   }
414 }