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