1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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 */
16package com.android.ide.eclipse.adt.internal.wizards.templates;
17
18
19import com.android.ide.eclipse.adt.AdtPlugin;
20import com.android.ide.eclipse.adt.internal.wizards.newproject.WorkingSetGroup;
21import com.android.ide.eclipse.adt.internal.wizards.newproject.WorkingSetHelper;
22
23import org.eclipse.core.runtime.IPath;
24import org.eclipse.core.runtime.IStatus;
25import org.eclipse.core.runtime.Platform;
26import org.eclipse.core.runtime.Status;
27import org.eclipse.jface.dialogs.IMessageProvider;
28import org.eclipse.jface.viewers.IStructuredSelection;
29import org.eclipse.jface.wizard.IWizardPage;
30import org.eclipse.jface.wizard.WizardPage;
31import org.eclipse.swt.SWT;
32import org.eclipse.swt.events.FocusEvent;
33import org.eclipse.swt.events.FocusListener;
34import org.eclipse.swt.events.ModifyEvent;
35import org.eclipse.swt.events.ModifyListener;
36import org.eclipse.swt.events.SelectionEvent;
37import org.eclipse.swt.events.SelectionListener;
38import org.eclipse.swt.layout.GridData;
39import org.eclipse.swt.layout.GridLayout;
40import org.eclipse.swt.widgets.Button;
41import org.eclipse.swt.widgets.Composite;
42import org.eclipse.swt.widgets.DirectoryDialog;
43import org.eclipse.swt.widgets.Label;
44import org.eclipse.swt.widgets.Shell;
45import org.eclipse.swt.widgets.Text;
46import org.eclipse.ui.IWorkbenchPart;
47import org.eclipse.ui.IWorkingSet;
48
49import java.io.File;
50
51/**
52 * Second wizard page in the "New Project From Template" wizard
53 */
54public class ProjectContentsPage extends WizardPage
55        implements ModifyListener, SelectionListener, FocusListener {
56
57    private final NewProjectWizardState mValues;
58
59    private boolean mIgnore;
60    private Button mCustomIconToggle;
61    private Button mLibraryToggle;
62
63    private Button mUseDefaultLocationToggle;
64    private Label mLocationLabel;
65    private Text mLocationText;
66    private Button mChooseLocationButton;
67    private static String sLastProjectLocation = System.getProperty("user.home"); //$NON-NLS-1$
68    private Button mCreateActivityToggle;
69    private WorkingSetGroup mWorkingSetGroup;
70
71    ProjectContentsPage(NewProjectWizardState values) {
72        super("newAndroidApp"); //$NON-NLS-1$
73        mValues = values;
74        setTitle("New Android Application");
75        setDescription("Configure Project");
76
77        mWorkingSetGroup = new WorkingSetGroup();
78        setWorkingSets(new IWorkingSet[0]);
79    }
80
81    @Override
82    public void createControl(Composite parent) {
83        Composite container = new Composite(parent, SWT.NULL);
84        setControl(container);
85        GridLayout gl_container = new GridLayout(4, false);
86        gl_container.horizontalSpacing = 10;
87        container.setLayout(gl_container);
88
89        mCustomIconToggle = new Button(container, SWT.CHECK);
90        mCustomIconToggle.setSelection(true);
91        mCustomIconToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
92        mCustomIconToggle.setText("Create custom launcher icon");
93        mCustomIconToggle.setSelection(mValues.createIcon);
94        mCustomIconToggle.addSelectionListener(this);
95
96        mCreateActivityToggle = new Button(container, SWT.CHECK);
97        mCreateActivityToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false,
98                4, 1));
99        mCreateActivityToggle.setText("Create activity");
100        mCreateActivityToggle.setSelection(mValues.createActivity);
101        mCreateActivityToggle.addSelectionListener(this);
102
103        new Label(container, SWT.NONE).setLayoutData(
104                new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
105
106        mLibraryToggle = new Button(container, SWT.CHECK);
107        mLibraryToggle.setSelection(true);
108        mLibraryToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
109        mLibraryToggle.setText("Mark this project as a library");
110        mLibraryToggle.setSelection(mValues.isLibrary);
111        mLibraryToggle.addSelectionListener(this);
112
113        // Blank line
114        new Label(container, SWT.NONE).setLayoutData(
115                new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
116
117        mUseDefaultLocationToggle = new Button(container, SWT.CHECK);
118        mUseDefaultLocationToggle.setLayoutData(
119                new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
120        mUseDefaultLocationToggle.setText("Create Project in Workspace");
121        mUseDefaultLocationToggle.addSelectionListener(this);
122
123        mLocationLabel = new Label(container, SWT.NONE);
124        mLocationLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
125        mLocationLabel.setText("Location:");
126
127        mLocationText = new Text(container, SWT.BORDER);
128        mLocationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
129        mLocationText.addModifyListener(this);
130
131        mChooseLocationButton = new Button(container, SWT.NONE);
132        mChooseLocationButton.setText("Browse...");
133        mChooseLocationButton.addSelectionListener(this);
134        mChooseLocationButton.setEnabled(false);
135        setUseCustomLocation(!mValues.useDefaultLocation);
136
137        new Label(container, SWT.NONE).setLayoutData(
138                new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
139
140        Composite group = mWorkingSetGroup.createControl(container);
141        group.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 4, 1));
142    }
143
144    @Override
145    public void setVisible(boolean visible) {
146        super.setVisible(visible);
147
148        if (visible) {
149            try {
150                mIgnore = true;
151                mUseDefaultLocationToggle.setSelection(mValues.useDefaultLocation);
152                mLocationText.setText(mValues.projectLocation);
153            } finally {
154                mIgnore = false;
155            }
156        }
157
158        validatePage();
159    }
160
161    private void setUseCustomLocation(boolean en) {
162        mValues.useDefaultLocation = !en;
163        mUseDefaultLocationToggle.setSelection(!en);
164        if (!en) {
165            updateProjectLocation(mValues.projectName);
166        }
167
168        mLocationLabel.setEnabled(en);
169        mLocationText.setEnabled(en);
170        mChooseLocationButton.setEnabled(en);
171    }
172
173    void init(IStructuredSelection selection, IWorkbenchPart activePart) {
174        setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart));
175    }
176
177    /**
178     * Returns the working sets to which the new project should be added.
179     *
180     * @return the selected working sets to which the new project should be added
181     */
182    private IWorkingSet[] getWorkingSets() {
183        return mWorkingSetGroup.getSelectedWorkingSets();
184    }
185
186    /**
187     * Sets the working sets to which the new project should be added.
188     *
189     * @param workingSets the initial selected working sets
190     */
191    private void setWorkingSets(IWorkingSet[] workingSets) {
192        assert workingSets != null;
193        mWorkingSetGroup.setWorkingSets(workingSets);
194    }
195
196    @Override
197    public IWizardPage getNextPage() {
198        // Sync working set data to the value object, since the WorkingSetGroup
199        // doesn't let us add listeners to do this lazily
200        mValues.workingSets = getWorkingSets();
201
202        return super.getNextPage();
203    }
204
205    // ---- Implements ModifyListener ----
206
207    @Override
208    public void modifyText(ModifyEvent e) {
209        if (mIgnore) {
210            return;
211        }
212
213        Object source = e.getSource();
214        if (source == mLocationText) {
215            mValues.projectLocation = mLocationText.getText().trim();
216        }
217
218        validatePage();
219    }
220
221
222    /** If the project should be created in the workspace, then update the project location
223     * based on the project name. */
224    private void updateProjectLocation(String projectName) {
225        if (projectName == null) {
226            projectName = "";
227        }
228
229        boolean useDefaultLocation = mUseDefaultLocationToggle.getSelection();
230
231        if (useDefaultLocation) {
232            IPath workspace = Platform.getLocation();
233            String projectLocation = workspace.append(projectName).toOSString();
234            mLocationText.setText(projectLocation);
235            mValues.projectLocation = projectLocation;
236        }
237    }
238
239    // ---- Implements SelectionListener ----
240
241    @Override
242    public void widgetSelected(SelectionEvent e) {
243        if (mIgnore) {
244            return;
245        }
246
247        Object source = e.getSource();
248        if (source == mCustomIconToggle) {
249            mValues.createIcon = mCustomIconToggle.getSelection();
250        } else if (source == mLibraryToggle) {
251            mValues.isLibrary = mLibraryToggle.getSelection();
252        } else if (source == mCreateActivityToggle) {
253            mValues.createActivity = mCreateActivityToggle.getSelection();
254        } else if (source == mUseDefaultLocationToggle) {
255            boolean useDefault = mUseDefaultLocationToggle.getSelection();
256            setUseCustomLocation(!useDefault);
257        } else if (source == mChooseLocationButton) {
258            String dir = promptUserForLocation(getShell());
259            if (dir != null) {
260                mLocationText.setText(dir);
261                mValues.projectLocation = dir;
262            }
263        }
264
265        validatePage();
266    }
267
268    private String promptUserForLocation(Shell shell) {
269        DirectoryDialog dd = new DirectoryDialog(getShell());
270        dd.setMessage("Select folder where project should be created");
271
272        String curLocation = mLocationText.getText().trim();
273        if (!curLocation.isEmpty()) {
274            dd.setFilterPath(curLocation);
275        } else if (sLastProjectLocation != null) {
276            dd.setFilterPath(sLastProjectLocation);
277        }
278
279        String dir = dd.open();
280        if (dir != null) {
281            sLastProjectLocation = dir;
282        }
283
284        return dir;
285    }
286
287    @Override
288    public void widgetDefaultSelected(SelectionEvent e) {
289    }
290
291    // ---- Implements FocusListener ----
292
293    @Override
294    public void focusGained(FocusEvent e) {
295    }
296
297    @Override
298    public void focusLost(FocusEvent e) {
299    }
300
301    // Validation
302
303    void validatePage() {
304        IStatus status = validateProjectLocation();
305
306        setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
307        if (status != null) {
308            setMessage(status.getMessage(),
309                    status.getSeverity() == IStatus.ERROR
310                        ? IMessageProvider.ERROR : IMessageProvider.WARNING);
311        } else {
312            setErrorMessage(null);
313            setMessage(null);
314        }
315    }
316
317    static IStatus validateLocationInWorkspace(NewProjectWizardState values) {
318        if (values.useDefaultLocation) {
319            return null;
320        }
321
322        // Validate location
323        if (values.projectName != null) {
324            File dest = Platform.getLocation().append(values.projectName).toFile();
325            if (dest.exists()) {
326                return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, String.format(
327                   "There is already a file or directory named \"%1$s\" in the selected location.",
328                        values.projectName));
329            }
330        }
331
332        return null;
333    }
334
335    private IStatus validateProjectLocation() {
336        if (mValues.useDefaultLocation) {
337            return validateLocationInWorkspace(mValues);
338        }
339
340        String location = mLocationText.getText();
341        if (location.trim().isEmpty()) {
342            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
343                    "Provide a valid file system location where the project should be created.");
344        }
345
346        File f = new File(location);
347        if (f.exists()) {
348            if (!f.isDirectory()) {
349                return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
350                        String.format("'%s' is not a valid folder.", location));
351            }
352
353            File[] children = f.listFiles();
354            if (children != null && children.length > 0) {
355                return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
356                        String.format("Folder '%s' is not empty.", location));
357            }
358        }
359
360        // if the folder doesn't exist, then make sure that the parent
361        // exists and is a writable folder
362        File parent = f.getParentFile();
363        if (!parent.exists()) {
364            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
365                    String.format("Folder '%s' does not exist.", parent.getName()));
366        }
367
368        if (!parent.isDirectory()) {
369            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
370                    String.format("'%s' is not a folder.", parent.getName()));
371        }
372
373        if (!parent.canWrite()) {
374            return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
375                    String.format("'%s' is not writeable.", parent.getName()));
376        }
377
378        return null;
379    }
380}
381