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