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