Home | History | Annotate | Download | only in gle2
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
     18 
     19 import static com.android.SdkConstants.ANDROID_PKG;
     20 import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
     21 import static com.android.SdkConstants.ANDROID_URI;
     22 import static com.android.SdkConstants.ATTR_CONTEXT;
     23 import static com.android.SdkConstants.ATTR_ID;
     24 import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
     25 import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
     26 import static com.android.SdkConstants.FD_GEN_SOURCES;
     27 import static com.android.SdkConstants.GRID_LAYOUT;
     28 import static com.android.SdkConstants.SCROLL_VIEW;
     29 import static com.android.SdkConstants.STRING_PREFIX;
     30 import static com.android.SdkConstants.VALUE_FALSE;
     31 import static com.android.SdkConstants.VALUE_FILL_PARENT;
     32 import static com.android.SdkConstants.VALUE_MATCH_PARENT;
     33 import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
     34 import static com.android.ide.common.rendering.RenderSecurityManager.ENABLED_PROPERTY;
     35 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
     36 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
     37 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER;
     38 import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET;
     39 import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
     40 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
     41 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
     42 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
     43 import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN;
     44 
     45 import com.android.SdkConstants;
     46 import com.android.annotations.NonNull;
     47 import com.android.annotations.Nullable;
     48 import com.android.ide.common.layout.BaseLayoutRule;
     49 import com.android.ide.common.rendering.LayoutLibrary;
     50 import com.android.ide.common.rendering.RenderSecurityException;
     51 import com.android.ide.common.rendering.RenderSecurityManager;
     52 import com.android.ide.common.rendering.StaticRenderSession;
     53 import com.android.ide.common.rendering.api.Capability;
     54 import com.android.ide.common.rendering.api.LayoutLog;
     55 import com.android.ide.common.rendering.api.RenderSession;
     56 import com.android.ide.common.rendering.api.ResourceValue;
     57 import com.android.ide.common.rendering.api.Result;
     58 import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
     59 import com.android.ide.common.resources.ResourceRepository;
     60 import com.android.ide.common.resources.ResourceResolver;
     61 import com.android.ide.common.resources.configuration.FolderConfiguration;
     62 import com.android.ide.common.sdk.LoadStatus;
     63 import com.android.ide.eclipse.adt.AdtConstants;
     64 import com.android.ide.eclipse.adt.AdtPlugin;
     65 import com.android.ide.eclipse.adt.AdtUtils;
     66 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
     67 import com.android.ide.eclipse.adt.internal.editors.IPageImageProvider;
     68 import com.android.ide.eclipse.adt.internal.editors.IconFactory;
     69 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate;
     70 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
     71 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
     72 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor;
     73 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ChangeFlags;
     74 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutReloadMonitor.ILayoutReloadListener;
     75 import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
     76 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
     77 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
     78 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
     79 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
     80 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher;
     81 import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
     82 import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
     83 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
     84 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.PaletteControl.PalettePage;
     85 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
     86 import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
     87 import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
     88 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
     89 import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
     90 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     91 import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
     92 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
     93 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
     94 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
     95 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
     96 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
     97 import com.android.resources.Density;
     98 import com.android.resources.ResourceFolderType;
     99 import com.android.resources.ResourceType;
    100 import com.android.sdklib.IAndroidTarget;
    101 import com.android.tools.lint.detector.api.LintUtils;
    102 import com.android.utils.Pair;
    103 
    104 import org.eclipse.core.resources.IFile;
    105 import org.eclipse.core.resources.IMarker;
    106 import org.eclipse.core.resources.IProject;
    107 import org.eclipse.core.resources.IResource;
    108 import org.eclipse.core.runtime.CoreException;
    109 import org.eclipse.core.runtime.IPath;
    110 import org.eclipse.core.runtime.IProgressMonitor;
    111 import org.eclipse.core.runtime.IStatus;
    112 import org.eclipse.core.runtime.NullProgressMonitor;
    113 import org.eclipse.core.runtime.Path;
    114 import org.eclipse.core.runtime.QualifiedName;
    115 import org.eclipse.core.runtime.Status;
    116 import org.eclipse.core.runtime.jobs.Job;
    117 import org.eclipse.jdt.core.IClasspathEntry;
    118 import org.eclipse.jdt.core.IJavaElement;
    119 import org.eclipse.jdt.core.IJavaModelMarker;
    120 import org.eclipse.jdt.core.IJavaProject;
    121 import org.eclipse.jdt.core.IPackageFragment;
    122 import org.eclipse.jdt.core.IPackageFragmentRoot;
    123 import org.eclipse.jdt.core.JavaCore;
    124 import org.eclipse.jdt.core.JavaModelException;
    125 import org.eclipse.jdt.internal.ui.preferences.BuildPathsPropertyPage;
    126 import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction;
    127 import org.eclipse.jdt.ui.wizards.NewClassWizardPage;
    128 import org.eclipse.jface.action.MenuManager;
    129 import org.eclipse.jface.dialogs.MessageDialog;
    130 import org.eclipse.jface.preference.IPreferenceStore;
    131 import org.eclipse.jface.text.BadLocationException;
    132 import org.eclipse.jface.text.IDocument;
    133 import org.eclipse.jface.text.source.ISourceViewer;
    134 import org.eclipse.jface.viewers.ISelection;
    135 import org.eclipse.jface.viewers.ISelectionChangedListener;
    136 import org.eclipse.jface.viewers.ISelectionProvider;
    137 import org.eclipse.jface.viewers.SelectionChangedEvent;
    138 import org.eclipse.jface.window.Window;
    139 import org.eclipse.swt.SWT;
    140 import org.eclipse.swt.custom.SashForm;
    141 import org.eclipse.swt.custom.StyleRange;
    142 import org.eclipse.swt.custom.StyledText;
    143 import org.eclipse.swt.events.MouseAdapter;
    144 import org.eclipse.swt.events.MouseEvent;
    145 import org.eclipse.swt.graphics.Image;
    146 import org.eclipse.swt.layout.GridData;
    147 import org.eclipse.swt.layout.GridLayout;
    148 import org.eclipse.swt.widgets.Composite;
    149 import org.eclipse.swt.widgets.Control;
    150 import org.eclipse.swt.widgets.Display;
    151 import org.eclipse.swt.widgets.Shell;
    152 import org.eclipse.text.edits.MalformedTreeException;
    153 import org.eclipse.text.edits.MultiTextEdit;
    154 import org.eclipse.text.edits.ReplaceEdit;
    155 import org.eclipse.ui.IActionBars;
    156 import org.eclipse.ui.IEditorInput;
    157 import org.eclipse.ui.IEditorPart;
    158 import org.eclipse.ui.IEditorSite;
    159 import org.eclipse.ui.INullSelectionListener;
    160 import org.eclipse.ui.ISelectionListener;
    161 import org.eclipse.ui.IWorkbench;
    162 import org.eclipse.ui.IWorkbenchPage;
    163 import org.eclipse.ui.IWorkbenchPart;
    164 import org.eclipse.ui.IWorkbenchPartSite;
    165 import org.eclipse.ui.IWorkbenchWindow;
    166 import org.eclipse.ui.PartInitException;
    167 import org.eclipse.ui.PlatformUI;
    168 import org.eclipse.ui.dialogs.PreferencesUtil;
    169 import org.eclipse.ui.ide.IDE;
    170 import org.eclipse.ui.part.EditorPart;
    171 import org.eclipse.ui.part.FileEditorInput;
    172 import org.eclipse.ui.part.IPageSite;
    173 import org.eclipse.ui.part.PageBookView;
    174 import org.eclipse.wb.core.controls.flyout.FlyoutControlComposite;
    175 import org.eclipse.wb.core.controls.flyout.IFlyoutListener;
    176 import org.eclipse.wb.core.controls.flyout.PluginFlyoutPreferences;
    177 import org.eclipse.wb.internal.core.editor.structure.PageSiteComposite;
    178 import org.w3c.dom.Element;
    179 import org.w3c.dom.Node;
    180 
    181 import java.io.File;
    182 import java.io.IOException;
    183 import java.util.ArrayList;
    184 import java.util.Collection;
    185 import java.util.Collections;
    186 import java.util.List;
    187 import java.util.Map;
    188 import java.util.Set;
    189 
    190 /**
    191  * Graphical layout editor part, version 2.
    192  * <p/>
    193  * The main component of the editor part is the {@link LayoutCanvasViewer}, which
    194  * actually delegates its work to the {@link LayoutCanvas} control.
    195  * <p/>
    196  * The {@link LayoutCanvasViewer} is set as the site's {@link ISelectionProvider}:
    197  * when the selection changes in the canvas, it is thus broadcasted to anyone listening
    198  * on the site's selection service.
    199  * <p/>
    200  * This part is also an {@link ISelectionListener}. It listens to the site's selection
    201  * service and thus receives selection changes from itself as well as the associated
    202  * outline and property sheet (these are registered by {@link LayoutEditorDelegate#delegateGetAdapter(Class)}).
    203  *
    204  * @since GLE2
    205  */
    206 public class GraphicalEditorPart extends EditorPart
    207     implements IPageImageProvider, INullSelectionListener, IFlyoutListener,
    208             ConfigurationClient {
    209 
    210     /*
    211      * Useful notes:
    212      * To understand Drag & drop:
    213      *   http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html
    214      *
    215      * To understand the site's selection listener, selection provider, and the
    216      * confusion of different-yet-similarly-named interfaces, consult this:
    217      *   http://www.eclipse.org/articles/Article-WorkbenchSelections/article.html
    218      *
    219      * To summarize the selection mechanism:
    220      * - The workbench site selection service can be seen as "centralized"
    221      *   service that registers selection providers and selection listeners.
    222      * - The editor part and the outline are selection providers.
    223      * - The editor part, the outline and the property sheet are listeners
    224      *   which all listen to each others indirectly.
    225      */
    226 
    227     /** Property key for the window preferences for the structure flyout */
    228     private static final String PREF_STRUCTURE = "design.structure";     //$NON-NLS-1$
    229 
    230     /** Property key for the window preferences for the palette flyout */
    231     private static final String PREF_PALETTE = "design.palette";         //$NON-NLS-1$
    232 
    233     /**
    234      * Session-property on files which specifies the initial config state to be used on
    235      * this file
    236      */
    237     public final static QualifiedName NAME_INITIAL_STATE =
    238         new QualifiedName(AdtPlugin.PLUGIN_ID, "initialstate");//$NON-NLS-1$
    239 
    240     /**
    241      * Session-property on files which specifies the inclusion-context (reference to another layout
    242      * which should be "including" this layout) when the file is opened
    243      */
    244     public final static QualifiedName NAME_INCLUDE =
    245         new QualifiedName(AdtPlugin.PLUGIN_ID, "includer");//$NON-NLS-1$
    246 
    247     /** Reference to the layout editor */
    248     private final LayoutEditorDelegate mEditorDelegate;
    249 
    250     /** Reference to the file being edited. Can also be used to access the {@link IProject}. */
    251     private IFile mEditedFile;
    252 
    253     /** The configuration chooser at the top of the layout editor. */
    254     private ConfigurationChooser mConfigChooser;
    255 
    256     /** The sash that splits the palette from the error view.
    257      * The error view is shown only when needed. */
    258     private SashForm mSashError;
    259 
    260     /** The palette displayed on the left of the sash. */
    261     private PaletteControl mPalette;
    262 
    263     /** The layout canvas displayed to the right of the sash. */
    264     private LayoutCanvasViewer mCanvasViewer;
    265 
    266     /** The Rules Engine associated with this editor. It is project-specific. */
    267     private RulesEngine mRulesEngine;
    268 
    269     /** Styled text displaying the most recent error in the error view. */
    270     private StyledText mErrorLabel;
    271 
    272     /**
    273      * The resource reference to a file that should surround this file (e.g. include this file
    274      * visually), or null if not applicable
    275      */
    276     private Reference mIncludedWithin;
    277 
    278     private Map<ResourceType, Map<String, ResourceValue>> mConfiguredFrameworkRes;
    279     private Map<ResourceType, Map<String, ResourceValue>> mConfiguredProjectRes;
    280     private ProjectCallback mProjectCallback;
    281     private boolean mNeedsRecompute = false;
    282     private TargetListener mTargetListener;
    283     private ResourceResolver mResourceResolver;
    284     private ReloadListener mReloadListener;
    285     private int mMinSdkVersion;
    286     private int mTargetSdkVersion;
    287     private LayoutActionBar mActionBar;
    288     private OutlinePage mOutlinePage;
    289     private FlyoutControlComposite mStructureFlyout;
    290     private FlyoutControlComposite mPaletteComposite;
    291     private PropertyFactory mPropertyFactory;
    292     private boolean mRenderedOnce;
    293     private final Object mCredential = new Object();
    294 
    295     /**
    296      * Flags which tracks whether this editor is currently active which is set whenever
    297      * {@link #activated()} is called and clear whenever {@link #deactivated()} is called.
    298      * This is used to suppress repeated calls to {@link #activate()} to avoid doing
    299      * unnecessary work.
    300      */
    301     private boolean mActive;
    302 
    303     /**
    304      * Constructs a new {@link GraphicalEditorPart}
    305      *
    306      * @param editorDelegate the associated XML editor delegate
    307      */
    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, this);
   1693         } else if (reset) {
   1694             // Also clears the set of missing/broken classes prior to rendering
   1695             mProjectCallback.getMissingClasses().clear();
   1696             mProjectCallback.getUninstantiatableClasses().clear();
   1697         }
   1698 
   1699         return mProjectCallback;
   1700     }
   1701 
   1702     /**
   1703      * Returns the resource name of this layout, NOT including the @layout/ prefix
   1704      *
   1705      * @return the resource name of this layout, NOT including the @layout/ prefix
   1706      */
   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