• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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