Home | History | Annotate | Download | only in common
      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      */
     99     public CommonXmlEditor() {
    100         super();
    101     }
    102 
    103     @Override
    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
    212     public UiElementNode getUiRootNode() {
    213         return mDelegate == null ? null : mDelegate.getUiRootNode();
    214     }
    215 
    216     public CommonXmlDelegate getDelegate() {
    217         return mDelegate;
    218     }
    219 
    220     // ---- Base Class Overrides ----
    221 
    222     @Override
    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
    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
    258     public boolean isSaveAsAllowed() {
    259         return mDelegate == null ? false : mDelegate.isSaveAsAllowed();
    260     }
    261 
    262     /**
    263      * Create the various form pages.
    264      */
    265     @Override
    266     protected void createFormPages() {
    267         if (mDelegate != null) {
    268             mDelegate.delegateCreateFormPages();
    269         }
    270     }
    271 
    272     @Override
    273     protected void postCreatePages() {
    274         super.postCreatePages();
    275 
    276         if (mDelegate != null) {
    277             mDelegate.delegatePostCreatePages();
    278         }
    279     }
    280 
    281     @Override
    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
    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
    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
    327     protected void xmlModelChanged(Document xml_doc) {
    328         if (mDelegate != null) {
    329             mDelegate.delegateXmlModelChanged(xml_doc);
    330         }
    331     }
    332 
    333     @Override
    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
    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
    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
    371     protected int getPersistenceCategory() {
    372         if (mDelegate != null) {
    373             return mDelegate.delegateGetPersistenceCategory();
    374         }
    375         return CATEGORY_OTHER;
    376     }
    377 
    378     @Override
    379     public void initUiRootNode(boolean force) {
    380         if (mDelegate != null) {
    381             mDelegate.delegateInitUiRootNode(force);
    382         }
    383     }
    384 
    385     @Override
    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
    398     public void showEditorInput(IEditorInput editorInput) {
    399         if (mDelegate instanceof LayoutEditorDelegate) {
    400             ((LayoutEditorDelegate) mDelegate).showEditorInput(editorInput);
    401         }
    402     }
    403 
    404     @Override
    405     public boolean supportsFormatOnGuiEdit() {
    406         if (mDelegate != null) {
    407             return mDelegate.delegateSupportsFormatOnGuiEdit();
    408         }
    409         return super.supportsFormatOnGuiEdit();
    410     }
    411 
    412     @Override
    413     public void activated() {
    414         super.activated();
    415         if (mDelegate != null) {
    416             mDelegate.delegateActivated();
    417         }
    418     }
    419 
    420     @Override
    421     public void deactivated() {
    422         super.deactivated();
    423         if (mDelegate != null) {
    424             mDelegate.delegateDeactivated();
    425         }
    426     }
    427 
    428     @Override
    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
    444     public void setPartName(String partName) {
    445         super.setPartName(partName);
    446     }
    447 
    448     @Override
    449     public void setPageText(int pageIndex, String text) {
    450         super.setPageText(pageIndex, text);
    451     }
    452 
    453     @Override
    454     public void firePropertyChange(int propertyId) {
    455         super.firePropertyChange(propertyId);
    456     }
    457 
    458     @Override
    459     public int getPageCount() {
    460         return super.getPageCount();
    461     }
    462 
    463     @Override
    464     public int getCurrentPage() {
    465         return super.getCurrentPage();
    466     }
    467 }
    468