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