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