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  */
16 
17 package com.android.ide.eclipse.adt.internal.editors.common;
18 
19 import com.android.ide.common.resources.ResourceFolder;
20 import com.android.ide.eclipse.adt.AdtConstants;
21 import com.android.ide.eclipse.adt.AdtPlugin;
22 import com.android.ide.eclipse.adt.AdtUtils;
23 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
24 import com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditorDelegate;
25 import com.android.ide.eclipse.adt.internal.editors.color.ColorEditorDelegate;
26 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate.IDelegateCreator;
27 import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditorDelegate;
28 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
29 import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditorDelegate;
30 import com.android.ide.eclipse.adt.internal.editors.otherxml.OtherXmlEditorDelegate;
31 import com.android.ide.eclipse.adt.internal.editors.otherxml.PlainXmlEditorDelegate;
32 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
33 import com.android.ide.eclipse.adt.internal.editors.values.ValuesEditorDelegate;
34 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
35 import com.android.resources.ResourceFolderType;
36 
37 import org.eclipse.core.resources.IFile;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.jobs.Job;
41 import org.eclipse.jface.text.source.ISourceViewer;
42 import org.eclipse.jface.text.source.ISourceViewerExtension2;
43 import org.eclipse.ui.IEditorDescriptor;
44 import org.eclipse.ui.IEditorInput;
45 import org.eclipse.ui.IEditorPart;
46 import org.eclipse.ui.IEditorSite;
47 import org.eclipse.ui.IFileEditorInput;
48 import org.eclipse.ui.IShowEditorInput;
49 import org.eclipse.ui.IURIEditorInput;
50 import org.eclipse.ui.PartInitException;
51 import org.eclipse.ui.forms.editor.IFormPage;
52 import org.eclipse.ui.ide.IDE;
53 import org.w3c.dom.Document;
54 
55 /**
56  * Multi-page form editor for ALL /res XML files.
57  * <p/>
58  * This editor doesn't actually do anything. Instead, it defers actual implementation
59  * to {@link CommonXmlDelegate} instances.
60  */
61 public class CommonXmlEditor extends AndroidXmlEditor implements IShowEditorInput {
62 
63     public static final String ID = AdtConstants.EDITORS_NAMESPACE + ".CommonXmlEditor"; //$NON-NLS-1$
64 
65     /**
66      * Registered {@link CommonXmlDelegate}s.
67      * All delegates must have a {@code Creator} class which is instantiated
68      * once here statically. All the creators are invoked in the order they
69      * are defined and the first one to return a non-null delegate is used.
70      */
71     private static final IDelegateCreator[] DELEGATES = {
72             new LayoutEditorDelegate.Creator(),
73             new ValuesEditorDelegate.Creator(),
74             new AnimationEditorDelegate.Creator(),
75             new ColorEditorDelegate.Creator(),
76             new DrawableEditorDelegate.Creator(),
77             new MenuEditorDelegate.Creator(),
78             new OtherXmlEditorDelegate.Creator(),
79     };
80 
81     /**
82      * IDs of legacy editors replaced by the {@link CommonXmlEditor}.
83      */
84     public static final String[] LEGACY_EDITOR_IDS = {
85         LayoutEditorDelegate.LEGACY_EDITOR_ID,
86         ValuesEditorDelegate.LEGACY_EDITOR_ID,
87         AnimationEditorDelegate.LEGACY_EDITOR_ID,
88         ColorEditorDelegate.LEGACY_EDITOR_ID,
89         DrawableEditorDelegate.LEGACY_EDITOR_ID,
90         MenuEditorDelegate.LEGACY_EDITOR_ID,
91         OtherXmlEditorDelegate.LEGACY_EDITOR_ID,
92     };
93 
94     private CommonXmlDelegate mDelegate = null;
95 
96     /**
97      * Creates the form editor for resources XML files.
98      */
CommonXmlEditor()99     public CommonXmlEditor() {
100         super();
101     }
102 
103     @Override
init(IEditorSite site, final IEditorInput editorInput)104     public void init(IEditorSite site, final IEditorInput editorInput)
105             throws PartInitException {
106         if (editorInput instanceof IFileEditorInput) {
107 
108             IFileEditorInput fileInput = (IFileEditorInput) editorInput;
109             IFile file = fileInput.getFile();
110 
111             // Adjust the default file editor ID
112 
113             IEditorDescriptor file_desc = IDE.getDefaultEditor(file);
114             String id = file_desc == null ? null : file_desc.getId();
115             boolean mustChange = id != null &&
116                                  !id.equals(ID) &&
117                                  id.startsWith(AdtConstants.EDITORS_NAMESPACE);
118             if (!mustChange) {
119                 // Maybe this was opened by a manual Open With with a legacy ID?
120                 id = site.getId();
121                 mustChange = id != null &&
122                              !id.equals(ID) &&
123                              id.startsWith(AdtConstants.EDITORS_NAMESPACE);
124             }
125 
126             if (mustChange) {
127                 // It starts by our editor namespace but it's not the right ID.
128                 // This is an old Android XML ID. Change it to our new ID.
129                 IDE.setDefaultEditor(file, ID);
130                 AdtPlugin.log(IStatus.INFO,
131                         "Changed legacy editor ID %s for %s",   //$NON-NLS-1$
132                         id,
133                         file.getFullPath());
134             }
135 
136             // Now find the delegate for the file.
137 
138             ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
139             ResourceFolderType type = resFolder == null ? null : resFolder.getType();
140 
141             if (type == null) {
142                 // We lack any real resource information about that file.
143                 // Let's take a guess using the actual path.
144                 String folderName = AdtUtils.getParentFolderName(editorInput);
145                 type = ResourceFolderType.getFolderType(folderName);
146             }
147 
148             if (type != null) {
149                 for (IDelegateCreator creator : DELEGATES) {
150                     mDelegate = creator.createForFile(this, type);
151                     if (mDelegate != null) {
152                         break;
153                     }
154                 }
155             }
156 
157             if (mDelegate == null) {
158                 // We didn't find any editor.
159                 // We'll use the PlainXmlEditorDelegate as a catch-all editor.
160                 AdtPlugin.log(IStatus.INFO,
161                         "No valid Android XML Editor Delegate found for file %1$s [Res %2$s, type %3$s]",
162                         file.getFullPath(),
163                         resFolder,
164                         type);
165                 mDelegate = new PlainXmlEditorDelegate(this);
166             }
167         } else if (editorInput instanceof IURIEditorInput) {
168             String folderName = AdtUtils.getParentFolderName(editorInput);
169             ResourceFolderType type = ResourceFolderType.getFolderType(folderName);
170             if (type == ResourceFolderType.LAYOUT) {
171                 // The layout editor has a lot of hardcoded requirements for real IFiles
172                 // and IProjects so for now just use a plain XML editor for project-less layout
173                 // files
174                 mDelegate = new OtherXmlEditorDelegate(this);
175             } else if (type != null) {
176                 for (IDelegateCreator creator : DELEGATES) {
177                     mDelegate = creator.createForFile(this, type);
178                     if (mDelegate != null) {
179                         break;
180                     }
181                 }
182             }
183 
184             if (mDelegate == null) {
185                 // We didn't find any editor.
186                 // We'll use the PlainXmlEditorDelegate as a catch-all editor.
187                 AdtPlugin.log(IStatus.INFO,
188                         "No valid Android XML Editor Delegate found for file %1$s [Res %2$s, type %3$s]",
189                         ((IURIEditorInput) editorInput).getURI().toString(),
190                         folderName,
191                         type);
192                 mDelegate = new PlainXmlEditorDelegate(this);
193             }
194         }
195 
196         if (mDelegate == null) {
197             // We can't do anything if we don't have a valid file.
198             AdtPlugin.log(IStatus.INFO,
199                     "Android XML Editor cannot process non-file input %1$s",   //$NON-NLS-1$
200                     (editorInput == null ? "null" : editorInput.toString()));   //$NON-NLS-1$
201             throw new PartInitException("Android XML Editor cannot process this input.");
202         } else {
203             // Invoke the editor's init after setting up the delegate. This will call setInput().
204             super.init(site, editorInput);
205         }
206     }
207 
208     /**
209      * @return The root node of the UI element hierarchy
210      */
211     @Override
getUiRootNode()212     public UiElementNode getUiRootNode() {
213         return mDelegate == null ? null : mDelegate.getUiRootNode();
214     }
215 
getDelegate()216     public CommonXmlDelegate getDelegate() {
217         return mDelegate;
218     }
219 
220     // ---- Base Class Overrides ----
221 
222     @Override
dispose()223     public void dispose() {
224         if (mDelegate != null) {
225             mDelegate.dispose();
226         }
227 
228         super.dispose();
229     }
230 
231     /**
232      * Save the XML.
233      * <p/>
234      * The actual save operation is done in the super class by committing
235      * all data to the XML model and then having the Structured XML Editor
236      * save the XML.
237      * <p/>
238      * Here we just need to tell the delegate that the model has
239      * been saved.
240      */
241     @Override
doSave(IProgressMonitor monitor)242     public void doSave(IProgressMonitor monitor) {
243         super.doSave(monitor);
244         if (mDelegate != null) {
245             mDelegate.delegateDoSave(monitor);
246         }
247     }
248 
249     /**
250      * Returns whether the "save as" operation is supported by this editor.
251      * <p/>
252      * Save-As is a valid operation for the ManifestEditor since it acts on a
253      * single source file.
254      *
255      * @see IEditorPart
256      */
257     @Override
isSaveAsAllowed()258     public boolean isSaveAsAllowed() {
259         return mDelegate == null ? false : mDelegate.isSaveAsAllowed();
260     }
261 
262     /**
263      * Create the various form pages.
264      */
265     @Override
createFormPages()266     protected void createFormPages() {
267         if (mDelegate != null) {
268             mDelegate.delegateCreateFormPages();
269         }
270     }
271 
272     @Override
postCreatePages()273     protected void postCreatePages() {
274         super.postCreatePages();
275 
276         if (mDelegate != null) {
277             mDelegate.delegatePostCreatePages();
278         }
279     }
280 
281     @Override
addPages()282     protected void addPages() {
283         // Create the editor pages.
284         // This will also create the EditorPart.
285         super.addPages();
286 
287         // When the EditorPart is being created, it configures the SourceViewer
288         // and will try to use our CommonSourceViewerConfig. Our config needs to
289         // know which ContentAssist processor to use (since we have one per resource
290         // folder type) but it doesn't have the necessary info to do so.
291         // Consequently, once the part is created, we can now unconfigure the source
292         // viewer and reconfigure it with the right settings.
293         ISourceViewer ssv = getStructuredSourceViewer();
294         if (mDelegate != null && ssv instanceof ISourceViewerExtension2) {
295             ((ISourceViewerExtension2) ssv).unconfigure();
296             ssv.configure(new CommonSourceViewerConfig(
297                     mDelegate.getAndroidContentAssistProcessor()));
298         }
299     }
300 
301     /* (non-java doc)
302      * Change the tab/title name to include the name of the layout.
303      */
304     @Override
setInput(IEditorInput input)305     protected void setInput(IEditorInput input) {
306         super.setInput(input);
307         assert mDelegate != null;
308         if (mDelegate != null) {
309             mDelegate.delegateSetInput(input);
310         }
311     }
312 
313     @Override
setInputWithNotify(IEditorInput input)314     public void setInputWithNotify(IEditorInput input) {
315         super.setInputWithNotify(input);
316         if (mDelegate instanceof LayoutEditorDelegate) {
317             ((LayoutEditorDelegate) mDelegate).delegateSetInputWithNotify(input);
318         }
319     }
320 
321     /**
322      * Processes the new XML Model, which XML root node is given.
323      *
324      * @param xml_doc The XML document, if available, or null if none exists.
325      */
326     @Override
xmlModelChanged(Document xml_doc)327     protected void xmlModelChanged(Document xml_doc) {
328         if (mDelegate != null) {
329             mDelegate.delegateXmlModelChanged(xml_doc);
330         }
331     }
332 
333     @Override
runLint()334     protected Job runLint() {
335         if (mDelegate != null && getEditorInput() instanceof IFileEditorInput) {
336             return mDelegate.delegateRunLint();
337         }
338         return null;
339     }
340 
341     /**
342      * Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
343      */
344     @Override
getAdapter(@uppressWarnings"rawtypes") Class adapter)345     public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
346         if (mDelegate != null) {
347             Object value = mDelegate.delegateGetAdapter(adapter);
348             if (value != null) {
349                 return value;
350             }
351         }
352 
353         // return default
354         return super.getAdapter(adapter);
355     }
356 
357     @Override
pageChange(int newPageIndex)358     protected void pageChange(int newPageIndex) {
359         if (mDelegate != null) {
360             mDelegate.delegatePageChange(newPageIndex);
361         }
362 
363         super.pageChange(newPageIndex);
364 
365         if (mDelegate != null) {
366             mDelegate.delegatePostPageChange(newPageIndex);
367         }
368     }
369 
370     @Override
getPersistenceCategory()371     protected int getPersistenceCategory() {
372         if (mDelegate != null) {
373             return mDelegate.delegateGetPersistenceCategory();
374         }
375         return CATEGORY_OTHER;
376     }
377 
378     @Override
initUiRootNode(boolean force)379     public void initUiRootNode(boolean force) {
380         if (mDelegate != null) {
381             mDelegate.delegateInitUiRootNode(force);
382         }
383     }
384 
385     @Override
setActivePage(String pageId)386     public IFormPage setActivePage(String pageId) {
387         IFormPage page = super.setActivePage(pageId);
388 
389         if (mDelegate != null) {
390             return mDelegate.delegatePostSetActivePage(page, pageId);
391         }
392 
393         return page;
394     }
395 
396     /* Implements showEditorInput(...) in IShowEditorInput */
397     @Override
showEditorInput(IEditorInput editorInput)398     public void showEditorInput(IEditorInput editorInput) {
399         if (mDelegate instanceof LayoutEditorDelegate) {
400             ((LayoutEditorDelegate) mDelegate).showEditorInput(editorInput);
401         }
402     }
403 
404     @Override
supportsFormatOnGuiEdit()405     public boolean supportsFormatOnGuiEdit() {
406         if (mDelegate != null) {
407             return mDelegate.delegateSupportsFormatOnGuiEdit();
408         }
409         return super.supportsFormatOnGuiEdit();
410     }
411 
412     @Override
activated()413     public void activated() {
414         super.activated();
415         if (mDelegate != null) {
416             mDelegate.delegateActivated();
417         }
418     }
419 
420     @Override
deactivated()421     public void deactivated() {
422         super.deactivated();
423         if (mDelegate != null) {
424             mDelegate.delegateDeactivated();
425         }
426     }
427 
428     @Override
getPartName()429     public String getPartName() {
430         if (mDelegate != null) {
431             String name = mDelegate.delegateGetPartName();
432             if (name != null) {
433                 return name;
434             }
435         }
436 
437         return super.getPartName();
438     }
439 
440     // --------------------
441     // Base methods exposed so that XmlEditorDelegate can access them
442 
443     @Override
setPartName(String partName)444     public void setPartName(String partName) {
445         super.setPartName(partName);
446     }
447 
448     @Override
setPageText(int pageIndex, String text)449     public void setPageText(int pageIndex, String text) {
450         super.setPageText(pageIndex, text);
451     }
452 
453     @Override
firePropertyChange(int propertyId)454     public void firePropertyChange(int propertyId) {
455         super.firePropertyChange(propertyId);
456     }
457 
458     @Override
getPageCount()459     public int getPageCount() {
460         return super.getPageCount();
461     }
462 
463     @Override
getCurrentPage()464     public int getCurrentPage() {
465         return super.getCurrentPage();
466     }
467 }
468