1 /* 2 * Copyright (C) 2007 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.layout; 18 19 import com.android.ide.eclipse.adt.AdtPlugin; 20 import com.android.ide.eclipse.adt.AndroidConstants; 21 import com.android.ide.eclipse.adt.internal.editors.AndroidEditor; 22 import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor; 23 import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions; 24 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode; 25 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode; 26 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; 27 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 28 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; 29 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; 30 31 import org.eclipse.core.resources.IFile; 32 import org.eclipse.core.runtime.IProgressMonitor; 33 import org.eclipse.core.runtime.NullProgressMonitor; 34 import org.eclipse.gef.ui.parts.TreeViewer; 35 import org.eclipse.ui.IEditorInput; 36 import org.eclipse.ui.IEditorPart; 37 import org.eclipse.ui.IPartListener; 38 import org.eclipse.ui.IShowEditorInput; 39 import org.eclipse.ui.IWorkbenchPage; 40 import org.eclipse.ui.IWorkbenchPart; 41 import org.eclipse.ui.IWorkbenchPartSite; 42 import org.eclipse.ui.PartInitException; 43 import org.eclipse.ui.part.FileEditorInput; 44 import org.eclipse.ui.views.contentoutline.IContentOutlinePage; 45 import org.eclipse.ui.views.properties.IPropertySheetPage; 46 import org.w3c.dom.Document; 47 48 /** 49 * Multi-page form editor for /res/layout XML files. 50 */ 51 public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPartListener { 52 53 public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".layout.LayoutEditor"; //$NON-NLS-1$ 54 55 /** Root node of the UI element hierarchy */ 56 private UiDocumentNode mUiRootNode; 57 58 private IGraphicalLayoutEditor mGraphicalEditor; 59 private int mGraphicalEditorIndex; 60 /** Implementation of the {@link IContentOutlinePage} for this editor */ 61 private UiContentOutlinePage mOutline; 62 /** Custom implementation of {@link IPropertySheetPage} for this editor */ 63 private UiPropertySheetPage mPropertyPage; 64 65 private UiEditorActions mUiEditorActions; 66 67 /** 68 * Creates the form editor for resources XML files. 69 */ LayoutEditor()70 public LayoutEditor() { 71 super(); 72 } 73 74 /** 75 * @return The root node of the UI element hierarchy 76 */ 77 @Override getUiRootNode()78 public UiDocumentNode getUiRootNode() { 79 return mUiRootNode; 80 } 81 82 // ---- Base Class Overrides ---- 83 84 @Override dispose()85 public void dispose() { 86 getSite().getPage().removePartListener(this); 87 88 super.dispose(); 89 } 90 91 /** 92 * Save the XML. 93 * <p/> 94 * The actual save operation is done in the super class by committing 95 * all data to the XML model and then having the Structured XML Editor 96 * save the XML. 97 * <p/> 98 * Here we just need to tell the graphical editor that the model has 99 * been saved. 100 */ 101 @Override doSave(IProgressMonitor monitor)102 public void doSave(IProgressMonitor monitor) { 103 super.doSave(monitor); 104 if (mGraphicalEditor != null) { 105 mGraphicalEditor.doSave(monitor); 106 } 107 } 108 109 /** 110 * Returns whether the "save as" operation is supported by this editor. 111 * <p/> 112 * Save-As is a valid operation for the ManifestEditor since it acts on a 113 * single source file. 114 * 115 * @see IEditorPart 116 */ 117 @Override isSaveAsAllowed()118 public boolean isSaveAsAllowed() { 119 return true; 120 } 121 122 /** 123 * Create the various form pages. 124 */ 125 @Override createFormPages()126 protected void createFormPages() { 127 try { 128 // The graphical layout editor is now enabled by default. 129 // In case there's an issue we provide a way to disable it using an 130 // env variable. 131 if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) { //$NON-NLS-1$ 132 if (mGraphicalEditor == null) { 133 134 if (System.getenv("USE_GLE2") != null) { //$NON-NLS-1$ //$NON-NLS-2$ 135 mGraphicalEditor = new GraphicalEditorPart(this); 136 } else { 137 mGraphicalEditor = new GraphicalLayoutEditor(this); 138 } 139 140 mGraphicalEditorIndex = addPage(mGraphicalEditor, getEditorInput()); 141 setPageText(mGraphicalEditorIndex, mGraphicalEditor.getTitle()); 142 } else { 143 mGraphicalEditor.reloadEditor(); 144 } 145 146 // update the config based on the opened file. 147 IEditorInput input = getEditorInput(); 148 if (input instanceof FileEditorInput) { 149 FileEditorInput fileInput = (FileEditorInput)input; 150 ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder( 151 fileInput.getFile()); 152 if (resFolder != null) { 153 mGraphicalEditor.editNewFile(resFolder.getConfiguration()); 154 } 155 } 156 157 // put in place the listener to handle layout recompute only when needed. 158 getSite().getPage().addPartListener(this); 159 } 160 } catch (PartInitException e) { 161 AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ 162 } 163 } 164 165 /* (non-java doc) 166 * Change the tab/title name to include the name of the layout. 167 */ 168 @Override setInput(IEditorInput input)169 protected void setInput(IEditorInput input) { 170 super.setInput(input); 171 handleNewInput(input); 172 } 173 174 /* 175 * (non-Javadoc) 176 * @see org.eclipse.ui.part.EditorPart#setInputWithNotify(org.eclipse.ui.IEditorInput) 177 */ 178 @Override setInputWithNotify(IEditorInput input)179 protected void setInputWithNotify(IEditorInput input) { 180 super.setInputWithNotify(input); 181 handleNewInput(input); 182 } 183 184 /** 185 * Called to replace the current {@link IEditorInput} with another one. 186 * <p/>This is used when {@link MatchingStrategy} returned <code>true</code> which means we're 187 * opening a different configuration of the same layout. 188 */ showEditorInput(IEditorInput editorInput)189 public void showEditorInput(IEditorInput editorInput) { 190 // save the current editor input. 191 doSave(new NullProgressMonitor()); 192 193 // get the current page 194 int currentPage = getActivePage(); 195 196 // remove the pages, except for the graphical editor, which will be dynamically adapted 197 // to the new model. 198 // page after the graphical editor: 199 int count = getPageCount(); 200 for (int i = count - 1 ; i > mGraphicalEditorIndex ; i--) { 201 removePage(i); 202 } 203 // pages before the graphical editor 204 for (int i = mGraphicalEditorIndex - 1 ; i >= 0 ; i--) { 205 removePage(i); 206 } 207 208 // set the current input. 209 setInputWithNotify(editorInput); 210 211 // re-create or reload the pages with the default page shown as the previous active page. 212 createAndroidPages(); 213 selectDefaultPage(Integer.toString(currentPage)); 214 215 // update the outline 216 if (mOutline != null && mGraphicalEditor != null) { 217 mOutline.reloadModel(); 218 } 219 } 220 221 /** 222 * Processes the new XML Model, which XML root node is given. 223 * 224 * @param xml_doc The XML document, if available, or null if none exists. 225 */ 226 @Override xmlModelChanged(Document xml_doc)227 protected void xmlModelChanged(Document xml_doc) { 228 // init the ui root on demand 229 initUiRootNode(false /*force*/); 230 231 mUiRootNode.loadFromXmlNode(xml_doc); 232 233 // update the model first, since it is used by the viewers. 234 super.xmlModelChanged(xml_doc); 235 236 if (mGraphicalEditor != null) { 237 mGraphicalEditor.onXmlModelChanged(); 238 } 239 240 if (mOutline != null) { 241 mOutline.reloadModel(); 242 } 243 } 244 245 /* (non-java doc) 246 * Returns the IContentOutlinePage when asked for it. 247 */ 248 @SuppressWarnings("unchecked") 249 @Override getAdapter(Class adapter)250 public Object getAdapter(Class adapter) { 251 // for the outline, force it to come from the Graphical Editor. 252 // This fixes the case where a layout file is opened in XML view first and the outline 253 // gets stuck in the XML outline. 254 if (IContentOutlinePage.class == adapter && mGraphicalEditor != null) { 255 256 if (mOutline == null && mGraphicalEditor instanceof GraphicalLayoutEditor) { 257 // TODO add support for GLE2 258 mOutline = new UiContentOutlinePage( 259 (GraphicalLayoutEditor) mGraphicalEditor, 260 new TreeViewer()); 261 } 262 263 return mOutline; 264 } 265 266 if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) { 267 if (mPropertyPage == null) { 268 mPropertyPage = new UiPropertySheetPage(); 269 } 270 271 return mPropertyPage; 272 } 273 274 // return default 275 return super.getAdapter(adapter); 276 } 277 278 @Override pageChange(int newPageIndex)279 protected void pageChange(int newPageIndex) { 280 super.pageChange(newPageIndex); 281 282 if (mGraphicalEditor != null) { 283 if (newPageIndex == mGraphicalEditorIndex) { 284 mGraphicalEditor.activated(); 285 } else { 286 mGraphicalEditor.deactivated(); 287 } 288 } 289 } 290 291 // ----- IPartListener Methods ---- 292 partActivated(IWorkbenchPart part)293 public void partActivated(IWorkbenchPart part) { 294 if (part == this) { 295 if (mGraphicalEditor != null) { 296 if (getActivePage() == mGraphicalEditorIndex) { 297 mGraphicalEditor.activated(); 298 } else { 299 mGraphicalEditor.deactivated(); 300 } 301 } 302 } 303 } 304 partBroughtToTop(IWorkbenchPart part)305 public void partBroughtToTop(IWorkbenchPart part) { 306 partActivated(part); 307 } 308 partClosed(IWorkbenchPart part)309 public void partClosed(IWorkbenchPart part) { 310 // pass 311 } 312 partDeactivated(IWorkbenchPart part)313 public void partDeactivated(IWorkbenchPart part) { 314 if (part == this) { 315 if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) { 316 mGraphicalEditor.deactivated(); 317 } 318 } 319 } 320 partOpened(IWorkbenchPart part)321 public void partOpened(IWorkbenchPart part) { 322 EclipseUiHelper.showView(EclipseUiHelper.CONTENT_OUTLINE_VIEW_ID, false /* activate */); 323 EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, false /* activate */); 324 } 325 326 public class UiEditorActions extends UiActions { 327 328 @Override getRootNode()329 protected UiDocumentNode getRootNode() { 330 return mUiRootNode; 331 } 332 333 // Select the new item 334 @Override selectUiNode(UiElementNode uiNodeToSelect)335 protected void selectUiNode(UiElementNode uiNodeToSelect) { 336 mGraphicalEditor.selectModel(uiNodeToSelect); 337 } 338 339 @Override commitPendingXmlChanges()340 public void commitPendingXmlChanges() { 341 // Pass. There is nothing to commit before the XML is changed here. 342 } 343 } 344 getUiEditorActions()345 public UiEditorActions getUiEditorActions() { 346 if (mUiEditorActions == null) { 347 mUiEditorActions = new UiEditorActions(); 348 } 349 return mUiEditorActions; 350 } 351 352 // ---- Local Methods ---- 353 354 /** 355 * Returns true if the Graphics editor page is visible. This <b>must</b> be 356 * called from the UI thread. 357 */ isGraphicalEditorActive()358 boolean isGraphicalEditorActive() { 359 IWorkbenchPartSite workbenchSite = getSite(); 360 IWorkbenchPage workbenchPage = workbenchSite.getPage(); 361 362 // check if the editor is visible in the workbench page 363 if (workbenchPage.isPartVisible(this) && workbenchPage.getActiveEditor() == this) { 364 // and then if the page of the editor is visible (not to be confused with 365 // the workbench page) 366 return mGraphicalEditorIndex == getActivePage(); 367 } 368 369 return false; 370 } 371 372 @Override initUiRootNode(boolean force)373 protected void initUiRootNode(boolean force) { 374 // The root UI node is always created, even if there's no corresponding XML node. 375 if (mUiRootNode == null || force) { 376 // get the target data from the opened file (and its project) 377 AndroidTargetData data = getTargetData(); 378 379 Document doc = null; 380 if (mUiRootNode != null) { 381 doc = mUiRootNode.getXmlDocument(); 382 } 383 384 DocumentDescriptor desc; 385 if (data == null) { 386 desc = new DocumentDescriptor("temp", null /*children*/); 387 } else { 388 desc = data.getLayoutDescriptors().getDescriptor(); 389 } 390 391 // get the descriptors from the data. 392 mUiRootNode = (UiDocumentNode) desc.createUiNode(); 393 mUiRootNode.setEditor(this); 394 395 onDescriptorsChanged(doc); 396 } 397 } 398 onDescriptorsChanged(Document document)399 private void onDescriptorsChanged(Document document) { 400 if (document != null) { 401 mUiRootNode.loadFromXmlNode(document); 402 } else { 403 mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlDocument()); 404 } 405 406 if (mOutline != null) { 407 mOutline.reloadModel(); 408 } 409 410 if (mGraphicalEditor != null) { 411 mGraphicalEditor.reloadEditor(); 412 mGraphicalEditor.reloadPalette(); 413 mGraphicalEditor.reloadConfigurationUi(true /*notify listener */); 414 mGraphicalEditor.recomputeLayout(); 415 } 416 } 417 418 /** 419 * Handles a new input, and update the part name. 420 * @param input the new input. 421 */ handleNewInput(IEditorInput input)422 private void handleNewInput(IEditorInput input) { 423 if (input instanceof FileEditorInput) { 424 FileEditorInput fileInput = (FileEditorInput) input; 425 IFile file = fileInput.getFile(); 426 setPartName(String.format("%1$s", 427 file.getName())); 428 } 429 } 430 } 431