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