1 /*
2  * Copyright (C) 2011 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  */
16 package com.android.ide.eclipse.adt.internal.assetstudio;
17 
18 import com.android.ide.eclipse.adt.AdtConstants;
19 import com.android.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.AdtUtils;
21 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
22 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
23 import com.android.utils.Pair;
24 
25 import org.eclipse.core.resources.IContainer;
26 import org.eclipse.core.resources.IFile;
27 import org.eclipse.core.resources.IProject;
28 import org.eclipse.core.resources.IResource;
29 import org.eclipse.core.runtime.CoreException;
30 import org.eclipse.core.runtime.IAdaptable;
31 import org.eclipse.core.runtime.IPath;
32 import org.eclipse.core.runtime.NullProgressMonitor;
33 import org.eclipse.core.runtime.Path;
34 import org.eclipse.jdt.core.IJavaProject;
35 import org.eclipse.jdt.ui.JavaUI;
36 import org.eclipse.jface.dialogs.MessageDialog;
37 import org.eclipse.jface.viewers.ISelectionProvider;
38 import org.eclipse.jface.viewers.IStructuredSelection;
39 import org.eclipse.jface.viewers.TreePath;
40 import org.eclipse.jface.viewers.TreeSelection;
41 import org.eclipse.jface.wizard.Wizard;
42 import org.eclipse.swt.SWT;
43 import org.eclipse.ui.IEditorPart;
44 import org.eclipse.ui.INewWizard;
45 import org.eclipse.ui.IViewPart;
46 import org.eclipse.ui.IWorkbench;
47 import org.eclipse.ui.IWorkbenchPage;
48 import org.eclipse.ui.IWorkbenchPartSite;
49 import org.eclipse.ui.IWorkbenchWindow;
50 import org.eclipse.ui.PlatformUI;
51 import org.eclipse.ui.part.FileEditorInput;
52 
53 import java.awt.image.BufferedImage;
54 import java.io.ByteArrayInputStream;
55 import java.io.ByteArrayOutputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.util.ArrayList;
59 import java.util.Collections;
60 import java.util.List;
61 import java.util.Map;
62 
63 import javax.imageio.ImageIO;
64 
65 /**
66  * Wizard for creating a new icon set
67  */
68 public class CreateAssetSetWizard extends Wizard implements INewWizard {
69     private ChooseAssetTypePage mChooseAssetPage;
70     private ConfigureAssetSetPage mConfigureAssetPage;
71     private IProject mInitialProject;
72     private List<IResource> mCreatedFiles;
73     private CreateAssetSetWizardState mValues = new CreateAssetSetWizardState();
74 
75     /** Creates a new asset set wizard */
CreateAssetSetWizard()76     public CreateAssetSetWizard() {
77         setWindowTitle("Create Asset Set");
78     }
79 
80     @Override
addPages()81     public void addPages() {
82         mValues.project = mInitialProject;
83 
84         mChooseAssetPage = new ChooseAssetTypePage(mValues);
85         mConfigureAssetPage = new ConfigureAssetSetPage(mValues);
86 
87         addPage(mChooseAssetPage);
88         addPage(mConfigureAssetPage);
89     }
90 
91     @Override
performFinish()92     public boolean performFinish() {
93         Map<String, Map<String, BufferedImage>> categories =
94                 ConfigureAssetSetPage.generateImages(mValues, false, null);
95 
96         IProject project = mValues.project;
97 
98         // Write out the images into the project
99         boolean yesToAll = false;
100         mCreatedFiles = new ArrayList<IResource>();
101 
102         for (Map<String, BufferedImage> previews : categories.values()) {
103             for (Map.Entry<String, BufferedImage> entry : previews.entrySet()) {
104                 String relativePath = entry.getKey();
105                 IPath dest = new Path(relativePath);
106                 IFile file = project.getFile(dest);
107                 if (file.exists()) {
108                     // Warn that the file already exists and ask the user what to do
109                     if (!yesToAll) {
110                         MessageDialog dialog = new MessageDialog(null, "File Already Exists", null,
111                                 String.format(
112                                         "%1$s already exists.\nWould you like to replace it?",
113                                         file.getProjectRelativePath().toOSString()),
114                                 MessageDialog.QUESTION, new String[] {
115                                         // Yes will be moved to the end because it's the default
116                                         "Yes", "No", "Cancel", "Yes to All"
117                                 }, 0);
118                         int result = dialog.open();
119                         switch (result) {
120                             case 0:
121                                 // Yes
122                                 break;
123                             case 3:
124                                 // Yes to all
125                                 yesToAll = true;
126                                 break;
127                             case 1:
128                                 // No
129                                 continue;
130                             case SWT.DEFAULT:
131                             case 2:
132                                 // Cancel
133                                 return false;
134                         }
135                     }
136 
137                     try {
138                         file.delete(true, new NullProgressMonitor());
139                     } catch (CoreException e) {
140                         AdtPlugin.log(e, null);
141                     }
142                 }
143 
144                 AdtUtils.createWsParentDirectory(file.getParent());
145                 BufferedImage image = entry.getValue();
146 
147                 ByteArrayOutputStream stream = new ByteArrayOutputStream();
148                 try {
149                     ImageIO.write(image, "PNG", stream); //$NON-NLS-1$
150                     byte[] bytes = stream.toByteArray();
151                     InputStream is = new ByteArrayInputStream(bytes);
152                     file.create(is, true /*force*/, null /*progress*/);
153                     mCreatedFiles.add(file);
154                 } catch (IOException e) {
155                     AdtPlugin.log(e, null);
156                 } catch (CoreException e) {
157                     AdtPlugin.log(e, null);
158                 }
159 
160                 try {
161                     file.getParent().refreshLocal(1, new NullProgressMonitor());
162                 } catch (CoreException e) {
163                     AdtPlugin.log(e, null);
164                 }
165             }
166         }
167 
168         // Finally select the files themselves
169         selectFiles(project, mCreatedFiles);
170 
171         return true;
172     }
173 
selectFiles(IProject project, List<? extends IResource> createdFiles)174     private void selectFiles(IProject project, List<? extends IResource> createdFiles) {
175         // Attempt to select the newly created files in the Package Explorer
176         IWorkbench workbench = AdtPlugin.getDefault().getWorkbench();
177         IWorkbenchPage page = workbench.getActiveWorkbenchWindow().getActivePage();
178         IViewPart viewPart = page.findView(JavaUI.ID_PACKAGES);
179         if (viewPart != null) {
180             IWorkbenchPartSite site = viewPart.getSite();
181             IJavaProject javaProject = null;
182             try {
183                 javaProject = BaseProjectHelper.getJavaProject(project);
184             } catch (CoreException e) {
185                 AdtPlugin.log(e, null);
186             }
187             final ISelectionProvider provider = site.getSelectionProvider();
188             if (provider != null) {
189                 List<TreePath> pathList = new ArrayList<TreePath>();
190                 for (IResource file : createdFiles) {
191                     // Create a TreePath for the given file,
192                     // which should be the JavaProject, followed by the folders down to
193                     // the final file.
194                     List<Object> segments = new ArrayList<Object>();
195                     segments.add(file);
196                     IContainer folder = file.getParent();
197                     if (folder != null && !(folder instanceof IProject)) {
198                         segments.add(folder);
199                         // res folder
200                         folder = folder.getParent();
201                         if (folder != null && !(folder instanceof IProject)) {
202                             segments.add(folder);
203                         }
204                     }
205                     // project
206                     segments.add(javaProject);
207 
208                     Collections.reverse(segments);
209                     TreePath path = new TreePath(segments.toArray());
210                     pathList.add(path);
211 
212                     // IDEA: Maybe normalize the files backwards (IFile objects aren't unique)
213                     // by maybe using the package explorer icons instead
214                 }
215 
216                 TreePath[] paths = pathList.toArray(new TreePath[pathList.size()]);
217                 final TreeSelection selection = new TreeSelection(paths);
218 
219                 provider.setSelection(selection);
220 
221                 // Workaround: The above doesn't always work; it will frequently select
222                 // some siblings of the real files. I've tried a number of workarounds:
223                 // normalizing the IFile objects by looking up the canonical ones via
224                 // their relative paths from the project; deferring execution with
225                 // Display.asyncRun; first calling select on the parents, etc.
226                 // However, it turns out a simple workaround works best: Calling this
227                 // method TWICE. The first call seems to expand all the necessary parents,
228                 // and the second call ensures that the correct children are selected!
229                 provider.setSelection(selection);
230 
231                 viewPart.setFocus();
232             }
233         }
234     }
235 
236     /** Sets the initial project to be used by the wizard */
setProject(IProject project)237     void setProject(IProject project) {
238         mInitialProject = project;
239         mValues.project = project;
240     }
241 
242     @Override
init(IWorkbench workbench, IStructuredSelection selection)243     public void init(IWorkbench workbench, IStructuredSelection selection) {
244         setHelpAvailable(false);
245 
246         mInitialProject = guessProject(selection);
247         mValues.project = mInitialProject;
248     }
249 
guessProject(IStructuredSelection selection)250     private IProject guessProject(IStructuredSelection selection) {
251         if (selection == null) {
252             return null;
253         }
254 
255         for (Object element : selection.toList()) {
256             if (element instanceof IAdaptable) {
257                 IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class);
258                 IProject project = res != null ? res.getProject() : null;
259 
260                 // Is this an Android project?
261                 try {
262                     if (project == null || !project.hasNature(AdtConstants.NATURE_DEFAULT)) {
263                         continue;
264                     }
265                 } catch (CoreException e) {
266                     // checking the nature failed, ignore this resource
267                     continue;
268                 }
269 
270                 return project;
271             } else if (element instanceof Pair<?, ?>) {
272                 // Pair of Project/String
273                 @SuppressWarnings("unchecked")
274                 Pair<IProject, String> pair = (Pair<IProject, String>) element;
275                 return pair.getFirst();
276             }
277         }
278 
279         // Try to figure out the project from the active editor
280         IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
281         if (window != null) {
282             IWorkbenchPage page = window.getActivePage();
283             if (page != null) {
284                 IEditorPart activeEditor = page.getActiveEditor();
285                 if (activeEditor instanceof AndroidXmlEditor) {
286                     Object input = ((AndroidXmlEditor) activeEditor).getEditorInput();
287                     if (input instanceof FileEditorInput) {
288                         FileEditorInput fileInput = (FileEditorInput) input;
289                         return fileInput.getFile().getProject();
290                     }
291                 }
292             }
293         }
294 
295         IJavaProject[] projects = AdtUtils.getOpenAndroidProjects();
296         if (projects != null && projects.length == 1) {
297             return projects[0].getProject();
298         }
299 
300         return null;
301     }
302 
303     /**
304      * Returns the list of files created by the wizard. This method will return
305      * null if {@link #performFinish()} has not yet been called.
306      *
307      * @return a list of files created by the wizard, or null
308      */
getCreatedFiles()309     List<IResource> getCreatedFiles() {
310         return mCreatedFiles;
311     }
312 }
313