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