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