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