2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.editors.layout;
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.layout.gle1.GraphicalLayoutEditor;
24 import com.android.ide.eclipse.adt.internal.editors.layout.gle1.UiContentOutlinePage;
25 import com.android.ide.eclipse.adt.internal.editors.layout.gle1.UiPropertySheetPage;
26 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
27 import com.android.ide.eclipse.adt.internal.editors.ui.tree.UiActions;
28 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
29 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
30 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder;
31 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
32 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
33 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper;
35 import org.eclipse.core.resources.IFile;
36 import org.eclipse.core.runtime.IProgressMonitor;
37 import org.eclipse.core.runtime.NullProgressMonitor;
38 import org.eclipse.gef.ui.parts.TreeViewer;
39 import org.eclipse.ui.IEditorInput;
40 import org.eclipse.ui.IEditorPart;
41 import org.eclipse.ui.IPartListener;
42 import org.eclipse.ui.IShowEditorInput;
43 import org.eclipse.ui.IWorkbenchPage;
44 import org.eclipse.ui.IWorkbenchPart;
45 import org.eclipse.ui.IWorkbenchPartSite;
46 import org.eclipse.ui.PartInitException;
47 import org.eclipse.ui.part.FileEditorInput;
48 import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
49 import org.eclipse.ui.views.properties.IPropertySheetPage;
50 import org.w3c.dom.Document;
53 * Multi-page form editor for /res/layout XML files.
55 public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPartListener {
57 public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".layout.LayoutEditor"; //$NON-NLS-1$
59 /** Root node of the UI element hierarchy */
60 private UiDocumentNode mUiRootNode;
62 private IGraphicalLayoutEditor mGraphicalEditor;
63 private int mGraphicalEditorIndex;
64 /** Implementation of the {@link IContentOutlinePage} for this editor */
65 private UiContentOutlinePage mOutline;
66 /** Custom implementation of {@link IPropertySheetPage} for this editor */
67 private UiPropertySheetPage mPropertyPage;
69 private UiEditorActions mUiEditorActions;
72 * Creates the form editor for resources XML files.
74 public LayoutEditor() {
79 * @return The root node of the UI element hierarchy
82 public UiDocumentNode getUiRootNode() {
86 // ---- Base Class Overrides ----
89 public void dispose() {
90 getSite().getPage().removePartListener(this);
98 * The actual save operation is done in the super class by committing
99 * all data to the XML model and then having the Structured XML Editor
102 * Here we just need to tell the graphical editor that the model has
106 public void doSave(IProgressMonitor monitor) {
107 super.doSave(monitor);
108 if (mGraphicalEditor != null) {
109 mGraphicalEditor.doSave(monitor);
114 * Returns whether the "save as" operation is supported by this editor.
116 * Save-As is a valid operation for the ManifestEditor since it acts on a
117 * single source file.
122 public boolean isSaveAsAllowed() {
127 * Create the various form pages.
130 protected void createFormPages() {
132 // The graphical layout editor is now enabled by default.
133 // In case there's an issue we provide a way to disable it using an
135 if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) { //$NON-NLS-1$
136 if (mGraphicalEditor == null) {
138 if (System.getenv("USE_GLE2") != null) { //$NON-NLS-1$ //$NON-NLS-2$
139 mGraphicalEditor = new GraphicalEditorPart(this);
141 mGraphicalEditor = new GraphicalLayoutEditor(this);
144 mGraphicalEditorIndex = addPage(mGraphicalEditor, getEditorInput());
145 setPageText(mGraphicalEditorIndex, mGraphicalEditor.getTitle());
147 mGraphicalEditor.reloadEditor();
150 // update the config based on the opened file.
151 IEditorInput input = getEditorInput();
152 if (input instanceof FileEditorInput) {
153 FileEditorInput fileInput = (FileEditorInput)input;
154 ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(
155 fileInput.getFile());
156 if (resFolder != null) {
157 mGraphicalEditor.editNewFile(resFolder.getConfiguration());
161 // put in place the listener to handle layout recompute only when needed.
162 getSite().getPage().addPartListener(this);
164 } catch (PartInitException e) {
165 AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$
170 * Change the tab/title name to include the name of the layout.
173 protected void setInput(IEditorInput input) {
174 super.setInput(input);
175 handleNewInput(input);
180 * @see org.eclipse.ui.part.EditorPart#setInputWithNotify(org.eclipse.ui.IEditorInput)
183 protected void setInputWithNotify(IEditorInput input) {
184 super.setInputWithNotify(input);
185 handleNewInput(input);
189 * Called to replace the current {@link IEditorInput} with another one.
190 * <p/>This is used when {@link MatchingStrategy} returned <code>true</code> which means we're
191 * opening a different configuration of the same layout.
193 public void showEditorInput(IEditorInput editorInput) {
194 // save the current editor input.
195 doSave(new NullProgressMonitor());
197 // get the current page
198 int currentPage = getActivePage();
200 // remove the pages, except for the graphical editor, which will be dynamically adapted
202 // page after the graphical editor:
203 int count = getPageCount();
204 for (int i = count - 1 ; i > mGraphicalEditorIndex ; i--) {
207 // pages before the graphical editor
208 for (int i = mGraphicalEditorIndex - 1 ; i >= 0 ; i--) {
212 // set the current input.
213 setInputWithNotify(editorInput);
215 // re-create or reload the pages with the default page shown as the previous active page.
216 createAndroidPages();
217 selectDefaultPage(Integer.toString(currentPage));
219 // update the outline
220 if (mOutline != null && mGraphicalEditor != null) {
221 mOutline.reloadModel();
226 * Processes the new XML Model, which XML root node is given.
228 * @param xml_doc The XML document, if available, or null if none exists.
231 protected void xmlModelChanged(Document xml_doc) {
232 // init the ui root on demand
233 initUiRootNode(false /*force*/);
235 mUiRootNode.loadFromXmlNode(xml_doc);
237 // update the model first, since it is used by the viewers.
238 super.xmlModelChanged(xml_doc);
240 if (mGraphicalEditor != null) {
241 mGraphicalEditor.onXmlModelChanged();
244 if (mOutline != null) {
245 mOutline.reloadModel();
250 * Returns the IContentOutlinePage when asked for it.
252 @SuppressWarnings("unchecked")
254 public Object getAdapter(Class adapter) {
255 // for the outline, force it to come from the Graphical Editor.
256 // This fixes the case where a layout file is opened in XML view first and the outline
257 // gets stuck in the XML outline.
258 if (IContentOutlinePage.class == adapter && mGraphicalEditor != null) {
260 if (mOutline == null && mGraphicalEditor instanceof GraphicalLayoutEditor) {
261 // TODO add support for GLE2
262 mOutline = new UiContentOutlinePage(
263 (GraphicalLayoutEditor) mGraphicalEditor,
270 if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) {
271 if (mPropertyPage == null) {
272 mPropertyPage = new UiPropertySheetPage();
275 return mPropertyPage;
279 return super.getAdapter(adapter);
283 protected void pageChange(int newPageIndex) {
284 super.pageChange(newPageIndex);
286 if (mGraphicalEditor != null) {
287 if (newPageIndex == mGraphicalEditorIndex) {
288 mGraphicalEditor.activated();
290 mGraphicalEditor.deactivated();
295 // ----- IPartListener Methods ----
297 public void partActivated(IWorkbenchPart part) {
299 if (mGraphicalEditor != null) {
300 if (getActivePage() == mGraphicalEditorIndex) {
301 mGraphicalEditor.activated();
303 mGraphicalEditor.deactivated();
309 public void partBroughtToTop(IWorkbenchPart part) {
313 public void partClosed(IWorkbenchPart part) {
317 public void partDeactivated(IWorkbenchPart part) {
319 if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) {
320 mGraphicalEditor.deactivated();
325 public void partOpened(IWorkbenchPart part) {
326 EclipseUiHelper.showView(EclipseUiHelper.CONTENT_OUTLINE_VIEW_ID, false /* activate */);
327 EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, false /* activate */);
330 public class UiEditorActions extends UiActions {
333 protected UiDocumentNode getRootNode() {
337 // Select the new item
339 protected void selectUiNode(UiElementNode uiNodeToSelect) {
340 mGraphicalEditor.selectModel(uiNodeToSelect);
344 public void commitPendingXmlChanges() {
345 // Pass. There is nothing to commit before the XML is changed here.
349 public UiEditorActions getUiEditorActions() {
350 if (mUiEditorActions == null) {
351 mUiEditorActions = new UiEditorActions();
353 return mUiEditorActions;
356 // ---- Local Methods ----
359 * Returns true if the Graphics editor page is visible. This <b>must</b> be
360 * called from the UI thread.
362 public boolean isGraphicalEditorActive() {
363 IWorkbenchPartSite workbenchSite = getSite();
364 IWorkbenchPage workbenchPage = workbenchSite.getPage();
366 // check if the editor is visible in the workbench page
367 if (workbenchPage.isPartVisible(this) && workbenchPage.getActiveEditor() == this) {
368 // and then if the page of the editor is visible (not to be confused with
369 // the workbench page)
370 return mGraphicalEditorIndex == getActivePage();
377 protected void initUiRootNode(boolean force) {
378 // The root UI node is always created, even if there's no corresponding XML node.
379 if (mUiRootNode == null || force) {
380 // get the target data from the opened file (and its project)
381 AndroidTargetData data = getTargetData();
384 if (mUiRootNode != null) {
385 doc = mUiRootNode.getXmlDocument();
388 DocumentDescriptor desc;
390 desc = new DocumentDescriptor("temp", null /*children*/);
392 desc = data.getLayoutDescriptors().getDescriptor();
395 // get the descriptors from the data.
396 mUiRootNode = (UiDocumentNode) desc.createUiNode();
397 mUiRootNode.setEditor(this);
399 onDescriptorsChanged(doc);
403 private void onDescriptorsChanged(Document document) {
404 if (document != null) {
405 mUiRootNode.loadFromXmlNode(document);
407 mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlDocument());
410 if (mOutline != null) {
411 mOutline.reloadModel();
414 if (mGraphicalEditor != null) {
415 mGraphicalEditor.reloadEditor();
416 mGraphicalEditor.reloadPalette();
417 mGraphicalEditor.reloadConfigurationUi(true /*notify listener */);
418 mGraphicalEditor.recomputeLayout();
423 * Handles a new input, and update the part name.
424 * @param input the new input.
426 private void handleNewInput(IEditorInput input) {
427 if (input instanceof FileEditorInput) {
428 FileEditorInput fileInput = (FileEditorInput) input;
429 IFile file = fileInput.getFile();
430 setPartName(String.format("%1$s",