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
17package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
18
19import static com.android.SdkConstants.ANDROID_PKG;
20import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
21import static com.android.SdkConstants.ANDROID_URI;
22import static com.android.SdkConstants.ATTR_CONTEXT;
23import static com.android.SdkConstants.ATTR_ID;
24import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
25import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
26import static com.android.SdkConstants.FD_GEN_SOURCES;
27import static com.android.SdkConstants.GRID_LAYOUT;
28import static com.android.SdkConstants.SCROLL_VIEW;
29import static com.android.SdkConstants.STRING_PREFIX;
30import static com.android.SdkConstants.VALUE_FALSE;
31import static com.android.SdkConstants.VALUE_FILL_PARENT;
32import static com.android.SdkConstants.VALUE_MATCH_PARENT;
33import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
34import static com.android.ide.common.rendering.RenderSecurityManager.ENABLED_PROPERTY;
35import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
36import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
37import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER;
38import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET;
39import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
40import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
41import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
42import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
43import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN;
44
45import com.android.SdkConstants;
46import com.android.annotations.NonNull;
47import com.android.annotations.Nullable;
48import com.android.ide.common.layout.BaseLayoutRule;
49import com.android.ide.common.rendering.LayoutLibrary;
50import com.android.ide.common.rendering.RenderSecurityException;
51import com.android.ide.common.rendering.RenderSecurityManager;
52import com.android.ide.common.rendering.StaticRenderSession;
53import com.android.ide.common.rendering.api.Capability;
54import com.android.ide.common.rendering.api.LayoutLog;
55import com.android.ide.common.rendering.api.RenderSession;
56import com.android.ide.common.rendering.api.ResourceValue;
57import com.android.ide.common.rendering.api.Result;
58import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
59import com.android.ide.common.resources.ResourceRepository;
60import com.android.ide.common.resources.ResourceResolver;
61import com.android.ide.common.resources.configuration.FolderConfiguration;
62import com.android.ide.common.sdk.LoadStatus;
63import com.android.ide.eclipse.adt.AdtConstants;
64import com.android.ide.eclipse.adt.AdtPlugin;
65import com.android.ide.eclipse.adt.AdtUtils;
66import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
67import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
68import com.android.ide.eclipse.adt.internal.editors.IconFactory;
69import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate;
70import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
71import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
72import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
73import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
74import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
75import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
76import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
77import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
78import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
79import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
80import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher;
81import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
82import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
83import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
84import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PaletteControl.PalettePage;
85import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
86import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
87import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
88import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
89import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
90import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
91import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
92import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
93import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
94import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
95import com.android.ide.eclipse.adt.internal.sdk.Sdk;
96import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
97import com.android.resources.Density;
98import com.android.resources.ResourceFolderType;
99import com.android.resources.ResourceType;
100import com.android.sdklib.IAndroidTarget;
101import com.android.tools.lint.detector.api.LintUtils;
102import com.android.utils.Pair;
103
104import org.eclipse.core.resources.IFile;
105import org.eclipse.core.resources.IMarker;
106import org.eclipse.core.resources.IProject;
107import org.eclipse.core.resources.IResource;
108import org.eclipse.core.runtime.CoreException;
109import org.eclipse.core.runtime.IPath;
110import org.eclipse.core.runtime.IProgressMonitor;
111import org.eclipse.core.runtime.IStatus;
112import org.eclipse.core.runtime.NullProgressMonitor;
113import org.eclipse.core.runtime.Path;
114import org.eclipse.core.runtime.QualifiedName;
115import org.eclipse.core.runtime.Status;
116import org.eclipse.core.runtime.jobs.Job;
117import org.eclipse.jdt.core.IClasspathEntry;
118import org.eclipse.jdt.core.IJavaElement;
119import org.eclipse.jdt.core.IJavaModelMarker;
120import org.eclipse.jdt.core.IJavaProject;
121import org.eclipse.jdt.core.IPackageFragment;
122import org.eclipse.jdt.core.IPackageFragmentRoot;
123import org.eclipse.jdt.core.JavaCore;
124import org.eclipse.jdt.core.JavaModelException;
125import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage;
126import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
127import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
128import org.eclipse.jface.action.MenuManager;
129import org.eclipse.jface.dialogs.MessageDialog;
130import org.eclipse.jface.preference.IPreferenceStore;
131import org.eclipse.jface.text.BadLocationException;
132import org.eclipse.jface.text.IDocument;
133import org.eclipse.jface.text.source.ISourceViewer;
134import org.eclipse.jface.viewers.ISelection;
135import org.eclipse.jface.viewers.ISelectionChangedListener;
136import org.eclipse.jface.viewers.ISelectionProvider;
137import org.eclipse.jface.viewers.SelectionChangedEvent;
138import org.eclipse.jface.window.Window;
139import org.eclipse.swt.SWT;
140import org.eclipse.swt.custom.SashForm;
141import org.eclipse.swt.custom.StyleRange;
142import org.eclipse.swt.custom.StyledText;
143import org.eclipse.swt.events.MouseAdapter;
144import org.eclipse.swt.events.MouseEvent;
145import org.eclipse.swt.graphics.Image;
146import org.eclipse.swt.layout.GridData;
147import org.eclipse.swt.layout.GridLayout;
148import org.eclipse.swt.widgets.Composite;
149import org.eclipse.swt.widgets.Control;
150import org.eclipse.swt.widgets.Display;
151import org.eclipse.swt.widgets.Shell;
152import org.eclipse.text.edits.MalformedTreeException;
153import org.eclipse.text.edits.MultiTextEdit;
154import org.eclipse.text.edits.ReplaceEdit;
155import org.eclipse.ui.IActionBars;
156import org.eclipse.ui.IEditorInput;
157import org.eclipse.ui.IEditorPart;
158import org.eclipse.ui.IEditorSite;
159import org.eclipse.ui.INullSelectionListener;
160import org.eclipse.ui.ISelectionListener;
161import org.eclipse.ui.IWorkbench;
162import org.eclipse.ui.IWorkbenchPage;
163import org.eclipse.ui.IWorkbenchPart;
164import org.eclipse.ui.IWorkbenchPartSite;
165import org.eclipse.ui.IWorkbenchWindow;
166import org.eclipse.ui.PartInitException;
167import org.eclipse.ui.PlatformUI;
168import org.eclipse.ui.dialogs.PreferencesUtil;
169import org.eclipse.ui.ide.IDE;
170import org.eclipse.ui.part.EditorPart;
171import org.eclipse.ui.part.FileEditorInput;
172import org.eclipse.ui.part.IPageSite;
173import org.eclipse.ui.part.PageBookView;
174import org.eclipse.wb.core.controls.flyout.FlyoutControlComposite;
175import org.eclipse.wb.core.controls.flyout.IFlyoutListener;
176import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences;
177import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
178import org.w3c.dom.Element;
179import org.w3c.dom.Node;
180
181import java.io.File;
182import java.io.IOException;
183import java.util.ArrayList;
184import java.util.Collection;
185import java.util.Collections;
186import java.util.List;
187import java.util.Map;
188import 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 */
206public 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     */
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
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
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
345    public Image getPageImage() {
346        return IconFactory.getInstance().getIcon("editor_page_design");  //$NON-NLS-1$
347    }
348
349    @Override
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
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 */
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
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     */
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
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
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     */
648    public void select(Node xmlNode) {
649        mCanvasViewer.getCanvas().getSelectionManager().select(xmlNode);
650    }
651
652    // ---- Implements ConfigurationClient ----
653    @Override
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
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
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
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
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
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
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
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
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
891    public Reference getIncludedWithin() {
892        return mIncludedWithin;
893    }
894
895    @Override
896    @Nullable
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
907        public void onProjectTargetChange(IProject changedProject) {
908            if (changedProject != null && changedProject.equals(getProject())) {
909                updateEditor();
910            }
911        }
912
913        @Override
914        public void onTargetLoaded(IAndroidTarget loadedTarget) {
915            IAndroidTarget target = getRenderingTarget();
916            if (target != null && target.equals(loadedTarget)) {
917                updateEditor();
918            }
919        }
920
921        @Override
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
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
956        private IProject getProject() {
957            return getEditorDelegate().getEditor().getProject();
958        }
959    }
960
961    /** Refresh the configured project resources associated with this editor */
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     */
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     */
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
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
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
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
1039    public boolean isSaveAsAllowed() {
1040        return false;
1041    }
1042
1043    @Override
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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) */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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
1560    public RenderService createRenderService(Configuration configuration,
1561            ResourceResolver resolver) {
1562        return RenderService.create(this, configuration, resolver, mCredential);
1563    }
1564
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     */
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 */
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);
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     */
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     */
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
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> */
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     */
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. */
1843    private void hideError() {
1844        mErrorLabel.setText("");
1845        mSashError.setMaximizedControl(mCanvasViewer.getControl());
1846    }
1847
1848    /** Display the problem list encountered during a render */
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
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     */
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
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
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
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. */
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 */
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 */
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 */
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     */
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     */
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     */
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     */
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
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
2438        private ActionLinkStyleRange(int action, Object... data) {
2439            super();
2440            mAction = action;
2441            mData = data;
2442        }
2443
2444        /** Performs the click action */
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
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     */
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
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
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     */
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     */
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     */
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     */
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     */
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
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
2897    public LayoutActionBar getLayoutActionBar() {
2898        return mActionBar;
2899    }
2900
2901    /**
2902     * Returns the target SDK version
2903     *
2904     * @return the target SDK version
2905     */
2906    public int getTargetSdkVersion() {
2907        return mTargetSdkVersion;
2908    }
2909
2910    /**
2911     * Returns the minimum SDK version
2912     *
2913     * @return the minimum SDK version
2914     */
2915    public int getMinSdkVersion() {
2916        return mMinSdkVersion;
2917    }
2918
2919    /** If the flyout hover is showing, dismiss it */
2920    public void dismissHoverPalette() {
2921        mPaletteComposite.dismissHover();
2922    }
2923
2924    // ---- Implements IFlyoutListener ----
2925
2926    @Override
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