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
.ide
.util
.newProjectWizard
;
18 import com
.intellij
.CommonBundle
;
19 import com
.intellij
.ide
.IdeBundle
;
20 import com
.intellij
.ide
.util
.BrowseFilesListener
;
21 import com
.intellij
.ide
.util
.ElementsChooser
;
22 import com
.intellij
.ide
.util
.JavaUtil
;
23 import com
.intellij
.ide
.util
.projectWizard
.AbstractStepWithProgress
;
24 import com
.intellij
.ide
.util
.projectWizard
.SourcePathsBuilder
;
25 import com
.intellij
.openapi
.application
.ApplicationManager
;
26 import com
.intellij
.openapi
.application
.ApplicationNamesInfo
;
27 import com
.intellij
.openapi
.diagnostic
.Logger
;
28 import com
.intellij
.openapi
.fileChooser
.FileChooserDescriptor
;
29 import com
.intellij
.openapi
.ui
.Messages
;
30 import com
.intellij
.openapi
.ui
.MultiLineLabelUI
;
31 import com
.intellij
.openapi
.util
.Computable
;
32 import com
.intellij
.openapi
.util
.Pair
;
33 import com
.intellij
.openapi
.util
.io
.FileUtil
;
34 import com
.intellij
.openapi
.vfs
.LocalFileSystem
;
35 import com
.intellij
.openapi
.vfs
.VfsUtil
;
36 import com
.intellij
.openapi
.vfs
.VirtualFile
;
37 import com
.intellij
.ui
.DocumentAdapter
;
38 import com
.intellij
.ui
.FieldPanel
;
39 import org
.jetbrains
.annotations
.NonNls
;
40 import org
.jetbrains
.annotations
.Nullable
;
41 import org
.jetbrains
.annotations
.NotNull
;
44 import javax
.swing
.event
.DocumentEvent
;
46 import java
.awt
.event
.ActionEvent
;
47 import java
.awt
.event
.ActionListener
;
48 import java
.awt
.event
.ItemEvent
;
49 import java
.awt
.event
.ItemListener
;
51 import java
.io
.IOException
;
52 import java
.util
.ArrayList
;
53 import java
.util
.Collections
;
54 import java
.util
.List
;
57 * @author Eugene Zhuravlev
60 public class SourcePathsStep
extends AbstractStepWithProgress
<List
<Pair
<String
, String
>>> {
61 private static final Logger LOG
= Logger
.getInstance("#com.intellij.ide.util.newProjectWizard.SourcePathsStep");
63 private String myCurrentMode
;
64 @NonNls private static final String CREATE_SOURCE_PANEL
= "create_source";
65 @NonNls private static final String CHOOSE_SOURCE_PANEL
= "choose_source";
67 private static final List
<Pair
<String
,String
>> EMPTY_STRING_STRING_ARRAY
= Collections
.emptyList();
68 private final SourcePathsBuilder myBuilder
;
69 private final Icon myIcon
;
70 private final String myHelpId
;
71 private ElementsChooser
<Pair
<String
,String
>> mySourcePathsChooser
;
72 private String myCurrentContentEntryPath
= null;
73 private JRadioButton myRbCreateSource
;
74 private JRadioButton myRbNoSource
;
75 private JTextField myTfSourceDirectoryName
;
76 private JTextField myTfFullPath
;
77 private JPanel myResultPanel
;
79 public SourcePathsStep(SourcePathsBuilder builder
, Icon icon
, @NonNls String helpId
) {
80 super(IdeBundle
.message("prompt.stop.searching.for.sources", ApplicationNamesInfo
.getInstance().getProductName()));
86 protected JComponent
createResultsPanel() {
87 myResultPanel
= new JPanel(new CardLayout());
88 myResultPanel
.add(createComponentForEmptyRootCase(), CREATE_SOURCE_PANEL
);
89 myResultPanel
.add(createComponentForChooseSources(), CHOOSE_SOURCE_PANEL
);
93 private JComponent
createComponentForEmptyRootCase() {
94 final JPanel panel
= new JPanel(new GridBagLayout());
95 final String text
= IdeBundle
.message("prompt.please.specify.java.sources.directory");
97 final JLabel label
= new JLabel(text
);
98 label
.setUI(new MultiLineLabelUI());
99 panel
.add(label
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.NONE
, new Insets(8, 10, 0, 10), 0, 0));
101 myRbCreateSource
= new JRadioButton(IdeBundle
.message("radio.create.source.directory"), true);
102 panel
.add(myRbCreateSource
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.NONE
, new Insets(8, 10, 0, 10), 0, 0));
104 myTfSourceDirectoryName
= new JTextField(suggestSourceDirectoryName());
105 final JLabel srcPathLabel
= new JLabel(IdeBundle
.message("prompt.enter.relative.path.to.module.content.root", File
.separator
));
106 panel
.add(srcPathLabel
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.HORIZONTAL
, new Insets(8, 30, 0, 0), 0, 0));
107 final FileChooserDescriptor chooserDescriptor
= new FileChooserDescriptor(false, true, false, false, false, false);
108 chooserDescriptor
.setIsTreeRootVisible(true);
109 final FieldPanel fieldPanel
= createFieldPanel(myTfSourceDirectoryName
, null, new BrowsePathListener(myTfSourceDirectoryName
, chooserDescriptor
));
110 panel
.add(fieldPanel
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.HORIZONTAL
, new Insets(8, 30, 0, 10), 0, 0));
112 myRbNoSource
= new JRadioButton(IdeBundle
.message("radio.do.not.create.source.directory"), true);
113 panel
.add(myRbNoSource
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 1.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.NONE
, new Insets(8, 10, 0, 10), 0, 0));
115 final JLabel fullPathLabel
= new JLabel(IdeBundle
.message("label.source.directory"));
116 panel
.add(fullPathLabel
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.SOUTHWEST
, GridBagConstraints
.NONE
, new Insets(8, 10, 0, 10), 0, 0));
118 myTfFullPath
= new JTextField();
119 myTfFullPath
.setEditable(false);
120 final Insets borderInsets
= myTfFullPath
.getBorder().getBorderInsets(myTfFullPath
);
121 myTfFullPath
.setBorder(BorderFactory
.createEmptyBorder(borderInsets
.top
, borderInsets
.left
, borderInsets
.bottom
, borderInsets
.right
));
122 panel
.add(myTfFullPath
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.SOUTHWEST
, GridBagConstraints
.HORIZONTAL
, new Insets(8, 10, 8, 10), 0, 0));
124 ButtonGroup group
= new ButtonGroup();
125 group
.add(myRbCreateSource
);
126 group
.add(myRbNoSource
);
127 myTfSourceDirectoryName
.getDocument().addDocumentListener(new DocumentAdapter() {
128 public void textChanged(DocumentEvent event
) {
129 updateFullPathField();
133 myRbCreateSource
.addItemListener(new ItemListener() {
134 public void itemStateChanged(ItemEvent e
) {
135 final boolean enabled
= e
.getStateChange() == ItemEvent
.SELECTED
;
136 srcPathLabel
.setEnabled(enabled
);
137 fieldPanel
.setEnabled(enabled
);
138 fullPathLabel
.setVisible(enabled
);
139 myTfFullPath
.setVisible(enabled
);
141 myTfSourceDirectoryName
.requestFocus();
148 @NonNls protected String
suggestSourceDirectoryName() {
152 private void updateFullPathField() {
153 final String sourceDirectoryPath
= getSourceDirectoryPath();
154 if (sourceDirectoryPath
!= null) {
155 myTfFullPath
.setText(sourceDirectoryPath
.replace('/', File
.separatorChar
));
158 myTfFullPath
.setText("");
162 private JComponent
createComponentForChooseSources() {
163 final JPanel panel
= new JPanel(new GridBagLayout());
164 mySourcePathsChooser
= new ElementsChooser
<Pair
<String
, String
>>(true) {
165 public String
getItemText(@NotNull Pair
<String
, String
> pair
) {
166 if ("".equals(pair
.second
)) return pair
.first
;
167 return pair
.first
+ " (" + pair
.second
+ ")";
170 final String text
= IdeBundle
.message("label.java.source.files.have.been.found");
171 final JLabel label
= new JLabel(text
);
172 label
.setUI(new MultiLineLabelUI());
173 panel
.add(label
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 2, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.HORIZONTAL
, new Insets(8, 10, 0, 10), 0, 0));
174 panel
.add(mySourcePathsChooser
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 2, 1, 1.0, 1.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.BOTH
, new Insets(8, 10, 8, 10), 0, 0));
176 final JButton markAllButton
= new JButton(IdeBundle
.message("button.mark.all"));
177 panel
.add(markAllButton
, new GridBagConstraints(0, GridBagConstraints
.RELATIVE
, 1, 1, 0.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.NONE
, new Insets(0, 10, 8, 2), 0, 0));
179 final JButton unmarkAllButton
= new JButton(IdeBundle
.message("button.unmark.all"));
180 panel
.add(unmarkAllButton
, new GridBagConstraints(1, GridBagConstraints
.RELATIVE
, 1, 1, 1.0, 0.0, GridBagConstraints
.NORTHWEST
, GridBagConstraints
.NONE
, new Insets(0, 0, 8, 10), 0, 0));
182 markAllButton
.addActionListener(new ActionListener() {
183 public void actionPerformed(ActionEvent e
) {
184 mySourcePathsChooser
.setAllElementsMarked(true);
187 unmarkAllButton
.addActionListener(new ActionListener() {
188 public void actionPerformed(ActionEvent e
) {
189 mySourcePathsChooser
.setAllElementsMarked(false);
196 public JComponent
getPreferredFocusedComponent() {
197 return myRbCreateSource
.isSelected()? myTfSourceDirectoryName
: mySourcePathsChooser
.getComponent();
200 public void updateDataModel() {
201 List
<Pair
<String
,String
>> paths
= null;
202 if (CHOOSE_SOURCE_PANEL
.equals(myCurrentMode
)) {
203 final List
<Pair
<String
, String
>> selectedElements
= mySourcePathsChooser
.getMarkedElements();
204 if (selectedElements
.size() > 0) {
205 paths
= new ArrayList
<Pair
<String
, String
>>(selectedElements
.size());
207 for (final Pair
<String
, String
> path
: selectedElements
) {
208 paths
.add(Pair
.create(path
.first
.replace(File
.separatorChar
, '/'), path
.second
));
213 if (myRbCreateSource
.isSelected()) {
214 final String sourceDirectoryPath
= getSourceDirectoryPath();
215 if (sourceDirectoryPath
!= null) {
216 paths
= Collections
.singletonList(Pair
.create(sourceDirectoryPath
, ""));
220 myBuilder
.setContentEntryPath(getContentRootPath());
222 myBuilder
.setSourcePaths(paths
);
225 myBuilder
.setSourcePaths(new ArrayList
<Pair
<String
, String
>>());
229 public boolean validate() {
230 if (!super.validate()) {
234 if (CREATE_SOURCE_PANEL
.equals(myCurrentMode
) && myRbCreateSource
.isSelected()) {
235 final String sourceDirectoryPath
= getSourceDirectoryPath();
236 final String relativePath
= myTfSourceDirectoryName
.getText().trim();
237 if (relativePath
.length() == 0) {
238 String text
= IdeBundle
.message("prompt.relative.path.to.sources.empty", sourceDirectoryPath
);
239 final int answer
= Messages
.showDialog(myTfSourceDirectoryName
, text
, IdeBundle
.message("title.mark.source.directory"),
240 new String
[]{IdeBundle
.message("action.mark"), IdeBundle
.message("action.do.not.mark"),
241 CommonBundle
.getCancelButtonText()}, 0, Messages
.getQuestionIcon());
243 return false; // cancel
245 if (answer
== 1) { // don't mark
246 myRbNoSource
.doClick();
249 if (sourceDirectoryPath
!= null) {
250 final File rootDir
= new File(getContentRootPath());
251 final File srcDir
= new File(sourceDirectoryPath
);
253 if (!FileUtil
.isAncestor(rootDir
, srcDir
, false)) {
254 Messages
.showErrorDialog(myTfSourceDirectoryName
,
255 IdeBundle
.message("error.source.directory.should.be.under.module.content.root.directory"),
256 CommonBundle
.getErrorTitle());
260 catch (IOException e
) {
261 Messages
.showErrorDialog(myTfSourceDirectoryName
, e
.getMessage(), CommonBundle
.getErrorTitle());
271 private String
getSourceDirectoryPath() {
272 final String contentEntryPath
= getContentRootPath();
273 final String dirName
= myTfSourceDirectoryName
.getText().trim().replace(File
.separatorChar
, '/');
274 if (contentEntryPath
!= null) {
275 return dirName
.length() > 0? contentEntryPath
+ "/" + dirName
: contentEntryPath
;
280 protected boolean shouldRunProgress() {
281 return isContentEntryChanged();
284 protected void onFinished(final List
<Pair
<String
, String
>> foundPaths
, final boolean canceled
) {
285 if (foundPaths
.size() > 0) {
286 myCurrentMode
= CHOOSE_SOURCE_PANEL
;
287 mySourcePathsChooser
.setElements(foundPaths
, true);
290 myCurrentMode
= CREATE_SOURCE_PANEL
;
291 updateFullPathField();
293 updateStepUI(canceled ?
null : getContentRootPath());
294 if (CHOOSE_SOURCE_PANEL
.equals(myCurrentMode
)) {
295 mySourcePathsChooser
.selectElements(foundPaths
.subList(0, 1));
297 else if (CREATE_SOURCE_PANEL
.equals(myCurrentMode
)) {
298 myTfSourceDirectoryName
.selectAll();
302 private void updateStepUI(final String contentEntryPath
) {
303 myCurrentContentEntryPath
= contentEntryPath
;
304 ((CardLayout
)myResultPanel
.getLayout()).show(myResultPanel
, myCurrentMode
);
305 myResultPanel
.revalidate();
308 protected boolean isContentEntryChanged() {
309 final String contentEntryPath
= getContentRootPath();
310 return myCurrentContentEntryPath
== null? contentEntryPath
!= null : !myCurrentContentEntryPath
.equals(contentEntryPath
);
313 protected List
<Pair
<String
,String
>> calculate() {
314 String contentEntryPath
= getContentRootPath();
315 if (contentEntryPath
== null) {
316 return EMPTY_STRING_STRING_ARRAY
;
318 final File entryFile
= new File(contentEntryPath
);
319 if (!entryFile
.exists()) {
320 return EMPTY_STRING_STRING_ARRAY
;
322 final File
[] children
= entryFile
.listFiles();
323 if (children
== null || children
.length
== 0) {
324 return EMPTY_STRING_STRING_ARRAY
;
326 final List
<Pair
<File
,String
>> suggestedRoots
= JavaUtil
.suggestRoots(entryFile
);
327 final List
<Pair
<String
,String
>> paths
= new ArrayList
<Pair
<String
, String
>>();
328 for (final Pair
<File
, String
> suggestedRoot
: suggestedRoots
) {
330 if (FileUtil
.isAncestor(entryFile
, suggestedRoot
.first
, false)) {
331 final String path
= FileUtil
.resolveShortWindowsName(suggestedRoot
.first
.getPath());
332 paths
.add(Pair
.create(path
, suggestedRoot
.second
));
335 catch (IOException e
) {
343 private String
getContentRootPath() {
344 return myBuilder
.getContentEntryPath();
347 protected void setSourceDirectoryName(String name
) {
348 name
= name
== null?
"" : name
.trim();
349 myTfSourceDirectoryName
.setText(name
);
352 protected String
getProgressText() {
353 final String root
= getContentRootPath();
354 return IdeBundle
.message("progress.searching.for.sources", root
!= null? root
.replace('/', File
.separatorChar
) : "") ;
357 private class BrowsePathListener
extends BrowseFilesListener
{
358 private final FileChooserDescriptor myChooserDescriptor
;
359 private final JTextField myField
;
361 public BrowsePathListener(JTextField textField
, final FileChooserDescriptor chooserDescriptor
) {
362 super(textField
, IdeBundle
.message("prompt.select.source.directory"), "", chooserDescriptor
);
363 myChooserDescriptor
= chooserDescriptor
;
368 private VirtualFile
getContentEntryDir() {
369 final String contentEntryPath
= getContentRootPath();
370 if (contentEntryPath
!= null) {
371 return ApplicationManager
.getApplication().runWriteAction(new Computable
<VirtualFile
>() {
372 public VirtualFile
compute() {
373 return LocalFileSystem
.getInstance().refreshAndFindFileByPath(contentEntryPath
);
380 public void actionPerformed(ActionEvent e
) {
381 final VirtualFile contentEntryDir
= getContentEntryDir();
382 if (contentEntryDir
!= null) {
383 myChooserDescriptor
.setRoot(contentEntryDir
);
384 final String textBefore
= myField
.getText().trim();
385 super.actionPerformed(e
);
386 if (!textBefore
.equals(myField
.getText().trim())) {
387 final String fullPath
= myField
.getText().trim().replace(File
.separatorChar
, '/');
388 final VirtualFile fileByPath
= LocalFileSystem
.getInstance().findFileByPath(fullPath
);
389 LOG
.assertTrue(fileByPath
!= null);
390 myField
.setText(VfsUtil
.getRelativePath(fileByPath
, contentEntryDir
, File
.separatorChar
));
396 public Icon
getIcon() {
400 public String
getHelpId() {