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