• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.gle2;
18 
19 import static com.android.SdkConstants.ANDROID_PKG;
20 import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
21 import static com.android.SdkConstants.ANDROID_URI;
22 import static com.android.SdkConstants.ATTR_CONTEXT;
23 import static com.android.SdkConstants.ATTR_ID;
24 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
25 import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
26 import static com.android.SdkConstants.FD_GEN_SOURCES;
27 import static com.android.SdkConstants.GRID_LAYOUT;
28 import static com.android.SdkConstants.SCROLL_VIEW;
29 import static com.android.SdkConstants.STRING_PREFIX;
30 import static com.android.SdkConstants.VALUE_FILL_PARENT;
31 import static com.android.SdkConstants.VALUE_MATCH_PARENT;
32 import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
33 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
34 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
35 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER;
36 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET;
37 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
38 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
39 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
40 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
41 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN;
42 
43 import com.android.SdkConstants;
44 import com.android.annotations.NonNull;
45 import com.android.annotations.Nullable;
46 import com.android.ide.common.layout.BaseLayoutRule;
47 import com.android.ide.common.rendering.LayoutLibrary;
48 import com.android.ide.common.rendering.StaticRenderSession;
49 import com.android.ide.common.rendering.api.Capability;
50 import com.android.ide.common.rendering.api.LayoutLog;
51 import com.android.ide.common.rendering.api.RenderSession;
52 import com.android.ide.common.rendering.api.ResourceValue;
53 import com.android.ide.common.rendering.api.Result;
54 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
55 import com.android.ide.common.resources.ResourceRepository;
56 import com.android.ide.common.resources.ResourceResolver;
57 import com.android.ide.common.resources.configuration.FolderConfiguration;
58 import com.android.ide.common.sdk.LoadStatus;
59 import com.android.ide.eclipse.adt.AdtConstants;
60 import com.android.ide.eclipse.adt.AdtPlugin;
61 import com.android.ide.eclipse.adt.AdtUtils;
62 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
63 import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
64 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
65 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate;
66 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
67 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
68 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
69 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
70 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
71 import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
72 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
73 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
74 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
75 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
76 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher;
77 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
78 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
79 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
80 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PaletteControl.PalettePage;
81 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
82 import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
83 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
84 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
85 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
86 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
87 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
88 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
89 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
90 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
91 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
92 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
93 import com.android.resources.Density;
94 import com.android.resources.ResourceFolderType;
95 import com.android.resources.ResourceType;
96 import com.android.sdklib.IAndroidTarget;
97 import com.android.tools.lint.detector.api.LintUtils;
98 import com.android.utils.Pair;
99 
100 import org.eclipse.core.resources.IFile;
101 import org.eclipse.core.resources.IMarker;
102 import org.eclipse.core.resources.IProject;
103 import org.eclipse.core.resources.IResource;
104 import org.eclipse.core.runtime.CoreException;
105 import org.eclipse.core.runtime.IPath;
106 import org.eclipse.core.runtime.IProgressMonitor;
107 import org.eclipse.core.runtime.IStatus;
108 import org.eclipse.core.runtime.NullProgressMonitor;
109 import org.eclipse.core.runtime.Path;
110 import org.eclipse.core.runtime.QualifiedName;
111 import org.eclipse.core.runtime.Status;
112 import org.eclipse.core.runtime.jobs.Job;
113 import org.eclipse.jdt.core.IClasspathEntry;
114 import org.eclipse.jdt.core.IJavaElement;
115 import org.eclipse.jdt.core.IJavaModelMarker;
116 import org.eclipse.jdt.core.IJavaProject;
117 import org.eclipse.jdt.core.IPackageFragment;
118 import org.eclipse.jdt.core.IPackageFragmentRoot;
119 import org.eclipse.jdt.core.JavaCore;
120 import org.eclipse.jdt.core.JavaModelException;
121 import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage;
122 import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
123 import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
124 import org.eclipse.jface.action.MenuManager;
125 import org.eclipse.jface.dialogs.MessageDialog;
126 import org.eclipse.jface.preference.IPreferenceStore;
127 import org.eclipse.jface.text.BadLocationException;
128 import org.eclipse.jface.text.IDocument;
129 import org.eclipse.jface.text.source.ISourceViewer;
130 import org.eclipse.jface.viewers.ISelection;
131 import org.eclipse.jface.viewers.ISelectionChangedListener;
132 import org.eclipse.jface.viewers.ISelectionProvider;
133 import org.eclipse.jface.viewers.SelectionChangedEvent;
134 import org.eclipse.jface.window.Window;
135 import org.eclipse.swt.SWT;
136 import org.eclipse.swt.custom.SashForm;
137 import org.eclipse.swt.custom.StyleRange;
138 import org.eclipse.swt.custom.StyledText;
139 import org.eclipse.swt.events.MouseAdapter;
140 import org.eclipse.swt.events.MouseEvent;
141 import org.eclipse.swt.graphics.Image;
142 import org.eclipse.swt.layout.GridData;
143 import org.eclipse.swt.layout.GridLayout;
144 import org.eclipse.swt.widgets.Composite;
145 import org.eclipse.swt.widgets.Control;
146 import org.eclipse.swt.widgets.Display;
147 import org.eclipse.swt.widgets.Shell;
148 import org.eclipse.text.edits.MalformedTreeException;
149 import org.eclipse.text.edits.MultiTextEdit;
150 import org.eclipse.text.edits.ReplaceEdit;
151 import org.eclipse.ui.IActionBars;
152 import org.eclipse.ui.IEditorInput;
153 import org.eclipse.ui.IEditorPart;
154 import org.eclipse.ui.IEditorSite;
155 import org.eclipse.ui.INullSelectionListener;
156 import org.eclipse.ui.ISelectionListener;
157 import org.eclipse.ui.IWorkbench;
158 import org.eclipse.ui.IWorkbenchPage;
159 import org.eclipse.ui.IWorkbenchPart;
160 import org.eclipse.ui.IWorkbenchPartSite;
161 import org.eclipse.ui.IWorkbenchWindow;
162 import org.eclipse.ui.PartInitException;
163 import org.eclipse.ui.PlatformUI;
164 import org.eclipse.ui.dialogs.PreferencesUtil;
165 import org.eclipse.ui.ide.IDE;
166 import org.eclipse.ui.part.EditorPart;
167 import org.eclipse.ui.part.FileEditorInput;
168 import org.eclipse.ui.part.IPageSite;
169 import org.eclipse.ui.part.PageBookView;
170 import org.eclipse.wb.core.controls.flyout.FlyoutControlComposite;
171 import org.eclipse.wb.core.controls.flyout.IFlyoutListener;
172 import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences;
173 import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
174 import org.w3c.dom.Element;
175 import org.w3c.dom.Node;
176 
177 import java.util.ArrayList;
178 import java.util.Collection;
179 import java.util.Collections;
180 import java.util.List;
181 import java.util.Map;
182 import java.util.Set;
183 
184 /**
185  * Graphical layout editor part, version 2.
186  * <p/>
187  * The main component of the editor part is the {@link LayoutCanvasViewer}, which
188  * actually delegates its work to the {@link LayoutCanvas} control.
189  * <p/>
190  * The {@link LayoutCanvasViewer} is set as the site's {@link ISelectionProvider}:
191  * when the selection changes in the canvas, it is thus broadcasted to anyone listening
192  * on the site's selection service.
193  * <p/>
194  * This part is also an {@link ISelectionListener}. It listens to the site's selection
195  * service and thus receives selection changes from itself as well as the associated
196  * outline and property sheet (these are registered by {@link LayoutEditorDelegate#delegateGetAdapter(Class)}).
197  *
198  * @since GLE2
199  */
200 public class GraphicalEditorPart extends EditorPart
201     implements IPageImageProvider, INullSelectionListener, IFlyoutListener,
202             ConfigurationClient {
203 
204     /*
205      * Useful notes:
206      * To understand Drag & drop:
207      *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
208      *
209      * To understand the site's selection listener, selection provider, and the
210      * confusion of different-yet-similarly-named interfaces, consult this:
211      *   http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html
212      *
213      * To summarize the selection mechanism:
214      * - The workbench site selection service can be seen as "centralized"
215      *   service that registers selection providers and selection listeners.
216      * - The editor part and the outline are selection providers.
217      * - The editor part, the outline and the property sheet are listeners
218      *   which all listen to each others indirectly.
219      */
220 
221     /** Property key for the window preferences for the structure flyout */
222     private static final String PREF_STRUCTURE = "design.structure";     //$NON-NLS-1$
223 
224     /** Property key for the window preferences for the palette flyout */
225     private static final String PREF_PALETTE = "design.palette";         //$NON-NLS-1$
226 
227     /**
228      * Session-property on files which specifies the initial config state to be used on
229      * this file
230      */
231     public final static QualifiedName NAME_INITIAL_STATE =
232         new QualifiedName(AdtPlugin.PLUGIN_ID, "initialstate");//$NON-NLS-1$
233 
234     /**
235      * Session-property on files which specifies the inclusion-context (reference to another layout
236      * which should be "including" this layout) when the file is opened
237      */
238     public final static QualifiedName NAME_INCLUDE =
239         new QualifiedName(AdtPlugin.PLUGIN_ID, "includer");//$NON-NLS-1$
240 
241     /** Reference to the layout editor */
242     private final LayoutEditorDelegate mEditorDelegate;
243 
244     /** Reference to the file being edited. Can also be used to access the {@link IProject}. */
245     private IFile mEditedFile;
246 
247     /** The configuration chooser at the top of the layout editor. */
248     private ConfigurationChooser mConfigChooser;
249 
250     /** The sash that splits the palette from the error view.
251      * The error view is shown only when needed. */
252     private SashForm mSashError;
253 
254     /** The palette displayed on the left of the sash. */
255     private PaletteControl mPalette;
256 
257     /** The layout canvas displayed to the right of the sash. */
258     private LayoutCanvasViewer mCanvasViewer;
259 
260     /** The Rules Engine associated with this editor. It is project-specific. */
261     private RulesEngine mRulesEngine;
262 
263     /** Styled text displaying the most recent error in the error view. */
264     private StyledText mErrorLabel;
265 
266     /**
267      * The resource reference to a file that should surround this file (e.g. include this file
268      * visually), or null if not applicable
269      */
270     private Reference mIncludedWithin;
271 
272     private Map<ResourceType, Map<String, ResourceValue>> mConfiguredFrameworkRes;
273     private Map<ResourceType, Map<String, ResourceValue>> mConfiguredProjectRes;
274     private ProjectCallback mProjectCallback;
275     private boolean mNeedsRecompute = false;
276     private TargetListener mTargetListener;
277     private ResourceResolver mResourceResolver;
278     private ReloadListener mReloadListener;
279     private int mMinSdkVersion;
280     private int mTargetSdkVersion;
281     private LayoutActionBar mActionBar;
282     private OutlinePage mOutlinePage;
283     private FlyoutControlComposite mStructureFlyout;
284     private FlyoutControlComposite mPaletteComposite;
285     private PropertyFactory mPropertyFactory;
286     private boolean mRenderedOnce;
287 
288     /**
289      * Flags which tracks whether this editor is currently active which is set whenever
290      * {@link #activated()} is called and clear whenever {@link #deactivated()} is called.
291      * This is used to suppress repeated calls to {@link #activate()} to avoid doing
292      * unnecessary work.
293      */
294     private boolean mActive;
295 
296     /**
297      * Constructs a new {@link GraphicalEditorPart}
298      *
299      * @param editorDelegate the associated XML editor delegate
300      */
GraphicalEditorPart(@onNull LayoutEditorDelegate editorDelegate)301     public GraphicalEditorPart(@NonNull LayoutEditorDelegate editorDelegate) {
302         mEditorDelegate = editorDelegate;
303         setPartName("Graphical Layout");
304     }
305 
306     // ------------------------------------
307     // Methods overridden from base classes
308     //------------------------------------
309 
310     /**
311      * Initializes the editor part with a site and input.
312      * {@inheritDoc}
313      */
314     @Override
init(IEditorSite site, IEditorInput input)315     public void init(IEditorSite site, IEditorInput input) throws PartInitException {
316         setSite(site);
317         useNewEditorInput(input);
318 
319         if (mTargetListener == null) {
320             mTargetListener = new TargetListener();
321             AdtPlugin.getDefault().addTargetListener(mTargetListener);
322 
323             // Trigger a check to see if the SDK needs to be reloaded (which will
324             // invoke onSdkLoaded asynchronously as needed).
325             AdtPlugin.getDefault().refreshSdk();
326         }
327     }
328 
useNewEditorInput(IEditorInput input)329     private void useNewEditorInput(IEditorInput input) throws PartInitException {
330         // The contract of init() mentions we need to fail if we can't understand the input.
331         if (!(input instanceof FileEditorInput)) {
332             throw new PartInitException("Input is not of type FileEditorInput: " +  //$NON-NLS-1$
333                     input == null ? "null" : input.toString());                     //$NON-NLS-1$
334         }
335     }
336 
337     @Override
getPageImage()338     public Image getPageImage() {
339         return IconFactory.getInstance().getIcon("editor_page_design");  //$NON-NLS-1$
340     }
341 
342     @Override
createPartControl(Composite parent)343     public void createPartControl(Composite parent) {
344 
345         Display d = parent.getDisplay();
346 
347         GridLayout gl = new GridLayout(1, false);
348         parent.setLayout(gl);
349         gl.marginHeight = gl.marginWidth = 0;
350 
351         // Check whether somebody has requested an initial state for the newly opened file.
352         // The initial state is a serialized version of the state compatible with
353         // {@link ConfigurationComposite#CONFIG_STATE}.
354         String initialState = null;
355         IFile file = mEditedFile;
356         if (file == null) {
357             IEditorInput input = mEditorDelegate.getEditor().getEditorInput();
358             if (input instanceof FileEditorInput) {
359                 file = ((FileEditorInput) input).getFile();
360             }
361         }
362 
363         if (file != null) {
364             try {
365                 initialState = (String) file.getSessionProperty(NAME_INITIAL_STATE);
366                 if (initialState != null) {
367                     // Only use once
368                     file.setSessionProperty(NAME_INITIAL_STATE, null);
369                 }
370             } catch (CoreException e) {
371                 AdtPlugin.log(e, "Can't read session property %1$s", NAME_INITIAL_STATE);
372             }
373         }
374 
375         IPreferenceStore preferenceStore = AdtPlugin.getDefault().getPreferenceStore();
376         PluginFlyoutPreferences preferences;
377         preferences = new PluginFlyoutPreferences(preferenceStore, PREF_PALETTE);
378         preferences.initializeDefaults(DOCK_WEST, STATE_OPEN, 200);
379         mPaletteComposite = new FlyoutControlComposite(parent, SWT.NONE, preferences);
380         mPaletteComposite.setTitleText("Palette");
381         mPaletteComposite.setMinWidth(100);
382         Composite paletteParent = mPaletteComposite.getFlyoutParent();
383         Composite editorParent = mPaletteComposite.getClientParent();
384         mPaletteComposite.setListener(this);
385 
386         mPaletteComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
387 
388         PageSiteComposite paletteComposite = new PageSiteComposite(paletteParent, SWT.BORDER);
389         paletteComposite.setTitleText("Palette");
390         paletteComposite.setTitleImage(IconFactory.getInstance().getIcon("palette"));
391         PalettePage decor = new PalettePage(this);
392         paletteComposite.setPage(decor);
393         mPalette = (PaletteControl) decor.getControl();
394         decor.createToolbarItems(paletteComposite.getToolBar());
395 
396         // Create the shared structure+editor area
397         preferences = new PluginFlyoutPreferences(preferenceStore, PREF_STRUCTURE);
398         preferences.initializeDefaults(DOCK_EAST, STATE_OPEN, 300);
399         mStructureFlyout = new FlyoutControlComposite(editorParent, SWT.NONE, preferences);
400         mStructureFlyout.setTitleText("Structure");
401         mStructureFlyout.setMinWidth(150);
402         mStructureFlyout.setListener(this);
403 
404         Composite layoutBarAndCanvas = new Composite(mStructureFlyout.getClientParent(), SWT.NONE);
405         GridLayout gridLayout = new GridLayout(1, false);
406         gridLayout.horizontalSpacing = 0;
407         gridLayout.verticalSpacing = 0;
408         gridLayout.marginWidth = 0;
409         gridLayout.marginHeight = 0;
410         layoutBarAndCanvas.setLayout(gridLayout);
411 
412         mConfigChooser = new ConfigurationChooser(this, layoutBarAndCanvas, initialState);
413         mConfigChooser.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
414 
415         mActionBar = new LayoutActionBar(layoutBarAndCanvas, SWT.NONE, this);
416         GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
417         mActionBar.setLayoutData(detailsData);
418         if (file != null) {
419             mActionBar.updateErrorIndicator(file);
420         }
421 
422         mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER);
423         mSashError.setLayoutData(new GridData(GridData.FILL_BOTH));
424 
425         mCanvasViewer = new LayoutCanvasViewer(mEditorDelegate, mRulesEngine, mSashError, SWT.NONE);
426         mSashError.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
427 
428         mErrorLabel = new StyledText(mSashError, SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
429         mErrorLabel.setEditable(false);
430         mErrorLabel.setBackground(d.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
431         mErrorLabel.setForeground(d.getSystemColor(SWT.COLOR_INFO_FOREGROUND));
432         mErrorLabel.addMouseListener(new ErrorLabelListener());
433 
434         mSashError.setWeights(new int[] { 80, 20 });
435         mSashError.setMaximizedControl(mCanvasViewer.getControl());
436 
437         // Create the structure views. We really should do this *lazily*, but that
438         // seems to cause a bug: property sheet won't update. Track this down later.
439         createStructureViews(mStructureFlyout.getFlyoutParent(), false);
440         showStructureViews(false, false, false);
441 
442         // Initialize the state
443         reloadPalette();
444 
445         IWorkbenchPartSite site = getSite();
446         site.setSelectionProvider(mCanvasViewer);
447         site.getPage().addSelectionListener(this);
448     }
449 
createStructureViews(Composite parent, boolean createPropertySheet)450     private void createStructureViews(Composite parent, boolean createPropertySheet) {
451         mOutlinePage = new OutlinePage(this);
452         mOutlinePage.setShowPropertySheet(createPropertySheet);
453         mOutlinePage.setShowHeader(true);
454 
455         IPageSite pageSite = new IPageSite() {
456 
457             @Override
458             public IWorkbenchPage getPage() {
459                 return getSite().getPage();
460             }
461 
462             @Override
463             public ISelectionProvider getSelectionProvider() {
464                 return getSite().getSelectionProvider();
465             }
466 
467             @Override
468             public Shell getShell() {
469                 return getSite().getShell();
470             }
471 
472             @Override
473             public IWorkbenchWindow getWorkbenchWindow() {
474                 return getSite().getWorkbenchWindow();
475             }
476 
477             @Override
478             public void setSelectionProvider(ISelectionProvider provider) {
479                 getSite().setSelectionProvider(provider);
480             }
481 
482             @Override
483             public Object getAdapter(Class adapter) {
484                 return getSite().getAdapter(adapter);
485             }
486 
487             @Override
488             public Object getService(Class api) {
489                 return getSite().getService(api);
490             }
491 
492             @Override
493             public boolean hasService(Class api) {
494                 return getSite().hasService(api);
495             }
496 
497             @Override
498             public void registerContextMenu(String menuId, MenuManager menuManager,
499                     ISelectionProvider selectionProvider) {
500             }
501 
502             @Override
503             public IActionBars getActionBars() {
504                 return null;
505             }
506         };
507         mOutlinePage.init(pageSite);
508         mOutlinePage.createControl(parent);
509         mOutlinePage.addSelectionChangedListener(new ISelectionChangedListener() {
510             @Override
511             public void selectionChanged(SelectionChangedEvent event) {
512                 getCanvasControl().getSelectionManager().setSelection(event.getSelection());
513             }
514         });
515     }
516 
517     /** Shows the embedded (within the layout editor) outline and or properties */
showStructureViews(final boolean showOutline, final boolean showProperties, final boolean updateLayout)518     void showStructureViews(final boolean showOutline, final boolean showProperties,
519             final boolean updateLayout) {
520         Display display = mConfigChooser.getDisplay();
521         if (display.getThread() != Thread.currentThread()) {
522             display.asyncExec(new Runnable() {
523                 @Override
524                 public void run() {
525                     if (!mConfigChooser.isDisposed()) {
526                         showStructureViews(showOutline, showProperties, updateLayout);
527                     }
528                 }
529 
530             });
531             return;
532         }
533 
534         boolean show = showOutline || showProperties;
535 
536         Control[] children = mStructureFlyout.getFlyoutParent().getChildren();
537         if (children.length == 0) {
538             if (show) {
539                 createStructureViews(mStructureFlyout.getFlyoutParent(), showProperties);
540             }
541             return;
542         }
543 
544         mOutlinePage.setShowPropertySheet(showProperties);
545 
546         Control control = children[0];
547         if (show != control.getVisible()) {
548             control.setVisible(show);
549             mOutlinePage.setActive(show); // disable/re-enable listeners etc
550             if (show) {
551                 ISelection selection = getCanvasControl().getSelectionManager().getSelection();
552                 mOutlinePage.selectionChanged(getEditorDelegate().getEditor(), selection);
553             }
554             if (updateLayout) {
555                 mStructureFlyout.layout();
556             }
557             // TODO: *dispose* the non-showing widgets to save memory?
558         }
559     }
560 
561     /**
562      * Returns the property factory associated with this editor
563      *
564      * @return the factory
565      */
566     @NonNull
getPropertyFactory()567     public PropertyFactory getPropertyFactory() {
568         if (mPropertyFactory == null) {
569             mPropertyFactory = new PropertyFactory(this);
570         }
571 
572         return mPropertyFactory;
573     }
574 
575     /**
576      * Invoked by {@link LayoutCanvas} to set the model (a.k.a. the root view info).
577      *
578      * @param rootViewInfo The root of the view info hierarchy. Can be null.
579      */
setModel(CanvasViewInfo rootViewInfo)580     public void setModel(CanvasViewInfo rootViewInfo) {
581         if (mOutlinePage != null) {
582             mOutlinePage.setModel(rootViewInfo);
583         }
584     }
585 
586     /**
587      * Listens to workbench selections that does NOT come from {@link LayoutEditorDelegate}
588      * (those are generated by ourselves).
589      * <p/>
590      * Selection can be null, as indicated by this class implementing
591      * {@link INullSelectionListener}.
592      */
593     @Override
selectionChanged(IWorkbenchPart part, ISelection selection)594     public void selectionChanged(IWorkbenchPart part, ISelection selection) {
595         Object delegate = part instanceof IEditorPart ?
596                 LayoutEditorDelegate.fromEditor((IEditorPart) part) : null;
597         if (delegate == null) {
598             if (part instanceof PageBookView) {
599                 PageBookView pbv = (PageBookView) part;
600                  org.eclipse.ui.part.IPage currentPage = pbv.getCurrentPage();
601                 if (currentPage instanceof OutlinePage) {
602                     LayoutCanvas canvas = getCanvasControl();
603                     if (canvas != null && canvas.getOutlinePage() != currentPage) {
604                         // The notification is not for this view; ignore
605                         // (can happen when there are multiple pages simultaneously
606                         // visible)
607                         return;
608                     }
609                 }
610             }
611             mCanvasViewer.setSelection(selection);
612         }
613     }
614 
615     @Override
dispose()616     public void dispose() {
617         getSite().getPage().removeSelectionListener(this);
618         getSite().setSelectionProvider(null);
619 
620         if (mTargetListener != null) {
621             AdtPlugin.getDefault().removeTargetListener(mTargetListener);
622             mTargetListener = null;
623         }
624 
625         if (mReloadListener != null) {
626             LayoutReloadMonitor.getMonitor().removeListener(mReloadListener);
627             mReloadListener = null;
628         }
629 
630         if (mCanvasViewer != null) {
631             mCanvasViewer.dispose();
632             mCanvasViewer = null;
633         }
634         super.dispose();
635     }
636 
637     /**
638      * Select the visual element corresponding to the given XML node
639      * @param xmlNode The Node whose element we want to select
640      */
select(Node xmlNode)641     public void select(Node xmlNode) {
642         mCanvasViewer.getCanvas().getSelectionManager().select(xmlNode);
643     }
644 
645     // ---- Implements ConfigurationClient ----
646     @Override
aboutToChange(int flags)647     public void aboutToChange(int flags) {
648         if ((flags & CFG_TARGET) != 0) {
649             IAndroidTarget oldTarget = mConfigChooser.getConfiguration().getTarget();
650             preRenderingTargetChangeCleanUp(oldTarget);
651         }
652     }
653 
654     @Override
changed(int flags)655     public boolean changed(int flags) {
656         mConfiguredFrameworkRes = mConfiguredProjectRes = null;
657         mResourceResolver = null;
658 
659         if (mEditedFile == null) {
660             return true;
661         }
662 
663         // Before doing the normal process, test for the following case.
664         // - the editor is being opened (or reset for a new input)
665         // - the file being opened is not the best match for any possible configuration
666         // - another random compatible config was chosen in the config composite.
667         // The result is that 'match' will not be the file being edited, but because this is not
668         // due to a config change, we should not trigger opening the actual best match (also,
669         // because the editor is still opening the MatchingStrategy woudln't answer true
670         // and the best match file would open in a different editor).
671         // So the solution is that if the editor is being created, we just call recomputeLayout
672         // without looking for a better matching layout file.
673         if (mEditorDelegate.getEditor().isCreatingPages()) {
674             recomputeLayout();
675         } else {
676             boolean affectsFileSelection = (flags & Configuration.MASK_FILE_ATTRS) != 0;
677             IFile best = null;
678             // get the resources of the file's project.
679             if (affectsFileSelection) {
680                 best = ConfigurationMatcher.getBestFileMatch(mConfigChooser);
681             }
682             if (best != null) {
683                 if (!best.equals(mEditedFile)) {
684                     try {
685                         // tell the editor that the next replacement file is due to a config
686                         // change.
687                         mEditorDelegate.setNewFileOnConfigChange(true);
688 
689                         boolean reuseEditor = AdtPrefs.getPrefs().isSharedLayoutEditor();
690                         if (!reuseEditor) {
691                             String data = ConfigurationDescription.getDescription(best);
692                             if (data == null) {
693                                 // Not previously opened: duplicate the current state as
694                                 // much as possible
695                                 data = mConfigChooser.getConfiguration().toPersistentString();
696                                 ConfigurationDescription.setDescription(best, data);
697                             }
698                         }
699 
700                         // ask the IDE to open the replacement file.
701                         IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), best,
702                                 CommonXmlEditor.ID);
703 
704                         // we're done!
705                         return reuseEditor;
706                     } catch (PartInitException e) {
707                         // FIXME: do something!
708                     }
709                 }
710 
711                 // at this point, we have not opened a new file.
712 
713                 // Store the state in the current file
714                 mConfigChooser.saveConstraints();
715 
716                 // Even though the layout doesn't change, the config changed, and referenced
717                 // resources need to be updated.
718                 recomputeLayout();
719             } else if (affectsFileSelection) {
720                 // display the error.
721                 Configuration configuration = mConfigChooser.getConfiguration();
722                 FolderConfiguration currentConfig = configuration.getFullConfig();
723                 displayError(
724                         "No resources match the configuration\n" +
725                         " \n" +
726                         "\t%1$s\n" +
727                         " \n" +
728                         "Change the configuration or create:\n" +
729                         " \n" +
730                         "\tres/%2$s/%3$s\n" +
731                         " \n" +
732                         "You can also click the 'Create New...' item in the configuration " +
733                         "dropdown menu above.",
734                         currentConfig.toDisplayString(),
735                         currentConfig.getFolderName(ResourceFolderType.LAYOUT),
736                         mEditedFile.getName());
737             } else {
738                 // Something else changed, such as the theme - just recompute existing
739                 // layout
740                 mConfigChooser.saveConstraints();
741                 recomputeLayout();
742             }
743         }
744 
745         if ((flags & CFG_TARGET) != 0) {
746             Configuration configuration = mConfigChooser.getConfiguration();
747             IAndroidTarget target = configuration.getTarget();
748             Sdk current = Sdk.getCurrent();
749             if (current != null) {
750                 AndroidTargetData targetData = current.getTargetData(target);
751                 updateCapabilities(targetData);
752             }
753         }
754 
755         if ((flags & (CFG_DEVICE | CFG_DEVICE_STATE)) != 0) {
756             // When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom
757             // out to fit the content, or zoom back in if we were zoomed out more from the
758             // previous view, but only up to 100% such that we never blow up pixels
759             if (mActionBar.isZoomingAllowed()) {
760                 getCanvasControl().setFitScale(true,  true /*allowZoomIn*/);
761             }
762         }
763 
764         reloadPalette();
765 
766         getCanvasControl().getPreviewManager().configurationChanged(flags);
767 
768         return true;
769     }
770 
771     @Override
setActivity(@onNull String activity)772     public void setActivity(@NonNull String activity) {
773         ManifestInfo manifest = ManifestInfo.get(mEditedFile.getProject());
774         String pkg = manifest.getPackage();
775         if (activity.startsWith(pkg) && activity.length() > pkg.length()
776                 && activity.charAt(pkg.length()) == '.') {
777             activity = activity.substring(pkg.length());
778         }
779         CommonXmlEditor editor = getEditorDelegate().getEditor();
780         Element element = editor.getUiRootNode().getXmlDocument().getDocumentElement();
781         AdtUtils.setToolsAttribute(editor,
782                 element, "Choose Activity", ATTR_CONTEXT,
783                 activity, false /*reveal*/, false /*append*/);
784     }
785 
786     /**
787      * Returns a {@link ProjectResources} for the framework resources based on the current
788      * configuration selection.
789      * @return the framework resources or null if not found.
790      */
791     @Override
792     @Nullable
getFrameworkResources()793     public ResourceRepository getFrameworkResources() {
794         return getFrameworkResources(getRenderingTarget());
795     }
796 
797     /**
798      * Returns a {@link ProjectResources} for the framework resources of a given
799      * target.
800      * @param target the target for which to return the framework resources.
801      * @return the framework resources or null if not found.
802      */
803     @Override
804     @Nullable
getFrameworkResources(@ullable IAndroidTarget target)805     public ResourceRepository getFrameworkResources(@Nullable IAndroidTarget target) {
806         if (target != null) {
807             AndroidTargetData data = Sdk.getCurrent().getTargetData(target);
808 
809             if (data != null) {
810                 return data.getFrameworkResources();
811             }
812         }
813 
814         return null;
815     }
816 
817     @Override
818     @Nullable
getProjectResources()819     public ProjectResources getProjectResources() {
820         if (mEditedFile != null) {
821             ResourceManager manager = ResourceManager.getInstance();
822             return manager.getProjectResources(mEditedFile.getProject());
823         }
824 
825         return null;
826     }
827 
828 
829     @Override
830     @NonNull
getConfiguredFrameworkResources()831     public Map<ResourceType, Map<String, ResourceValue>> getConfiguredFrameworkResources() {
832         if (mConfiguredFrameworkRes == null && mConfigChooser != null) {
833             ResourceRepository frameworkRes = getFrameworkResources();
834 
835             if (frameworkRes == null) {
836                 AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework");
837             } else {
838                 // get the framework resource values based on the current config
839                 mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(
840                         mConfigChooser.getConfiguration().getFullConfig());
841             }
842         }
843 
844         return mConfiguredFrameworkRes;
845     }
846 
847     @Override
848     @NonNull
getConfiguredProjectResources()849     public Map<ResourceType, Map<String, ResourceValue>> getConfiguredProjectResources() {
850         if (mConfiguredProjectRes == null && mConfigChooser != null) {
851             ProjectResources project = getProjectResources();
852 
853             // get the project resource values based on the current config
854             mConfiguredProjectRes = project.getConfiguredResources(
855                     mConfigChooser.getConfiguration().getFullConfig());
856         }
857 
858         return mConfiguredProjectRes;
859     }
860 
861     @Override
createConfigFile()862     public void createConfigFile() {
863         LayoutCreatorDialog dialog = new LayoutCreatorDialog(mConfigChooser.getShell(),
864                 mEditedFile.getName(), mConfigChooser.getConfiguration().getFullConfig());
865         if (dialog.open() != Window.OK) {
866             return;
867         }
868 
869         FolderConfiguration config = new FolderConfiguration();
870         dialog.getConfiguration(config);
871 
872         // Creates a new layout file from the specified {@link FolderConfiguration}.
873         CreateNewConfigJob job = new CreateNewConfigJob(this, mEditedFile, config);
874         job.schedule();
875     }
876 
877     /**
878      * Returns the resource name of the file that is including this current layout, if any
879      * (may be null)
880      *
881      * @return the resource name of an including layout, or null
882      */
883     @Override
getIncludedWithin()884     public Reference getIncludedWithin() {
885         return mIncludedWithin;
886     }
887 
888     @Override
889     @Nullable
getCanvas()890     public LayoutCanvas getCanvas() {
891         return getCanvasControl();
892     }
893 
894     /**
895      * Listens to target changed in the current project, to trigger a new layout rendering.
896      */
897     private class TargetListener implements ITargetChangeListener {
898 
899         @Override
onProjectTargetChange(IProject changedProject)900         public void onProjectTargetChange(IProject changedProject) {
901             if (changedProject != null && changedProject.equals(getProject())) {
902                 updateEditor();
903             }
904         }
905 
906         @Override
onTargetLoaded(IAndroidTarget loadedTarget)907         public void onTargetLoaded(IAndroidTarget loadedTarget) {
908             IAndroidTarget target = getRenderingTarget();
909             if (target != null && target.equals(loadedTarget)) {
910                 updateEditor();
911             }
912         }
913 
914         @Override
onSdkLoaded()915         public void onSdkLoaded() {
916             // get the current rendering target to unload it
917             IAndroidTarget oldTarget = getRenderingTarget();
918             preRenderingTargetChangeCleanUp(oldTarget);
919 
920             computeSdkVersion();
921 
922             // get the project target
923             Sdk currentSdk = Sdk.getCurrent();
924             if (currentSdk != null) {
925                 IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
926                 if (target != null) {
927                     mConfigChooser.onSdkLoaded(target);
928                     changed(CFG_FOLDER | CFG_TARGET);
929                 }
930             }
931         }
932 
updateEditor()933         private void updateEditor() {
934             mEditorDelegate.getEditor().commitPages(false /* onSave */);
935 
936             // because the target changed we must reset the configured resources.
937             mConfiguredFrameworkRes = mConfiguredProjectRes = null;
938             mResourceResolver = null;
939 
940             // make sure we remove the custom view loader, since its parent class loader is the
941             // bridge class loader.
942             mProjectCallback = null;
943 
944             // recreate the ui root node always, this will also call onTargetChange
945             // on the config composite
946             mEditorDelegate.delegateInitUiRootNode(true /*force*/);
947         }
948 
getProject()949         private IProject getProject() {
950             return getEditorDelegate().getEditor().getProject();
951         }
952     }
953 
954     /** Refresh the configured project resources associated with this editor */
refreshProjectResources()955     public void refreshProjectResources() {
956         mConfiguredProjectRes = null;
957         mResourceResolver = null;
958     }
959 
960     /**
961      * Returns the currently edited file
962      *
963      * @return the currently edited file, or null
964      */
getEditedFile()965     public IFile getEditedFile() {
966         return mEditedFile;
967     }
968 
969     /**
970      * Returns the project for the currently edited file, or null
971      *
972      * @return the project containing the edited file, or null
973      */
getProject()974     public IProject getProject() {
975         if (mEditedFile != null) {
976             return mEditedFile.getProject();
977         } else {
978             return null;
979         }
980     }
981 
982     // ----------------
983 
984     /**
985      * Save operation in the Graphical Editor Part.
986      * <p/>
987      * In our workflow, the model is owned by the Structured XML Editor.
988      * The graphical layout editor just displays it -- thus we don't really
989      * save anything here.
990      * <p/>
991      * This must NOT call the parent editor part. At the contrary, the parent editor
992      * part will call this *after* having done the actual save operation.
993      * <p/>
994      * The only action this editor must do is mark the undo command stack as
995      * being no longer dirty.
996      */
997     @Override
doSave(IProgressMonitor monitor)998     public void doSave(IProgressMonitor monitor) {
999         // TODO implement a command stack
1000 //        getCommandStack().markSaveLocation();
1001 //        firePropertyChange(PROP_DIRTY);
1002     }
1003 
1004     /**
1005      * Save operation in the Graphical Editor Part.
1006      * <p/>
1007      * In our workflow, the model is owned by the Structured XML Editor.
1008      * The graphical layout editor just displays it -- thus we don't really
1009      * save anything here.
1010      */
1011     @Override
doSaveAs()1012     public void doSaveAs() {
1013         // pass
1014     }
1015 
1016     /**
1017      * In our workflow, the model is owned by the Structured XML Editor.
1018      * The graphical layout editor just displays it -- thus we don't really
1019      * save anything here.
1020      */
1021     @Override
isDirty()1022     public boolean isDirty() {
1023         return false;
1024     }
1025 
1026     /**
1027      * In our workflow, the model is owned by the Structured XML Editor.
1028      * The graphical layout editor just displays it -- thus we don't really
1029      * save anything here.
1030      */
1031     @Override
isSaveAsAllowed()1032     public boolean isSaveAsAllowed() {
1033         return false;
1034     }
1035 
1036     @Override
setFocus()1037     public void setFocus() {
1038         // TODO Auto-generated method stub
1039 
1040     }
1041 
1042     /**
1043      * Responds to a page change that made the Graphical editor page the activated page.
1044      */
activated()1045     public void activated() {
1046         if (!mActive) {
1047             mActive = true;
1048 
1049             syncDockingState();
1050             mActionBar.updateErrorIndicator();
1051 
1052             boolean changed = mConfigChooser.syncRenderState();
1053             if (changed) {
1054                 // Will also force recomputeLayout()
1055                 return;
1056             }
1057 
1058             if (mNeedsRecompute) {
1059                 recomputeLayout();
1060             }
1061 
1062             mCanvasViewer.getCanvas().syncPreviewMode();
1063         }
1064     }
1065 
1066     /**
1067      * The global docking state version. This number is incremented each time
1068      * the user customizes the window layout in any layout.
1069      */
1070     private static int sDockingStateVersion;
1071 
1072     /**
1073      * The window docking state version that this window is currently showing;
1074      * when a different window is reconfigured, the global version number is
1075      * incremented, and when this window is shown, and the current version is
1076      * less than the global version, the window layout will be synced.
1077      */
1078     private int mDockingStateVersion;
1079 
1080     /**
1081      * Syncs the window docking state.
1082      * <p>
1083      * The layout editor lets you change the docking state -- e.g. you can minimize the
1084      * palette, and drag the structure view to the bottom, and so on. When you restart
1085      * the IDE, the window comes back up with your customized state.
1086      * <p>
1087      * <b>However</b>, when you have multiple editor files open, if you minimize the palette
1088      * in one editor and then switch to another, the other editor will have the old window
1089      * state. That's because each editor has its own set of windows.
1090      * <p>
1091      * This method fixes this. Whenever a window is shown, this method is called, and the
1092      * docking state is synced such that the editor will match the current persistent docking
1093      * state.
1094      */
syncDockingState()1095     private void syncDockingState() {
1096         if (mDockingStateVersion == sDockingStateVersion) {
1097             // No changes to apply
1098             return;
1099         }
1100         mDockingStateVersion = sDockingStateVersion;
1101 
1102         IPreferenceStore preferenceStore = AdtPlugin.getDefault().getPreferenceStore();
1103         PluginFlyoutPreferences preferences;
1104         preferences = new PluginFlyoutPreferences(preferenceStore, PREF_PALETTE);
1105         mPaletteComposite.apply(preferences);
1106         preferences = new PluginFlyoutPreferences(preferenceStore, PREF_STRUCTURE);
1107         mStructureFlyout.apply(preferences);
1108         mPaletteComposite.layout();
1109         mStructureFlyout.layout();
1110         mPaletteComposite.redraw(); // the structure view is nested within the palette
1111     }
1112 
1113     /**
1114      * Responds to a page change that made the Graphical editor page the deactivated page
1115      */
deactivated()1116     public void deactivated() {
1117         mActive = false;
1118 
1119         LayoutCanvas canvas = getCanvasControl();
1120         if (canvas != null) {
1121             canvas.deactivated();
1122         }
1123     }
1124 
1125     /**
1126      * Opens and initialize the editor with a new file.
1127      * @param file the file being edited.
1128      */
openFile(IFile file)1129     public void openFile(IFile file) {
1130         mEditedFile = file;
1131         mConfigChooser.setFile(mEditedFile);
1132 
1133         if (mReloadListener == null) {
1134             mReloadListener = new ReloadListener();
1135             LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), mReloadListener);
1136         }
1137 
1138         if (mRulesEngine == null) {
1139             mRulesEngine = new RulesEngine(this, mEditedFile.getProject());
1140             if (mCanvasViewer != null) {
1141                 mCanvasViewer.getCanvas().setRulesEngine(mRulesEngine);
1142             }
1143         }
1144 
1145         // Pick up hand-off data: somebody requesting this file to be opened may have
1146         // requested that it should be opened as included within another file
1147         if (mEditedFile != null) {
1148             try {
1149                 mIncludedWithin = (Reference) mEditedFile.getSessionProperty(NAME_INCLUDE);
1150                 if (mIncludedWithin != null) {
1151                     // Only use once
1152                     mEditedFile.setSessionProperty(NAME_INCLUDE, null);
1153                 }
1154             } catch (CoreException e) {
1155                 AdtPlugin.log(e, "Can't access session property %1$s", NAME_INCLUDE);
1156             }
1157         }
1158 
1159         computeSdkVersion();
1160     }
1161 
1162     /**
1163      * Resets the editor with a replacement file.
1164      * @param file the replacement file.
1165      */
replaceFile(IFile file)1166     public void replaceFile(IFile file) {
1167         mEditedFile = file;
1168         mConfigChooser.replaceFile(mEditedFile);
1169         computeSdkVersion();
1170     }
1171 
1172     /**
1173      * Resets the editor with a replacement file coming from a config change in the config
1174      * selector.
1175      * @param file the replacement file.
1176      */
changeFileOnNewConfig(IFile file)1177     public void changeFileOnNewConfig(IFile file) {
1178         mEditedFile = file;
1179         mConfigChooser.changeFileOnNewConfig(mEditedFile);
1180     }
1181 
1182     /**
1183      * Responds to a target change for the project of the edited file
1184      */
onTargetChange()1185     public void onTargetChange() {
1186         AndroidTargetData targetData = mConfigChooser.onXmlModelLoaded();
1187         updateCapabilities(targetData);
1188 
1189         changed(CFG_FOLDER | CFG_TARGET);
1190     }
1191 
1192     /** Updates the capabilities for the given target data (which may be null) */
updateCapabilities(AndroidTargetData targetData)1193     private void updateCapabilities(AndroidTargetData targetData) {
1194         if (targetData != null) {
1195             LayoutLibrary layoutLib = targetData.getLayoutLibrary();
1196             if (mIncludedWithin != null && !layoutLib.supports(Capability.EMBEDDED_LAYOUT)) {
1197                 showIn(null);
1198             }
1199         }
1200     }
1201 
1202     /**
1203      * Returns the {@link CommonXmlDelegate} for this editor
1204      *
1205      * @return the {@link CommonXmlDelegate} for this editor
1206      */
1207     @NonNull
getEditorDelegate()1208     public LayoutEditorDelegate getEditorDelegate() {
1209         return mEditorDelegate;
1210     }
1211 
1212     /**
1213      * Returns the {@link RulesEngine} associated with this editor
1214      *
1215      * @return the {@link RulesEngine} associated with this editor, never null
1216      */
getRulesEngine()1217     public RulesEngine getRulesEngine() {
1218         return mRulesEngine;
1219     }
1220 
1221     /**
1222      * Return the {@link LayoutCanvas} associated with this editor
1223      *
1224      * @return the associated {@link LayoutCanvas}
1225      */
getCanvasControl()1226     public LayoutCanvas getCanvasControl() {
1227         if (mCanvasViewer != null) {
1228             return mCanvasViewer.getCanvas();
1229         }
1230         return null;
1231     }
1232 
1233     /**
1234      * Returns the {@link UiDocumentNode} for the XML model edited by this editor
1235      *
1236      * @return the associated model
1237      */
getModel()1238     public UiDocumentNode getModel() {
1239         return mEditorDelegate.getUiRootNode();
1240     }
1241 
1242     /**
1243      * Callback for XML model changed. Only update/recompute the layout if the editor is visible
1244      */
onXmlModelChanged()1245     public void onXmlModelChanged() {
1246         // To optimize the rendering when the user is editing in the XML pane, we don't
1247         // refresh the editor if it's not the active part.
1248         //
1249         // This behavior is acceptable when the editor is the single "full screen" part
1250         // (as in this case active means visible.)
1251         // Unfortunately this breaks in 2 cases:
1252         // - when performing a drag'n'drop from one editor to another, the target is not
1253         //   properly refreshed before it becomes active.
1254         // - when duplicating the editor window and placing both editors side by side (xml in one
1255         //   and canvas in the other one), the canvas may not be refreshed when the XML is edited.
1256         //
1257         // TODO find a way to really query whether the pane is visible, not just active.
1258 
1259         if (mEditorDelegate.isGraphicalEditorActive()) {
1260             recomputeLayout();
1261         } else {
1262             // Remember we want to recompute as soon as the editor becomes active.
1263             mNeedsRecompute = true;
1264         }
1265     }
1266 
1267     /**
1268      * Recomputes the layout
1269      */
recomputeLayout()1270     public void recomputeLayout() {
1271         try {
1272             if (!ensureFileValid()) {
1273                 return;
1274             }
1275 
1276             UiDocumentNode model = getModel();
1277             LayoutCanvas canvas = mCanvasViewer.getCanvas();
1278             if (!ensureModelValid(model)) {
1279                 // Although we display an error, we still treat an empty document as a
1280                 // successful layout result so that we can drop new elements in it.
1281                 //
1282                 // For that purpose, create a special LayoutScene that has no image,
1283                 // no root view yet indicates success and then update the canvas with it.
1284 
1285                 canvas.setSession(
1286                         new StaticRenderSession(
1287                                 Result.Status.SUCCESS.createResult(),
1288                                 null /*rootViewInfo*/, null /*image*/),
1289                         null /*explodeNodes*/, true /* layoutlib5 */);
1290                 return;
1291             }
1292 
1293             LayoutLibrary layoutLib = getReadyLayoutLib(true /*displayError*/);
1294 
1295             if (layoutLib != null) {
1296                 // if drawing in real size, (re)set the scaling factor.
1297                 if (mActionBar.isZoomingRealSize()) {
1298                     mActionBar.computeAndSetRealScale(false /* redraw */);
1299                 }
1300 
1301                 IProject project = mEditedFile.getProject();
1302                 renderWithBridge(project, model, layoutLib);
1303 
1304                 canvas.getPreviewManager().renderPreviews();
1305             }
1306         } finally {
1307             // no matter the result, we are done doing the recompute based on the latest
1308             // resource/code change.
1309             mNeedsRecompute = false;
1310         }
1311     }
1312 
1313     /**
1314      * Reloads the palette
1315      */
reloadPalette()1316     public void reloadPalette() {
1317         if (mPalette != null) {
1318             IAndroidTarget renderingTarget = getRenderingTarget();
1319             if (renderingTarget != null) {
1320                 mPalette.reloadPalette(renderingTarget);
1321             }
1322         }
1323     }
1324 
1325     /**
1326      * Returns the {@link LayoutLibrary} associated with this editor, if it has
1327      * been initialized already. May return null if it has not been initialized (or has
1328      * not finished initializing).
1329      *
1330      * @return The {@link LayoutLibrary}, or null
1331      */
getLayoutLibrary()1332     public LayoutLibrary getLayoutLibrary() {
1333         return getReadyLayoutLib(false /*displayError*/);
1334     }
1335 
1336     /**
1337      * Returns the scale to multiply pixels in the layout coordinate space with to obtain
1338      * the corresponding dip (device independent pixel)
1339      *
1340      * @return the scale to multiple layout coordinates with to obtain the dip position
1341      */
getDipScale()1342     public float getDipScale() {
1343         float dpi = mConfigChooser.getConfiguration().getDensity().getDpiValue();
1344         return Density.DEFAULT_DENSITY / dpi;
1345     }
1346 
1347     // --- private methods ---
1348 
1349     /**
1350      * Ensure that the file associated with this editor is valid (exists and is
1351      * synchronized). Any reasons why it is not are displayed in the editor's error area.
1352      *
1353      * @return True if the editor is valid, false otherwise.
1354      */
ensureFileValid()1355     private boolean ensureFileValid() {
1356         // check that the resource exists. If the file is opened but the project is closed
1357         // or deleted for some reason (changed from outside of eclipse), then this will
1358         // return false;
1359         if (mEditedFile.exists() == false) {
1360             displayError("Resource '%1$s' does not exist.",
1361                          mEditedFile.getFullPath().toString());
1362             return false;
1363         }
1364 
1365         if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) {
1366             String message = String.format("%1$s is out of sync. Please refresh.",
1367                     mEditedFile.getName());
1368 
1369             displayError(message);
1370 
1371             // also print it in the error console.
1372             IProject iProject = mEditedFile.getProject();
1373             AdtPlugin.printErrorToConsole(iProject.getName(), message);
1374             return false;
1375         }
1376 
1377         return true;
1378     }
1379 
1380     /**
1381      * Returns a {@link LayoutLibrary} that is ready for rendering, or null if the bridge
1382      * is not available or not ready yet (due to SDK loading still being in progress etc).
1383      * If enabled, any reasons preventing the bridge from being returned are displayed to the
1384      * editor's error area.
1385      *
1386      * @param displayError whether to display the loading error or not.
1387      *
1388      * @return LayoutBridge the layout bridge for rendering this editor's scene
1389      */
getReadyLayoutLib(boolean displayError)1390     LayoutLibrary getReadyLayoutLib(boolean displayError) {
1391         Sdk currentSdk = Sdk.getCurrent();
1392         if (currentSdk != null) {
1393             IAndroidTarget target = getRenderingTarget();
1394 
1395             if (target != null) {
1396                 AndroidTargetData data = currentSdk.getTargetData(target);
1397                 if (data != null) {
1398                     LayoutLibrary layoutLib = data.getLayoutLibrary();
1399 
1400                     if (layoutLib.getStatus() == LoadStatus.LOADED) {
1401                         return layoutLib;
1402                     } else if (displayError) { // getBridge() == null
1403                         // SDK is loaded but not the layout library!
1404 
1405                         // check whether the bridge managed to load, or not
1406                         if (layoutLib.getStatus() == LoadStatus.LOADING) {
1407                             displayError("Eclipse is loading framework information and the layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.",
1408                                          mEditedFile.getName());
1409                         } else {
1410                             String message = layoutLib.getLoadMessage();
1411                             displayError("Eclipse failed to load the framework information and the layout library!" +
1412                                     message != null ? "\n" + message : "");
1413                         }
1414                     }
1415                 } else { // data == null
1416                     // It can happen that the workspace refreshes while the SDK is loading its
1417                     // data, which could trigger a redraw of the opened layout if some resources
1418                     // changed while Eclipse is closed.
1419                     // In this case data could be null, but this is not an error.
1420                     // We can just silently return, as all the opened editors are automatically
1421                     // refreshed once the SDK finishes loading.
1422                     LoadStatus targetLoadStatus = currentSdk.checkAndLoadTargetData(target, null);
1423 
1424                     // display error is asked.
1425                     if (displayError) {
1426                         String targetName = target.getName();
1427                         switch (targetLoadStatus) {
1428                             case LOADING:
1429                                 String s;
1430                                 if (currentSdk.getTarget(getProject()) == target) {
1431                                     s = String.format(
1432                                             "The project target (%1$s) is still loading.",
1433                                             targetName);
1434                                 } else {
1435                                     s = String.format(
1436                                             "The rendering target (%1$s) is still loading.",
1437                                             targetName);
1438                                 }
1439                                 s += "\nThe layout will refresh automatically once the process is finished.";
1440                                 displayError(s);
1441 
1442                                 break;
1443                             case FAILED: // known failure
1444                             case LOADED: // success but data isn't loaded?!?!
1445                                 displayError("The project target (%s) was not properly loaded.",
1446                                         targetName);
1447                                 break;
1448                         }
1449                     }
1450                 }
1451 
1452             } else if (displayError) { // target == null
1453                 displayError("The project target is not set. Right click project, choose Properties | Android.");
1454             }
1455         } else if (displayError) { // currentSdk == null
1456             displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
1457                          mEditedFile.getName());
1458         }
1459 
1460         return null;
1461     }
1462 
1463     /**
1464      * Returns the {@link IAndroidTarget} used for the rendering.
1465      * <p/>
1466      * This first looks for the rendering target setup in the config UI, and if nothing has
1467      * been setup yet, returns the target of the project.
1468      *
1469      * @return an IAndroidTarget object or null if no target is setup and the project has no
1470      * target set.
1471      *
1472      */
getRenderingTarget()1473     public IAndroidTarget getRenderingTarget() {
1474         // if the SDK is null no targets are loaded.
1475         Sdk currentSdk = Sdk.getCurrent();
1476         if (currentSdk == null) {
1477             return null;
1478         }
1479 
1480         // attempt to get a target from the configuration selector.
1481         IAndroidTarget renderingTarget = mConfigChooser.getConfiguration().getTarget();
1482         if (renderingTarget != null) {
1483             return renderingTarget;
1484         }
1485 
1486         // fall back to the project target
1487         if (mEditedFile != null) {
1488             return currentSdk.getTarget(mEditedFile.getProject());
1489         }
1490 
1491         return null;
1492     }
1493 
1494     /**
1495      * Returns whether the current rendering target supports the given capability
1496      *
1497      * @param capability the capability to be looked up
1498      * @return true if the current rendering target supports the given capability
1499      */
renderingSupports(Capability capability)1500     public boolean renderingSupports(Capability capability) {
1501         IAndroidTarget target = getRenderingTarget();
1502         if (target != null) {
1503             AndroidTargetData targetData = Sdk.getCurrent().getTargetData(target);
1504             LayoutLibrary layoutLib = targetData.getLayoutLibrary();
1505             return layoutLib.supports(capability);
1506         }
1507 
1508         return false;
1509     }
1510 
ensureModelValid(UiDocumentNode model)1511     private boolean ensureModelValid(UiDocumentNode model) {
1512         // check there is actually a model (maybe the file is empty).
1513         if (model.getUiChildren().size() == 0) {
1514             if (mEditorDelegate.getEditor().isCreatingPages()) {
1515                 displayError("Loading editor");
1516                 return false;
1517             }
1518             displayError(
1519                     "No XML content. Please add a root view or layout to your document.");
1520             return false;
1521         }
1522 
1523         return true;
1524     }
1525 
renderWithBridge(IProject iProject, UiDocumentNode model, LayoutLibrary layoutLib)1526     private void renderWithBridge(IProject iProject, UiDocumentNode model,
1527             LayoutLibrary layoutLib) {
1528         LayoutCanvas canvas = getCanvasControl();
1529         Set<UiElementNode> explodeNodes = canvas.getNodesToExplode();
1530         RenderLogger logger = new RenderLogger(mEditedFile.getName());
1531         RenderingMode renderingMode = RenderingMode.NORMAL;
1532         // FIXME set the rendering mode using ViewRule or something.
1533         List<UiElementNode> children = model.getUiChildren();
1534         if (children.size() > 0 &&
1535                 children.get(0).getDescriptor().getXmlLocalName().equals(SCROLL_VIEW)) {
1536             renderingMode = RenderingMode.V_SCROLL;
1537         }
1538 
1539         RenderSession session = RenderService.create(this)
1540             .setModel(model)
1541             .setLog(logger)
1542             .setRenderingMode(renderingMode)
1543             .setIncludedWithin(mIncludedWithin)
1544             .setNodesToExpand(explodeNodes)
1545             .createRenderSession();
1546 
1547         boolean layoutlib5 = layoutLib.supports(Capability.EMBEDDED_LAYOUT);
1548         canvas.setSession(session, explodeNodes, layoutlib5);
1549 
1550         // update the UiElementNode with the layout info.
1551         if (session != null && session.getResult().isSuccess() == false) {
1552             // An error was generated. Print it (and any other accumulated warnings)
1553             String errorMessage = session.getResult().getErrorMessage();
1554             Throwable exception = session.getResult().getException();
1555             if (exception != null && errorMessage == null) {
1556                 errorMessage = exception.toString();
1557             }
1558             if (exception != null || (errorMessage != null && errorMessage.length() > 0)) {
1559                 logger.error(null, errorMessage, exception, null /*data*/);
1560             } else if (!logger.hasProblems()) {
1561                 logger.error(null, "Unexpected error in rendering, no details given",
1562                         null /*data*/);
1563             }
1564             // These errors will be included in the log warnings which are
1565             // displayed regardless of render success status below
1566         }
1567 
1568         // We might have detected some missing classes and swapped them by a mock view,
1569         // or run into fidelity warnings or missing resources, so emit all these
1570         // warnings
1571         Set<String> missingClasses = mProjectCallback.getMissingClasses();
1572         Set<String> brokenClasses = mProjectCallback.getUninstantiatableClasses();
1573         if (logger.hasProblems()) {
1574             displayLoggerProblems(iProject, logger);
1575             displayFailingClasses(missingClasses, brokenClasses, true);
1576             displayUserStackTrace(logger, true);
1577         } else if (missingClasses.size() > 0 || brokenClasses.size() > 0) {
1578             displayFailingClasses(missingClasses, brokenClasses, false);
1579             displayUserStackTrace(logger, true);
1580         } else if (session != null) {
1581             // Nope, no missing or broken classes. Clear success, congrats!
1582             hideError();
1583 
1584             // First time this layout is opened, run lint on the file (after a delay)
1585             if (!mRenderedOnce) {
1586                 mRenderedOnce = true;
1587                 Job job = new Job("Run Lint") {
1588                     @Override
1589                     protected IStatus run(IProgressMonitor monitor) {
1590                         getEditorDelegate().delegateRunLint();
1591                         return Status.OK_STATUS;
1592                     }
1593 
1594                 };
1595                 job.setSystem(true);
1596                 job.schedule(3000); // 3 seconds
1597             }
1598 
1599             mConfigChooser.ensureInitialized();
1600         }
1601 
1602         model.refreshUi();
1603     }
1604 
1605     /**
1606      * Returns the {@link ResourceResolver} for this editor
1607      *
1608      * @return the resolver used to resolve resources for the current configuration of
1609      *         this editor, or null
1610      */
getResourceResolver()1611     public ResourceResolver getResourceResolver() {
1612         if (mResourceResolver == null) {
1613             String theme = mConfigChooser.getThemeName();
1614             if (theme == null) {
1615                 displayError("Missing theme.");
1616                 return null;
1617             }
1618             boolean isProjectTheme = mConfigChooser.getConfiguration().isProjectTheme();
1619 
1620             Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes =
1621                 getConfiguredProjectResources();
1622 
1623             // Get the framework resources
1624             Map<ResourceType, Map<String, ResourceValue>> frameworkResources =
1625                 getConfiguredFrameworkResources();
1626 
1627             if (configuredProjectRes == null) {
1628                 displayError("Missing project resources for current configuration.");
1629                 return null;
1630             }
1631 
1632             if (frameworkResources == null) {
1633                 displayError("Missing framework resources.");
1634                 return null;
1635             }
1636 
1637             mResourceResolver = ResourceResolver.create(
1638                     configuredProjectRes, frameworkResources,
1639                     theme, isProjectTheme);
1640         }
1641 
1642         return mResourceResolver;
1643     }
1644 
1645     /** Returns a project callback, and optionally resets it */
getProjectCallback(boolean reset, LayoutLibrary layoutLibrary)1646     ProjectCallback getProjectCallback(boolean reset, LayoutLibrary layoutLibrary) {
1647         // Lazily create the project callback the first time we need it
1648         if (mProjectCallback == null) {
1649             ResourceManager resManager = ResourceManager.getInstance();
1650             IProject project = getProject();
1651             ProjectResources projectRes = resManager.getProjectResources(project);
1652             mProjectCallback = new ProjectCallback(layoutLibrary, projectRes, project);
1653         } else if (reset) {
1654             // Also clears the set of missing/broken classes prior to rendering
1655             mProjectCallback.getMissingClasses().clear();
1656             mProjectCallback.getUninstantiatableClasses().clear();
1657         }
1658 
1659         return mProjectCallback;
1660     }
1661 
1662     /**
1663      * Returns the resource name of this layout, NOT including the @layout/ prefix
1664      *
1665      * @return the resource name of this layout, NOT including the @layout/ prefix
1666      */
getLayoutResourceName()1667     public String getLayoutResourceName() {
1668         return ResourceHelper.getLayoutName(mEditedFile);
1669     }
1670 
1671     /**
1672      * Cleans up when the rendering target is about to change
1673      * @param oldTarget the old rendering target.
1674      */
preRenderingTargetChangeCleanUp(IAndroidTarget oldTarget)1675     private void preRenderingTargetChangeCleanUp(IAndroidTarget oldTarget) {
1676         // first clear the caches related to this file in the old target
1677         Sdk currentSdk = Sdk.getCurrent();
1678         if (currentSdk != null) {
1679             AndroidTargetData data = currentSdk.getTargetData(oldTarget);
1680             if (data != null) {
1681                 LayoutLibrary layoutLib = data.getLayoutLibrary();
1682 
1683                 // layoutLib can never be null.
1684                 layoutLib.clearCaches(mEditedFile.getProject());
1685             }
1686         }
1687 
1688         // Also remove the ProjectCallback as it caches custom views which must be reloaded
1689         // with the classloader of the new LayoutLib. We also have to clear it out
1690         // because it stores a reference to the layout library which could have changed.
1691         mProjectCallback = null;
1692 
1693         // FIXME: get rid of the current LayoutScene if any.
1694     }
1695 
1696     private class ReloadListener implements ILayoutReloadListener {
1697         /**
1698          * Called when the file changes triggered a redraw of the layout
1699          */
1700         @Override
reloadLayout(final ChangeFlags flags, final boolean libraryChanged)1701         public void reloadLayout(final ChangeFlags flags, final boolean libraryChanged) {
1702             if (mConfigChooser.isDisposed()) {
1703                 return;
1704             }
1705             Display display = mConfigChooser.getDisplay();
1706             display.asyncExec(new Runnable() {
1707                 @Override
1708                 public void run() {
1709                     reloadLayoutSwt(flags, libraryChanged);
1710                 }
1711             });
1712         }
1713 
1714         /** Reload layout. <b>Must be called on the SWT thread</b> */
reloadLayoutSwt(ChangeFlags flags, boolean libraryChanged)1715         private void reloadLayoutSwt(ChangeFlags flags, boolean libraryChanged) {
1716             if (mConfigChooser.isDisposed()) {
1717                 return;
1718             }
1719             assert mConfigChooser.getDisplay().getThread() == Thread.currentThread();
1720 
1721             boolean recompute = false;
1722             // we only care about the r class of the main project.
1723             if (flags.rClass && libraryChanged == false) {
1724                 recompute = true;
1725                 if (mEditedFile != null) {
1726                     ResourceManager manager = ResourceManager.getInstance();
1727                     ProjectResources projectRes = manager.getProjectResources(
1728                             mEditedFile.getProject());
1729 
1730                     if (projectRes != null) {
1731                         projectRes.resetDynamicIds();
1732                     }
1733                 }
1734             }
1735 
1736             if (flags.localeList) {
1737                 // the locale list *potentially* changed so we update the locale in the
1738                 // config composite.
1739                 // However there's no recompute, as it could not be needed
1740                 // (for instance a new layout)
1741                 // If a resource that's not a layout changed this will trigger a recompute anyway.
1742                 mConfigChooser.updateLocales();
1743             }
1744 
1745             // if a resources was modified.
1746             if (flags.resources) {
1747                 recompute = true;
1748 
1749                 // TODO: differentiate between single and multi resource file changed, and whether
1750                 // the resource change affects the cache.
1751 
1752                 // force a reparse in case a value XML file changed.
1753                 mConfiguredProjectRes = null;
1754                 mResourceResolver = null;
1755 
1756                 // clear the cache in the bridge in case a bitmap/9-patch changed.
1757                 LayoutLibrary layoutLib = getReadyLayoutLib(true /*displayError*/);
1758                 if (layoutLib != null) {
1759                     layoutLib.clearCaches(mEditedFile.getProject());
1760                 }
1761             }
1762 
1763             if (flags.code) {
1764                 // only recompute if the custom view loader was used to load some code.
1765                 if (mProjectCallback != null && mProjectCallback.isUsed()) {
1766                     mProjectCallback = null;
1767                     recompute = true;
1768                 }
1769             }
1770 
1771             if (flags.manifest) {
1772                 recompute |= computeSdkVersion();
1773             }
1774 
1775             if (recompute) {
1776                 if (mEditorDelegate.isGraphicalEditorActive()) {
1777                     recomputeLayout();
1778                 } else {
1779                     mNeedsRecompute = true;
1780                 }
1781             }
1782         }
1783     }
1784 
1785     // ---- Error handling ----
1786 
1787     /**
1788      * Switches the sash to display the error label.
1789      *
1790      * @param errorFormat The new error to display if not null.
1791      * @param parameters String.format parameters for the error format.
1792      */
displayError(String errorFormat, Object...parameters)1793     private void displayError(String errorFormat, Object...parameters) {
1794         if (errorFormat != null) {
1795             mErrorLabel.setText(String.format(errorFormat, parameters));
1796         } else {
1797             mErrorLabel.setText("");
1798         }
1799         mSashError.setMaximizedControl(null);
1800     }
1801 
1802     /** Displays the canvas and hides the error label. */
hideError()1803     private void hideError() {
1804         mErrorLabel.setText("");
1805         mSashError.setMaximizedControl(mCanvasViewer.getControl());
1806     }
1807 
1808     /** Display the problem list encountered during a render */
displayUserStackTrace(RenderLogger logger, boolean append)1809     private void displayUserStackTrace(RenderLogger logger, boolean append) {
1810         List<Throwable> throwables = logger.getFirstTrace();
1811         if (throwables == null || throwables.isEmpty()) {
1812             return;
1813         }
1814 
1815         Throwable throwable = throwables.get(0);
1816         StackTraceElement[] frames = throwable.getStackTrace();
1817         int end = -1;
1818         boolean haveInterestingFrame = false;
1819         for (int i = 0; i < frames.length; i++) {
1820             StackTraceElement frame = frames[i];
1821             if (isInterestingFrame(frame)) {
1822                 haveInterestingFrame = true;
1823             }
1824             String className = frame.getClassName();
1825             if (className.equals(
1826                     "com.android.layoutlib.bridge.impl.RenderSessionImpl")) { //$NON-NLS-1$
1827                 end = i;
1828                 break;
1829             }
1830         }
1831 
1832         if (end == -1 || !haveInterestingFrame) {
1833             // Not a recognized stack trace range: just skip it
1834             return;
1835         }
1836 
1837         if (!append) {
1838             mErrorLabel.setText("\n");    //$NON-NLS-1$
1839         } else {
1840             addText(mErrorLabel, "\n\n"); //$NON-NLS-1$
1841         }
1842 
1843         addText(mErrorLabel, throwable.toString() + '\n');
1844         for (int i = 0; i < end; i++) {
1845             StackTraceElement frame = frames[i];
1846             String className = frame.getClassName();
1847             String methodName = frame.getMethodName();
1848             addText(mErrorLabel, "    at " + className + '.' + methodName + '(');
1849             String fileName = frame.getFileName();
1850             if (fileName != null && !fileName.isEmpty()) {
1851                 int lineNumber = frame.getLineNumber();
1852                 String location = fileName + ':' + lineNumber;
1853                 if (isInterestingFrame(frame)) {
1854                     addActionLink(mErrorLabel, ActionLinkStyleRange.LINK_OPEN_LINE,
1855                             location, className, methodName, fileName, lineNumber);
1856                 } else {
1857                     addText(mErrorLabel, location);
1858                 }
1859                 addText(mErrorLabel, ")\n"); //$NON-NLS-1$
1860             }
1861         }
1862     }
1863 
isInterestingFrame(StackTraceElement frame)1864     private static boolean isInterestingFrame(StackTraceElement frame) {
1865         String className = frame.getClassName();
1866         return !(className.startsWith("android.")         //$NON-NLS-1$
1867                 || className.startsWith("com.android.")   //$NON-NLS-1$
1868                 || className.startsWith("java.")          //$NON-NLS-1$
1869                 || className.startsWith("javax.")         //$NON-NLS-1$
1870                 || className.startsWith("sun."));         //$NON-NLS-1$
1871     }
1872 
1873     /**
1874      * Switches the sash to display the error label to show a list of
1875      * missing classes and give options to create them.
1876      */
displayFailingClasses(Set<String> missingClasses, Set<String> brokenClasses, boolean append)1877     private void displayFailingClasses(Set<String> missingClasses, Set<String> brokenClasses,
1878             boolean append) {
1879         if (missingClasses.size() == 0 && brokenClasses.size() == 0) {
1880             return;
1881         }
1882 
1883         if (!append) {
1884             mErrorLabel.setText("");    //$NON-NLS-1$
1885         } else {
1886             addText(mErrorLabel, "\n"); //$NON-NLS-1$
1887         }
1888 
1889         if (missingClasses.size() > 0) {
1890             addText(mErrorLabel, "The following classes could not be found:\n");
1891             for (String clazz : missingClasses) {
1892                 addText(mErrorLabel, "- ");
1893                 addText(mErrorLabel, clazz);
1894                 addText(mErrorLabel, " (");
1895 
1896                 IProject project = getProject();
1897                 Collection<String> customViews = getCustomViewClassNames(project);
1898                 addTypoSuggestions(clazz, customViews, false);
1899                 addTypoSuggestions(clazz, customViews, true);
1900                 addTypoSuggestions(clazz, getAndroidViewClassNames(project), false);
1901 
1902                 addActionLink(mErrorLabel,
1903                         ActionLinkStyleRange.LINK_FIX_BUILD_PATH, "Fix Build Path", clazz);
1904                 addText(mErrorLabel, ", ");
1905                 addActionLink(mErrorLabel,
1906                         ActionLinkStyleRange.LINK_EDIT_XML, "Edit XML", clazz);
1907                 if (clazz.indexOf('.') != -1) {
1908                     // Add "Create Class" link, but only for custom views
1909                     addText(mErrorLabel, ", ");
1910                     addActionLink(mErrorLabel,
1911                             ActionLinkStyleRange.LINK_CREATE_CLASS, "Create Class", clazz);
1912                 }
1913                 addText(mErrorLabel, ")\n");
1914             }
1915         }
1916         if (brokenClasses.size() > 0) {
1917             addText(mErrorLabel, "The following classes could not be instantiated:\n");
1918 
1919             // Do we have a custom class (not an Android or add-ons class)
1920             boolean haveCustomClass = false;
1921 
1922             for (String clazz : brokenClasses) {
1923                 addText(mErrorLabel, "- ");
1924                 addText(mErrorLabel, clazz);
1925                 addText(mErrorLabel, " (");
1926                 addActionLink(mErrorLabel,
1927                         ActionLinkStyleRange.LINK_OPEN_CLASS, "Open Class", clazz);
1928                 addText(mErrorLabel, ", ");
1929                 addActionLink(mErrorLabel,
1930                         ActionLinkStyleRange.LINK_SHOW_LOG, "Show Error Log", clazz);
1931                 addText(mErrorLabel, ")\n");
1932 
1933                 if (!(clazz.startsWith("android.") || //$NON-NLS-1$
1934                         clazz.startsWith("com.google."))) { //$NON-NLS-1$
1935                     haveCustomClass = true;
1936                 }
1937             }
1938 
1939             addText(mErrorLabel, "See the Error Log (Window > Show View) for more details.\n");
1940 
1941             if (haveCustomClass) {
1942                 addBoldText(mErrorLabel, "Tip: Use View.isInEditMode() in your custom views "
1943                         + "to skip code when shown in Eclipse");
1944             }
1945         }
1946 
1947         mSashError.setMaximizedControl(null);
1948     }
1949 
addTypoSuggestions(String actual, Collection<String> views, boolean compareWithPackage)1950     private void addTypoSuggestions(String actual, Collection<String> views,
1951             boolean compareWithPackage) {
1952         if (views.size() == 0) {
1953             return;
1954         }
1955 
1956         // Look for typos and try to match with custom views and android views
1957         String actualBase = actual.substring(actual.lastIndexOf('.') + 1);
1958         int maxDistance = actualBase.length() >= 4 ? 2 : 1;
1959 
1960         if (views.size() > 0) {
1961             for (String suggested : views) {
1962                 String suggestedBase = suggested.substring(suggested.lastIndexOf('.') + 1);
1963 
1964                 String matchWith = compareWithPackage ? suggested : suggestedBase;
1965                 if (Math.abs(actualBase.length() - matchWith.length()) > maxDistance) {
1966                     // The string lengths differ more than the allowed edit distance;
1967                     // no point in even attempting to compute the edit distance (requires
1968                     // O(n*m) storage and O(n*m) speed, where n and m are the string lengths)
1969                     continue;
1970                 }
1971                 if (LintUtils.editDistance(actualBase, matchWith) <= maxDistance) {
1972                     // Suggest this class as a typo for the given class
1973                     String labelClass = (suggestedBase.equals(actual) || actual.indexOf('.') != -1)
1974                         ? suggested : suggestedBase;
1975                     addActionLink(mErrorLabel,
1976                             ActionLinkStyleRange.LINK_CHANGE_CLASS_TO,
1977                             String.format("Change to %1$s",
1978                                     // Only show full package name if class name
1979                                     // is the same
1980                                     labelClass),
1981                             actual,
1982                             viewNeedsPackage(suggested) ? suggested : suggestedBase);
1983                     addText(mErrorLabel, ", ");
1984                 }
1985             }
1986         }
1987     }
1988 
getCustomViewClassNames(IProject project)1989     private static Collection<String> getCustomViewClassNames(IProject project) {
1990         CustomViewFinder finder = CustomViewFinder.get(project);
1991         Collection<String> views = finder.getAllViews();
1992         if (views == null) {
1993             finder.refresh();
1994             views = finder.getAllViews();
1995         }
1996 
1997         return views;
1998     }
1999 
getAndroidViewClassNames(IProject project)2000     private static Collection<String> getAndroidViewClassNames(IProject project) {
2001         Sdk currentSdk = Sdk.getCurrent();
2002         IAndroidTarget target = currentSdk.getTarget(project);
2003         if (target != null) {
2004             AndroidTargetData targetData = currentSdk.getTargetData(target);
2005             LayoutDescriptors layoutDescriptors = targetData.getLayoutDescriptors();
2006             return layoutDescriptors.getAllViewClassNames();
2007         }
2008 
2009         return Collections.emptyList();
2010     }
2011 
2012     /** Add a normal line of text to the styled text widget. */
addText(StyledText styledText, String...string)2013     private void addText(StyledText styledText, String...string) {
2014         for (String s : string) {
2015             styledText.append(s);
2016         }
2017     }
2018 
2019     /** Display the problem list encountered during a render */
displayLoggerProblems(IProject project, RenderLogger logger)2020     private void displayLoggerProblems(IProject project, RenderLogger logger) {
2021         if (logger.hasProblems()) {
2022             mErrorLabel.setText("");
2023             // A common source of problems is attempting to open a layout when there are
2024             // compilation errors. In this case, may not have run (or may not be up to date)
2025             // so resources cannot be looked up etc. Explain this situation to the user.
2026 
2027             boolean hasAaptErrors = false;
2028             boolean hasJavaErrors = false;
2029             try {
2030                 IMarker[] markers;
2031                 markers = project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
2032                 if (markers.length > 0) {
2033                     for (IMarker marker : markers) {
2034                         String markerType = marker.getType();
2035                         if (markerType.equals(IJavaModelMarker.JAVA_MODEL_PROBLEM_MARKER)) {
2036                             int severity = marker.getAttribute(IMarker.SEVERITY, -1);
2037                             if (severity == IMarker.SEVERITY_ERROR) {
2038                                 hasJavaErrors = true;
2039                             }
2040                         } else if (markerType.equals(AdtConstants.MARKER_AAPT_COMPILE)) {
2041                             int severity = marker.getAttribute(IMarker.SEVERITY, -1);
2042                             if (severity == IMarker.SEVERITY_ERROR) {
2043                                 hasAaptErrors = true;
2044                             }
2045                         }
2046                     }
2047                 }
2048             } catch (CoreException e) {
2049                 AdtPlugin.log(e, null);
2050             }
2051 
2052             if (logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR)) {
2053                 addBoldText(mErrorLabel,
2054                         "Missing styles. Is the correct theme chosen for this layout?\n");
2055                 addText(mErrorLabel,
2056                         "Use the Theme combo box above the layout to choose a different layout, " +
2057                         "or fix the theme style references.\n\n");
2058             }
2059 
2060             List<Throwable> trace = logger.getFirstTrace();
2061             if (trace != null
2062                     && trace.toString().contains(
2063                             "java.lang.IndexOutOfBoundsException: Index: 2, Size: 2") //$NON-NLS-1$
2064                     && mConfigChooser.getConfiguration().getDensity() == Density.TV) {
2065                 addBoldText(mErrorLabel,
2066                         "It looks like you are using a render target where the layout library " +
2067                         "does not support the tvdpi density.\n\n");
2068                 addText(mErrorLabel, "Please try either updating to " +
2069                         "the latest available version (using the SDK manager), or if no updated " +
2070                         "version is available for this specific version of Android, try using " +
2071                         "a more recent render target version.\n\n");
2072 
2073             }
2074 
2075             if (hasAaptErrors && logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_PREFIX)) {
2076                 // Text will automatically be wrapped by the error widget so no reason
2077                 // to insert linebreaks in this error message:
2078                 String message =
2079                     "NOTE: This project contains resource errors, so aapt did not succeed, "
2080                      + "which can cause rendering failures. "
2081                      + "Fix resource problems first.\n\n";
2082                  addBoldText(mErrorLabel, message);
2083             } else if (hasJavaErrors && mProjectCallback != null && mProjectCallback.isUsed()) {
2084                 // Text will automatically be wrapped by the error widget so no reason
2085                 // to insert linebreaks in this error message:
2086                 String message =
2087                    "NOTE: This project contains Java compilation errors, "
2088                     + "which can cause rendering failures for custom views. "
2089                     + "Fix compilation problems first.\n\n";
2090                 addBoldText(mErrorLabel, message);
2091             }
2092 
2093             if (logger.seenTag(RenderLogger.TAG_MISSING_DIMENSION)) {
2094                 List<UiElementNode> elements = UiDocumentNode.getAllElements(getModel());
2095                 for (UiElementNode element : elements) {
2096                     String width = element.getAttributeValue(ATTR_LAYOUT_WIDTH);
2097                     if (width == null || width.length() == 0) {
2098                         addSetAttributeLink(element, ATTR_LAYOUT_WIDTH);
2099                     }
2100 
2101                     String height = element.getAttributeValue(ATTR_LAYOUT_HEIGHT);
2102                     if (height == null || height.length() == 0) {
2103                         addSetAttributeLink(element, ATTR_LAYOUT_HEIGHT);
2104                     }
2105                 }
2106             }
2107 
2108             String problems = logger.getProblems(false /*includeFidelityWarnings*/);
2109             addText(mErrorLabel, problems);
2110 
2111             List<String> fidelityWarnings = logger.getFidelityWarnings();
2112             if (fidelityWarnings != null && fidelityWarnings.size() > 0) {
2113                 addText(mErrorLabel,
2114                         "The graphics preview in the layout editor may not be accurate:\n");
2115                 for (String warning : fidelityWarnings) {
2116                     addText(mErrorLabel, warning + ' ');
2117                     addActionLink(mErrorLabel,
2118                             ActionLinkStyleRange.IGNORE_FIDELITY_WARNING,
2119                             "(Ignore for this session)\n", warning);
2120                 }
2121             }
2122 
2123             mSashError.setMaximizedControl(null);
2124         } else {
2125             mSashError.setMaximizedControl(mCanvasViewer.getControl());
2126         }
2127     }
2128 
2129     /** Appends an action link to set the given attribute on the given value */
addSetAttributeLink(UiElementNode element, String attribute)2130     private void addSetAttributeLink(UiElementNode element, String attribute) {
2131         if (element.getXmlNode().getNodeName().equals(GRID_LAYOUT)) {
2132             // GridLayout does not require a layout_width or layout_height to be defined
2133             return;
2134         }
2135 
2136         String fill = VALUE_FILL_PARENT;
2137         // See whether we should offer match_parent instead of fill_parent
2138         Sdk currentSdk = Sdk.getCurrent();
2139         if (currentSdk != null) {
2140             IAndroidTarget target = currentSdk.getTarget(getProject());
2141             if (target.getVersion().getApiLevel() >= 8) {
2142                 fill = VALUE_MATCH_PARENT;
2143             }
2144         }
2145 
2146         String id = element.getAttributeValue(ATTR_ID);
2147         if (id == null || id.length() == 0) {
2148             id = '<' + element.getXmlNode().getNodeName() + '>';
2149         } else {
2150             id = BaseLayoutRule.stripIdPrefix(id);
2151         }
2152 
2153         addText(mErrorLabel, String.format("\"%1$s\" does not set the required %2$s attribute:\n",
2154                 id, attribute));
2155         addText(mErrorLabel, " (1) ");
2156         addActionLink(mErrorLabel,
2157                 ActionLinkStyleRange.SET_ATTRIBUTE,
2158                 String.format("Set to \"%1$s\"", VALUE_WRAP_CONTENT),
2159                 element, attribute, VALUE_WRAP_CONTENT);
2160         addText(mErrorLabel, "\n (2) ");
2161         addActionLink(mErrorLabel,
2162                 ActionLinkStyleRange.SET_ATTRIBUTE,
2163                 String.format("Set to \"%1$s\"\n", fill),
2164                 element, attribute, fill);
2165     }
2166 
2167     /** Appends the given text as a bold string in the given text widget */
addBoldText(StyledText styledText, String text)2168     private void addBoldText(StyledText styledText, String text) {
2169         String s = styledText.getText();
2170         int start = (s == null ? 0 : s.length());
2171 
2172         styledText.append(text);
2173         StyleRange sr = new StyleRange();
2174         sr.start = start;
2175         sr.length = text.length();
2176         sr.fontStyle = SWT.BOLD;
2177         styledText.setStyleRange(sr);
2178     }
2179 
2180     /**
2181      * Add a URL-looking link to the styled text widget.
2182      * <p/>
2183      * A mouse-click listener is setup and it interprets the link based on the
2184      * action, corresponding to the value fields in {@link ActionLinkStyleRange}.
2185      */
addActionLink(StyledText styledText, int action, String label, Object... data)2186     private void addActionLink(StyledText styledText, int action, String label,
2187             Object... data) {
2188         String s = styledText.getText();
2189         int start = (s == null ? 0 : s.length());
2190         styledText.append(label);
2191 
2192         StyleRange sr = new ActionLinkStyleRange(action, data);
2193         sr.start = start;
2194         sr.length = label.length();
2195         sr.fontStyle = SWT.NORMAL;
2196         sr.underlineStyle = SWT.UNDERLINE_LINK;
2197         sr.underline = true;
2198         styledText.setStyleRange(sr);
2199     }
2200 
2201     /**
2202      * Looks up the resource file corresponding to the given type
2203      *
2204      * @param type The type of resource to look up, such as {@link ResourceType#LAYOUT}
2205      * @param name The name of the resource (not including ".xml")
2206      * @param isFrameworkResource if true, the resource is a framework resource, otherwise
2207      *            it's a project resource
2208      * @return the resource file defining the named resource, or null if not found
2209      */
findResourceFile(ResourceType type, String name, boolean isFrameworkResource)2210     public IPath findResourceFile(ResourceType type, String name, boolean isFrameworkResource) {
2211         // FIXME: This code does not handle theme value resolution.
2212         // There is code to handle this, but it's in layoutlib; we should
2213         // expose that and use it here.
2214 
2215         Map<ResourceType, Map<String, ResourceValue>> map;
2216         map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes;
2217         if (map == null) {
2218             // Not yet configured
2219             return null;
2220         }
2221 
2222         Map<String, ResourceValue> layoutMap = map.get(type);
2223         if (layoutMap != null) {
2224             ResourceValue value = layoutMap.get(name);
2225             if (value != null) {
2226                 String valueStr = value.getValue();
2227                 if (valueStr.startsWith("?")) { //$NON-NLS-1$
2228                     // FIXME: It's a reference. We should resolve this properly.
2229                     return null;
2230                 }
2231                 return new Path(valueStr);
2232             }
2233         }
2234 
2235         return null;
2236     }
2237 
2238     /**
2239      * Looks up the path to the file corresponding to the given attribute value, such as
2240      * @layout/foo, which will return the foo.xml file in res/layout/. (The general format
2241      * of the resource url is {@literal @[<package_name>:]<resource_type>/<resource_name>}.
2242      *
2243      * @param url the attribute url
2244      * @return the path to the file defining this attribute, or null if not found
2245      */
findResourceFile(String url)2246     public IPath findResourceFile(String url) {
2247         if (!url.startsWith("@")) { //$NON-NLS-1$
2248             return null;
2249         }
2250         int typeEnd = url.indexOf('/', 1);
2251         if (typeEnd == -1) {
2252             return null;
2253         }
2254         int nameBegin = typeEnd + 1;
2255         int typeBegin = 1;
2256         int colon = url.lastIndexOf(':', typeEnd);
2257         boolean isFrameworkResource = false;
2258         if (colon != -1) {
2259             // The URL contains a package name.
2260             // While the url format technically allows other package names,
2261             // the platform apparently only supports @android for now (or if it does,
2262             // there are no usages in the current code base so this is not common).
2263             String packageName = url.substring(typeBegin, colon);
2264             if (ANDROID_PKG.equals(packageName)) {
2265                 isFrameworkResource = true;
2266             }
2267 
2268             typeBegin = colon + 1;
2269         }
2270 
2271         String typeName = url.substring(typeBegin, typeEnd);
2272         ResourceType type = ResourceType.getEnum(typeName);
2273         if (type == null) {
2274             return null;
2275         }
2276 
2277         String name = url.substring(nameBegin);
2278         return findResourceFile(type, name, isFrameworkResource);
2279     }
2280 
2281     /**
2282      * Resolve the given @string reference into a literal String using the current project
2283      * configuration
2284      *
2285      * @param text the text resource reference to resolve
2286      * @return the resolved string, or null
2287      */
findString(String text)2288     public String findString(String text) {
2289         if (text.startsWith(STRING_PREFIX)) {
2290             return findString(text.substring(STRING_PREFIX.length()), false);
2291         } else if (text.startsWith(ANDROID_STRING_PREFIX)) {
2292             return findString(text.substring(ANDROID_STRING_PREFIX.length()), true);
2293         } else {
2294             return text;
2295         }
2296     }
2297 
findString(String name, boolean isFrameworkResource)2298     private String findString(String name, boolean isFrameworkResource) {
2299         Map<ResourceType, Map<String, ResourceValue>> map;
2300         map = isFrameworkResource ? mConfiguredFrameworkRes : mConfiguredProjectRes;
2301         if (map == null) {
2302             // Not yet configured
2303             return null;
2304         }
2305 
2306         Map<String, ResourceValue> layoutMap = map.get(ResourceType.STRING);
2307         if (layoutMap != null) {
2308             ResourceValue value = layoutMap.get(name);
2309             if (value != null) {
2310                 // FIXME: This code does not handle theme value resolution.
2311                 // There is code to handle this, but it's in layoutlib; we should
2312                 // expose that and use it here.
2313                 return value.getValue();
2314             }
2315         }
2316 
2317         return null;
2318     }
2319 
2320     /**
2321      * This StyleRange represents a clickable link in the render output, where various
2322      * actions can be taken such as creating a class, opening the project chooser to
2323      * adjust the build path, etc.
2324      */
2325     private class ActionLinkStyleRange extends StyleRange {
2326         /** Create a view class */
2327         private static final int LINK_CREATE_CLASS = 1;
2328         /** Edit the build path for the current project */
2329         private static final int LINK_FIX_BUILD_PATH = 2;
2330         /** Show the XML tab */
2331         private static final int LINK_EDIT_XML = 3;
2332         /** Open the given class */
2333         private static final int LINK_OPEN_CLASS = 4;
2334         /** Show the error log */
2335         private static final int LINK_SHOW_LOG = 5;
2336         /** Change the class reference to the given fully qualified name */
2337         private static final int LINK_CHANGE_CLASS_TO = 6;
2338         /** Ignore the given fidelity warning */
2339         private static final int IGNORE_FIDELITY_WARNING = 7;
2340         /** Set an attribute on the given XML element to a given value  */
2341         private static final int SET_ATTRIBUTE = 8;
2342         /** Open the given file and line number */
2343         private static final int LINK_OPEN_LINE = 9;
2344 
2345         /** Client data: the contents depend on the specific action */
2346         private final Object[] mData;
2347         /** The action to be taken when the link is clicked */
2348         private final int mAction;
2349 
ActionLinkStyleRange(int action, Object... data)2350         private ActionLinkStyleRange(int action, Object... data) {
2351             super();
2352             mAction = action;
2353             mData = data;
2354         }
2355 
2356         /** Performs the click action */
onClick()2357         public void onClick() {
2358             switch (mAction) {
2359                 case LINK_CREATE_CLASS:
2360                     createNewClass((String) mData[0]);
2361                     break;
2362                 case LINK_EDIT_XML:
2363                     mEditorDelegate.getEditor().setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID);
2364                     break;
2365                 case LINK_FIX_BUILD_PATH:
2366                     @SuppressWarnings("restriction")
2367                     String id = BuildPathsPropertyPage.PROP_ID;
2368                     PreferencesUtil.createPropertyDialogOn(
2369                             AdtPlugin.getShell(),
2370                             getProject(), id, null, null).open();
2371                     break;
2372                 case LINK_OPEN_CLASS:
2373                     AdtPlugin.openJavaClass(getProject(), (String) mData[0]);
2374                     break;
2375                 case LINK_OPEN_LINE:
2376                     boolean success = AdtPlugin.openStackTraceLine(
2377                             (String) mData[0],   // class
2378                             (String) mData[1],   // method
2379                             (String) mData[2],   // file
2380                             (Integer) mData[3]); // line
2381                     if (!success) {
2382                         MessageDialog.openError(mErrorLabel.getShell(), "Not Found",
2383                                 String.format("Could not find %1$s.%2$s", mData[0], mData[1]));
2384                     }
2385                     break;
2386                 case LINK_SHOW_LOG:
2387                     IWorkbench workbench = PlatformUI.getWorkbench();
2388                     IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
2389                     try {
2390                         IWorkbenchPage page = workbenchWindow.getActivePage();
2391                         page.showView("org.eclipse.pde.runtime.LogView"); //$NON-NLS-1$
2392                     } catch (PartInitException e) {
2393                         AdtPlugin.log(e, null);
2394                     }
2395                     break;
2396                 case LINK_CHANGE_CLASS_TO:
2397                     // Change class reference of mData[0] to mData[1]
2398                     // TODO: run under undo lock
2399                     MultiTextEdit edits = new MultiTextEdit();
2400                     ISourceViewer textViewer =
2401                         mEditorDelegate.getEditor().getStructuredSourceViewer();
2402                     IDocument document = textViewer.getDocument();
2403                     String xml = document.get();
2404                     int index = 0;
2405                     // Replace <old with <new and </old with </new
2406                     String prefix = "<"; //$NON-NLS-1$
2407                     String find = prefix + mData[0];
2408                     String replaceWith = prefix + mData[1];
2409                     while (true) {
2410                         index = xml.indexOf(find, index);
2411                         if (index == -1) {
2412                             break;
2413                         }
2414                         edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
2415                         index += find.length();
2416                     }
2417                     index = 0;
2418                     prefix = "</"; //$NON-NLS-1$
2419                     find = prefix + mData[0];
2420                     replaceWith = prefix + mData[1];
2421                     while (true) {
2422                         index = xml.indexOf(find, index);
2423                         if (index == -1) {
2424                             break;
2425                         }
2426                         edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
2427                         index += find.length();
2428                     }
2429                     // Handle <view class="old">
2430                     index = 0;
2431                     prefix = "\""; //$NON-NLS-1$
2432                     String suffix = "\""; //$NON-NLS-1$
2433                     find = prefix + mData[0] + suffix;
2434                     replaceWith = prefix + mData[1] + suffix;
2435                     while (true) {
2436                         index = xml.indexOf(find, index);
2437                         if (index == -1) {
2438                             break;
2439                         }
2440                         edits.addChild(new ReplaceEdit(index, find.length(), replaceWith));
2441                         index += find.length();
2442                     }
2443                     try {
2444                         edits.apply(document);
2445                     } catch (MalformedTreeException e) {
2446                         AdtPlugin.log(e, null);
2447                     } catch (BadLocationException e) {
2448                         AdtPlugin.log(e, null);
2449                     }
2450                     break;
2451                 case IGNORE_FIDELITY_WARNING:
2452                     RenderLogger.ignoreFidelityWarning((String) mData[0]);
2453                     recomputeLayout();
2454                     break;
2455                 case SET_ATTRIBUTE: {
2456                     final UiElementNode element = (UiElementNode) mData[0];
2457                     final String attribute = (String) mData[1];
2458                     final String value = (String) mData[2];
2459                     mEditorDelegate.getEditor().wrapUndoEditXmlModel(
2460                             String.format("Set \"%1$s\" to \"%2$s\"", attribute, value),
2461                             new Runnable() {
2462                         @Override
2463                         public void run() {
2464                             element.setAttributeValue(attribute, ANDROID_URI, value, true);
2465                             element.commitDirtyAttributesToXml();
2466                         }
2467                     });
2468                     break;
2469                 }
2470                 default:
2471                     assert false : mAction;
2472                     break;
2473             }
2474         }
2475 
2476         @Override
similarTo(StyleRange style)2477         public boolean similarTo(StyleRange style) {
2478             // Prevent adjacent link ranges from getting merged
2479             return false;
2480         }
2481     }
2482 
2483     /**
2484      * Returns the error label for the graphical editor (which may not be visible
2485      * or showing errors)
2486      *
2487      * @return the error label, never null
2488      */
getErrorLabel()2489     StyledText getErrorLabel() {
2490         return mErrorLabel;
2491     }
2492 
2493     /**
2494      * Monitor clicks on the error label.
2495      * If the click happens on a style range created by
2496      * {@link GraphicalEditorPart#addClassLink(StyledText, String)}, we assume it's about
2497      * a missing class and we then proceed to display the standard Eclipse class creator wizard.
2498      */
2499     private class ErrorLabelListener extends MouseAdapter {
2500 
2501         @Override
mouseUp(MouseEvent event)2502         public void mouseUp(MouseEvent event) {
2503             super.mouseUp(event);
2504 
2505             if (event.widget != mErrorLabel) {
2506                 return;
2507             }
2508 
2509             int offset = mErrorLabel.getCaretOffset();
2510 
2511             StyleRange r = null;
2512             StyleRange[] ranges = mErrorLabel.getStyleRanges();
2513             if (ranges != null && ranges.length > 0) {
2514                 for (StyleRange sr : ranges) {
2515                     if (sr.start <= offset && sr.start + sr.length > offset) {
2516                         r = sr;
2517                         break;
2518                     }
2519                 }
2520             }
2521 
2522             if (r instanceof ActionLinkStyleRange) {
2523                 ActionLinkStyleRange range = (ActionLinkStyleRange) r;
2524                 range.onClick();
2525             }
2526 
2527             LayoutCanvas canvas = getCanvasControl();
2528             canvas.updateMenuActionState();
2529         }
2530     }
2531 
createNewClass(String fqcn)2532     private void createNewClass(String fqcn) {
2533 
2534         int pos = fqcn.lastIndexOf('.');
2535         String packageName = pos < 0 ? "" : fqcn.substring(0, pos);  //$NON-NLS-1$
2536         String className = pos <= 0 || pos >= fqcn.length() ? "" : fqcn.substring(pos + 1); //$NON-NLS-1$
2537 
2538         // create the wizard page for the class creation, and configure it
2539         NewClassWizardPage page = new NewClassWizardPage();
2540 
2541         // set the parent class
2542         page.setSuperClass(SdkConstants.CLASS_VIEW, true /* canBeModified */);
2543 
2544         // get the source folders as java elements.
2545         IPackageFragmentRoot[] roots = getPackageFragmentRoots(
2546                 mEditorDelegate.getEditor().getProject(),
2547                 false /*includeContainers*/, true /*skipGenFolder*/);
2548 
2549         IPackageFragmentRoot currentRoot = null;
2550         IPackageFragment currentFragment = null;
2551         int packageMatchCount = -1;
2552 
2553         for (IPackageFragmentRoot root : roots) {
2554             // Get the java element for the package.
2555             // This method is said to always return a IPackageFragment even if the
2556             // underlying folder doesn't exist...
2557             IPackageFragment fragment = root.getPackageFragment(packageName);
2558             if (fragment != null && fragment.exists()) {
2559                 // we have a perfect match! we use it.
2560                 currentRoot = root;
2561                 currentFragment = fragment;
2562                 packageMatchCount = -1;
2563                 break;
2564             } else {
2565                 // we don't have a match. we look for the fragment with the best match
2566                 // (ie the closest parent package we can find)
2567                 try {
2568                     IJavaElement[] children;
2569                     children = root.getChildren();
2570                     for (IJavaElement child : children) {
2571                         if (child instanceof IPackageFragment) {
2572                             fragment = (IPackageFragment)child;
2573                             if (packageName.startsWith(fragment.getElementName())) {
2574                                 // its a match. get the number of segments
2575                                 String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$
2576                                 if (segments.length > packageMatchCount) {
2577                                     packageMatchCount = segments.length;
2578                                     currentFragment = fragment;
2579                                     currentRoot = root;
2580                                 }
2581                             }
2582                         }
2583                     }
2584                 } catch (JavaModelException e) {
2585                     // Couldn't get the children: we just ignore this package root.
2586                 }
2587             }
2588         }
2589 
2590         ArrayList<IPackageFragment> createdFragments = null;
2591 
2592         if (currentRoot != null) {
2593             // if we have a perfect match, we set it and we're done.
2594             if (packageMatchCount == -1) {
2595                 page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
2596                 page.setPackageFragment(currentFragment, true /* canBeModified */);
2597             } else {
2598                 // we have a partial match.
2599                 // create the package. We have to start with the first segment so that we
2600                 // know what to delete in case of a cancel.
2601                 try {
2602                     createdFragments = new ArrayList<IPackageFragment>();
2603 
2604                     int totalCount = packageName.split("\\.").length; //$NON-NLS-1$
2605                     int count = 0;
2606                     int index = -1;
2607                     // skip the matching packages
2608                     while (count < packageMatchCount) {
2609                         index = packageName.indexOf('.', index+1);
2610                         count++;
2611                     }
2612 
2613                     // create the rest of the segments, except for the last one as indexOf will
2614                     // return -1;
2615                     while (count < totalCount - 1) {
2616                         index = packageName.indexOf('.', index+1);
2617                         count++;
2618                         createdFragments.add(currentRoot.createPackageFragment(
2619                                 packageName.substring(0, index),
2620                                 true /* force*/, new NullProgressMonitor()));
2621                     }
2622 
2623                     // create the last package
2624                     createdFragments.add(currentRoot.createPackageFragment(
2625                             packageName, true /* force*/, new NullProgressMonitor()));
2626 
2627                     // set the root and fragment in the Wizard page
2628                     page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/);
2629                     page.setPackageFragment(createdFragments.get(createdFragments.size()-1),
2630                             true /* canBeModified */);
2631                 } catch (JavaModelException e) {
2632                     // If we can't create the packages, there's a problem.
2633                     // We revert to the default package
2634                     for (IPackageFragmentRoot root : roots) {
2635                         // Get the java element for the package.
2636                         // This method is said to always return a IPackageFragment even if the
2637                         // underlying folder doesn't exist...
2638                         IPackageFragment fragment = root.getPackageFragment(packageName);
2639                         if (fragment != null && fragment.exists()) {
2640                             page.setPackageFragmentRoot(root, true /* canBeModified*/);
2641                             page.setPackageFragment(fragment, true /* canBeModified */);
2642                             break;
2643                         }
2644                     }
2645                 }
2646             }
2647         } else if (roots.length > 0) {
2648             // if we haven't found a valid fragment, we set the root to the first source folder.
2649             page.setPackageFragmentRoot(roots[0], true /* canBeModified*/);
2650         }
2651 
2652         // if we have a starting class name we use it
2653         if (className != null) {
2654             page.setTypeName(className, true /* canBeModified*/);
2655         }
2656 
2657         // create the action that will open it the wizard.
2658         OpenNewClassWizardAction action = new OpenNewClassWizardAction();
2659         action.setConfiguredWizardPage(page);
2660         action.run();
2661         IJavaElement element = action.getCreatedElement();
2662 
2663         if (element == null) {
2664             // lets delete the packages we created just for this.
2665             // we need to start with the leaf and go up
2666             if (createdFragments != null) {
2667                 try {
2668                     for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) {
2669                         createdFragments.get(i).delete(true /* force*/,
2670                                                        new NullProgressMonitor());
2671                     }
2672                 } catch (JavaModelException e) {
2673                     e.printStackTrace();
2674                 }
2675             }
2676         }
2677     }
2678 
2679     /**
2680      * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source
2681      * folders of the specified project.
2682      *
2683      * @param project the project
2684      * @param includeContainers True to include containers
2685      * @param skipGenFolder True to skip the "gen" folder
2686      * @return an array of IPackageFragmentRoot.
2687      */
getPackageFragmentRoots(IProject project, boolean includeContainers, boolean skipGenFolder)2688     private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project,
2689             boolean includeContainers, boolean skipGenFolder) {
2690         ArrayList<IPackageFragmentRoot> result = new ArrayList<IPackageFragmentRoot>();
2691         try {
2692             IJavaProject javaProject = JavaCore.create(project);
2693             IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots();
2694             for (int i = 0; i < roots.length; i++) {
2695                 if (skipGenFolder) {
2696                     IResource resource = roots[i].getResource();
2697                     if (resource != null && resource.getName().equals(FD_GEN_SOURCES)) {
2698                         continue;
2699                     }
2700                 }
2701                 IClasspathEntry entry = roots[i].getRawClasspathEntry();
2702                 if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE ||
2703                         (includeContainers &&
2704                                 entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER)) {
2705                     result.add(roots[i]);
2706                 }
2707             }
2708         } catch (JavaModelException e) {
2709         }
2710 
2711         return result.toArray(new IPackageFragmentRoot[result.size()]);
2712     }
2713 
2714     /**
2715      * Reopens this file as included within the given file (this assumes that the given
2716      * file has an include tag referencing this view, and the set of views that have this
2717      * property can be found using the {@link IncludeFinder}.
2718      *
2719      * @param includeWithin reference to a file to include as a surrounding context,
2720      *   or null to show the file standalone
2721      */
showIn(Reference includeWithin)2722     public void showIn(Reference includeWithin) {
2723         mIncludedWithin = includeWithin;
2724 
2725         if (includeWithin != null) {
2726             IFile file = includeWithin.getFile();
2727 
2728             // Update configuration
2729             if (file != null) {
2730                 mConfigChooser.resetConfigFor(file);
2731             }
2732         }
2733         recomputeLayout();
2734     }
2735 
2736     /**
2737      * Return all resource names of a given type, either in the project or in the
2738      * framework.
2739      *
2740      * @param framework if true, return all the framework resource names, otherwise return
2741      *            all the project resource names
2742      * @param type the type of resource to look up
2743      * @return a collection of resource names, never null but possibly empty
2744      */
getResourceNames(boolean framework, ResourceType type)2745     public Collection<String> getResourceNames(boolean framework, ResourceType type) {
2746         Map<ResourceType, Map<String, ResourceValue>> map =
2747             framework ? mConfiguredFrameworkRes : mConfiguredProjectRes;
2748         Map<String, ResourceValue> animations = map.get(type);
2749         if (animations != null) {
2750             return animations.keySet();
2751         } else {
2752             return Collections.emptyList();
2753         }
2754     }
2755 
2756     /**
2757      * Return this editor's current configuration
2758      *
2759      * @return the current configuration
2760      */
getConfiguration()2761     public FolderConfiguration getConfiguration() {
2762         return mConfigChooser.getConfiguration().getFullConfig();
2763     }
2764 
2765     /**
2766      * Figures out the project's minSdkVersion and targetSdkVersion and return whether the values
2767      * have changed.
2768      */
computeSdkVersion()2769     private boolean computeSdkVersion() {
2770         int oldMinSdkVersion = mMinSdkVersion;
2771         int oldTargetSdkVersion = mTargetSdkVersion;
2772 
2773         Pair<Integer, Integer> v = ManifestInfo.computeSdkVersions(mEditedFile.getProject());
2774         mMinSdkVersion = v.getFirst();
2775         mTargetSdkVersion = v.getSecond();
2776 
2777         return oldMinSdkVersion != mMinSdkVersion || oldTargetSdkVersion != mTargetSdkVersion;
2778     }
2779 
2780     /**
2781      * Returns the associated configuration chooser
2782      *
2783      * @return the configuration chooser
2784      */
2785     @NonNull
getConfigurationChooser()2786     public ConfigurationChooser getConfigurationChooser() {
2787         return mConfigChooser;
2788     }
2789 
2790     /**
2791      * Returns the associated layout actions bar
2792      *
2793      * @return the layout actions bar
2794      */
2795     @NonNull
getLayoutActionBar()2796     public LayoutActionBar getLayoutActionBar() {
2797         return mActionBar;
2798     }
2799 
2800     /**
2801      * Returns the target SDK version
2802      *
2803      * @return the target SDK version
2804      */
getTargetSdkVersion()2805     public int getTargetSdkVersion() {
2806         return mTargetSdkVersion;
2807     }
2808 
2809     /**
2810      * Returns the minimum SDK version
2811      *
2812      * @return the minimum SDK version
2813      */
getMinSdkVersion()2814     public int getMinSdkVersion() {
2815         return mMinSdkVersion;
2816     }
2817 
2818     /** If the flyout hover is showing, dismiss it */
dismissHoverPalette()2819     public void dismissHoverPalette() {
2820         mPaletteComposite.dismissHover();
2821     }
2822 
2823     // ---- Implements IFlyoutListener ----
2824 
2825     @Override
stateChanged(int oldState, int newState)2826     public void stateChanged(int oldState, int newState) {
2827         // Auto zoom the surface if you open or close flyout windows such as the palette
2828         // or the property/outline views
2829         if (newState == STATE_OPEN || newState == STATE_COLLAPSED && oldState == STATE_OPEN) {
2830             getCanvasControl().setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/);
2831         }
2832 
2833         sDockingStateVersion++;
2834         mDockingStateVersion = sDockingStateVersion;
2835     }
2836 }
2837