aboutsummaryrefslogtreecommitdiffstats
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java65
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java49
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java120
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java27
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java24
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java88
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java373
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java4
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java2
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AvdManagerAction.java2
-rw-r--r--[-rwxr-xr-x]eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ConvertSwitchQuickFixProcessor.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/Messages.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java97
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceChangeHandler.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java36
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ChangedFileSetHelper.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java64
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java164
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java77
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidDoubleClickStrategy.java92
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/CompletionProposal.java74
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java125
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java13
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java112
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlFormatPreferences.java (renamed from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatPreferences.java)73
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlPrettyPrinter.java249
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatStyle.java130
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java976
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java51
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java174
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java26
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java401
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java464
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java390
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java155
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java223
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java110
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleManager.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java506
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java54
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java508
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java76
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java352
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java32
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java59
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java33
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java97
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java227
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java275
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java390
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java10
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java1333
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java221
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java1695
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java184
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java103
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java181
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml12
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java62
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java108
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java11
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java24
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java17
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java117
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java9
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java83
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/DosLineEndingsFix.java64
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java168
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ExtractStringFix.java12
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java27
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java28
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/TypoFix.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java130
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java112
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/BuildPreferencePage.java184
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/Messages.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/messages.properties4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java25
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java292
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChange.java293
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChangeDescription.java122
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutFileChanges.java61
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidPackageRenameChange.java135
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeMoveChange.java45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeRenameChange.java120
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java528
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidRenameParticipant.java84
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java416
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java381
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java146
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java547
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java362
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java529
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java (renamed from eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/FixImportsJob.java)2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java224
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java177
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java752
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java211
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java157
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java411
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java163
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java78
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java120
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java43
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java84
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java70
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java191
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java226
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/utils/FingerprintUtils.java63
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java69
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java78
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java173
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java54
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java23
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/AddTranslationDialog.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ChooseConfigurationPage.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java24
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java50
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java52
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java120
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/messages.properties2
215 files changed, 15433 insertions, 6226 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
index a555ef4..83ce9ef 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
@@ -44,7 +44,6 @@ import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.IDragElement;
import com.android.ide.common.api.IMenuCallback;
import com.android.ide.common.api.INode;
-import com.android.ide.common.api.IValidator;
import com.android.ide.common.api.IViewMetadata;
import com.android.ide.common.api.IViewRule;
import com.android.ide.common.api.RuleAction;
@@ -191,27 +190,11 @@ public class BaseViewRule extends AbstractViewRule {
// Ids must be set individually so open the id dialog for each
// selected node (though allow cancel to break the loop)
for (INode node : selectedNodes) {
- // Strip off the @id prefix stuff
- String oldId = node.getStringAttr(ANDROID_URI, ATTR_ID);
- oldId = stripIdPrefix(ensureValidString(oldId));
- IValidator validator = mRulesEngine.getResourceValidator("id",//$NON-NLS-1$
- false /*uniqueInProject*/,
- true /*uniqueInLayout*/,
- false /*exists*/,
- oldId);
- String newId = mRulesEngine.displayInput("New Id:", oldId, validator);
- if (newId != null && newId.trim().length() > 0) {
- if (!newId.startsWith(NEW_ID_PREFIX)) {
- newId = NEW_ID_PREFIX + stripIdPrefix(newId);
- }
- node.editXml("Change ID", new PropertySettingNodeHandler(ANDROID_URI,
- ATTR_ID, newId));
- editedProperty(ATTR_ID);
- } else if (newId == null) {
- // Cancelled
+ if (!mRulesEngine.rename(node)) {
break;
}
}
+ editedProperty(ATTR_ID);
return;
} else if (isProp) {
INode firstNode = selectedNodes.get(0);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java
index e809d00..f99cf0c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/FragmentRule.java
@@ -30,7 +30,7 @@ public class FragmentRule extends BaseViewRule {
@Override
public void onCreate(@NonNull INode node, @NonNull INode parent,
@NonNull InsertType insertType) {
- // When dropping a fragment tag, ask the user which layout to include.
+ // When dropping a fragment tag, ask the user which class to use.
if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW
String fqcn = mRulesEngine.displayFragmentSourceInput();
if (fqcn != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java
index a197e23..80a23c6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/GridLayoutRule.java
@@ -28,6 +28,7 @@ import static com.android.SdkConstants.GRAVITY_VALUE_FILL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_HORIZONTAL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_VERTICAL;
import static com.android.SdkConstants.GRAVITY_VALUE_LEFT;
+import static com.android.SdkConstants.GRID_LAYOUT;
import static com.android.SdkConstants.VALUE_HORIZONTAL;
import static com.android.SdkConstants.VALUE_TRUE;
@@ -53,6 +54,7 @@ import com.android.ide.common.api.SegmentType;
import com.android.ide.common.layout.grid.GridDropHandler;
import com.android.ide.common.layout.grid.GridLayoutPainter;
import com.android.ide.common.layout.grid.GridModel;
+import com.android.ide.common.layout.grid.GridModel.ViewData;
import com.android.utils.Pair;
import java.net.URL;
@@ -143,7 +145,7 @@ public class GridLayoutRule extends BaseLayoutRule {
* Whether the grid is edited in "grid mode" where the operations are row/column based
* rather than free-form
*/
- public static boolean sGridMode = false;
+ public static boolean sGridMode = true;
/** Constructs a new {@link GridLayoutRule} */
public GridLayoutRule() {
@@ -228,6 +230,9 @@ public class GridLayoutRule extends BaseLayoutRule {
// Add and Remove Column actions only apply in Grid Mode
if (sGridMode) {
+ actions.add(RuleAction.createToggle(ACTION_SHOW_STRUCTURE, "Show Structure",
+ sShowStructure, actionCallback, ICON_SHOW_STRUCT, 147, false));
+
// Add Row and Add Column
actions.add(RuleAction.createSeparator(150));
actions.add(RuleAction.createAction(ACTION_ADD_COL, "Add Column", actionCallback,
@@ -366,7 +371,8 @@ public class GridLayoutRule extends BaseLayoutRule {
public String getNamespace(INode layout) {
String namespace = ANDROID_URI;
- if (!layout.getFqcn().equals(FQCN_GRID_LAYOUT)) {
+ String fqcn = layout.getFqcn();
+ if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) {
namespace = mRulesEngine.getAppNameSpace();
}
@@ -407,10 +413,12 @@ public class GridLayoutRule extends BaseLayoutRule {
boolean moved) {
super.onRemovingChildren(deleted, parent, moved);
- // Attempt to clean up spacer objects for any newly-empty rows or columns
- // as the result of this deletion
- GridModel grid = GridModel.get(mRulesEngine, parent, null);
- grid.onDeleted(deleted);
+ if (!sGridMode) {
+ // Attempt to clean up spacer objects for any newly-empty rows or columns
+ // as the result of this deletion
+ GridModel grid = GridModel.get(mRulesEngine, parent, null);
+ grid.onDeleted(deleted);
+ }
}
@Override
@@ -454,6 +462,35 @@ public class GridLayoutRule extends BaseLayoutRule {
Rect oldBounds, Rect newBounds, SegmentType horizontalEdge, SegmentType verticalEdge) {
if (resizingWidget(state)) {
+ if (state.fillWidth || state.fillHeight || state.wrapWidth || state.wrapHeight) {
+ GridModel grid = getGrid(state);
+ ViewData view = grid.getView(node);
+ if (view != null) {
+ String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY);
+ int gravity = GravityHelper.getGravity(gravityString, 0);
+ if (view.column > 0 && verticalEdge != null && state.fillWidth) {
+ state.fillWidth = false;
+ state.wrapWidth = true;
+ gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
+ gravity |= GravityHelper.GRAVITY_FILL_HORIZ;
+ } else if (verticalEdge != null && state.wrapWidth) {
+ gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
+ gravity |= GravityHelper.GRAVITY_LEFT;
+ }
+ if (view.row > 0 && horizontalEdge != null && state.fillHeight) {
+ state.fillHeight = false;
+ state.wrapHeight = true;
+ gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
+ gravity |= GravityHelper.GRAVITY_FILL_VERT;
+ } else if (horizontalEdge != null && state.wrapHeight) {
+ gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
+ gravity |= GravityHelper.GRAVITY_TOP;
+ }
+ gravityString = GravityHelper.getGravity(gravity);
+ grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString);
+ // Fall through and set layout_width and/or layout_height to wrap_content
+ }
+ }
super.setNewSizeBounds(state, node, layout, oldBounds, newBounds, horizontalEdge,
verticalEdge);
} else {
@@ -463,6 +500,22 @@ public class GridLayoutRule extends BaseLayoutRule {
GridModel grid = getGrid(state);
grid.setColumnSpanAttribute(node, columnSpan);
grid.setRowSpanAttribute(node, rowSpan);
+
+ ViewData view = grid.getView(node);
+ if (view != null) {
+ String gravityString = grid.getGridAttribute(view.node, ATTR_LAYOUT_GRAVITY);
+ int gravity = GravityHelper.getGravity(gravityString, 0);
+ if (verticalEdge != null && columnSpan > 1) {
+ gravity &= ~GravityHelper.GRAVITY_HORIZ_MASK;
+ gravity |= GravityHelper.GRAVITY_FILL_HORIZ;
+ }
+ if (horizontalEdge != null && rowSpan > 1) {
+ gravity &= ~GravityHelper.GRAVITY_VERT_MASK;
+ gravity |= GravityHelper.GRAVITY_FILL_VERT;
+ }
+ gravityString = GravityHelper.getGravity(gravity);
+ grid.setGridAttribute(view.node, ATTR_LAYOUT_GRAVITY, gravityString);
+ }
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java
index d7a3026..610fe5d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java
@@ -188,6 +188,9 @@ public class LinearLayoutRule extends BaseLayoutRule {
weight = mRulesEngine.displayInput("Enter Weight Value:", weight,
null);
if (weight != null) {
+ if (weight.isEmpty()) {
+ weight = null; // remove attribute
+ }
for (INode child : children) {
child.setAttribute(ANDROID_URI,
ATTR_LAYOUT_WEIGHT, weight);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java
index 1dafe53..9f2b4ae 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ScrollViewRule.java
@@ -19,7 +19,9 @@ package com.android.ide.common.layout;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.ATTR_ORIENTATION;
import static com.android.SdkConstants.FQCN_LINEAR_LAYOUT;
+import static com.android.SdkConstants.VALUE_VERTICAL;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -58,7 +60,8 @@ public class ScrollViewRule extends FrameLayoutRule {
// Insert a default linear layout (which will in turn be registered as
// a child of this node and the create child method above will set its
// fill parent attributes, its id, etc.
- node.appendChild(FQCN_LINEAR_LAYOUT);
+ INode linear = node.appendChild(FQCN_LINEAR_LAYOUT);
+ linear.setAttribute(ANDROID_URI, ATTR_ORIENTATION, VALUE_VERTICAL);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java
new file mode 100644
index 0000000..a89a3d8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/ViewTagRule.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.common.layout;
+
+import static com.android.SdkConstants.ATTR_CLASS;
+
+import com.android.annotations.NonNull;
+import com.android.ide.common.api.INode;
+import com.android.ide.common.api.IViewRule;
+import com.android.ide.common.api.InsertType;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+
+/**
+ * An {@link IViewRule} for the special XML {@code <view>} tag.
+ */
+public class ViewTagRule extends BaseViewRule {
+ @Override
+ public void onCreate(@NonNull INode node, @NonNull INode parent,
+ @NonNull InsertType insertType) {
+ // When dropping a view tag, ask the user which custom view class to use
+ if (insertType == InsertType.CREATE) { // NOT InsertType.CREATE_PREVIEW
+ String fqcn = mRulesEngine.displayCustomViewClassInput();
+ if (fqcn != null) {
+ if (!ViewElementDescriptor.viewNeedsPackage(fqcn)) {
+ fqcn = fqcn.substring(fqcn.lastIndexOf('.') + 1);
+ }
+ node.editXml("Set Custom View Class",
+ new PropertySettingNodeHandler(null, ATTR_CLASS,
+ fqcn.length() > 0 ? fqcn : null));
+ } else {
+ // Remove the view; the insertion was canceled
+ parent.removeChild(node);
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java
index 8a6fdef..8bdb56b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridDropHandler.java
@@ -15,17 +15,17 @@
*/
package com.android.ide.common.layout.grid;
-import static com.android.ide.common.layout.GravityHelper.getGravity;
-import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE;
-import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE;
-import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE;
-import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP;
import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN;
import static com.android.SdkConstants.ATTR_LAYOUT_COLUMN_SPAN;
import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW;
import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
+import static com.android.ide.common.layout.GravityHelper.getGravity;
+import static com.android.ide.common.layout.GridLayoutRule.GRID_SIZE;
+import static com.android.ide.common.layout.GridLayoutRule.MARGIN_SIZE;
+import static com.android.ide.common.layout.GridLayoutRule.MAX_CELL_DIFFERENCE;
+import static com.android.ide.common.layout.GridLayoutRule.SHORT_GAP_DP;
import static com.android.ide.common.layout.grid.GridModel.UNDEFINED;
import static java.lang.Math.abs;
@@ -46,6 +46,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Locale;
/**
* The {@link GridDropHandler} handles drag and drop operations into and within a
@@ -83,8 +84,10 @@ public class GridDropHandler {
int x1 = p.x;
int y1 = p.y;
+ Rect dragBounds = feedback.dragBounds;
+ int w = dragBounds != null ? dragBounds.w : 0;
+ int h = dragBounds != null ? dragBounds.h : 0;
if (!GridLayoutRule.sGridMode) {
- Rect dragBounds = feedback.dragBounds;
if (dragBounds != null) {
// Sometimes the items are centered under the mouse so
// offset by the top left corner distance
@@ -92,8 +95,6 @@ public class GridDropHandler {
y1 += dragBounds.y;
}
- int w = dragBounds != null ? dragBounds.w : 0;
- int h = dragBounds != null ? dragBounds.h : 0;
int x2 = x1 + w;
int y2 = y1 + h;
@@ -185,19 +186,72 @@ public class GridDropHandler {
int SLOP = 2;
int radius = mRule.getNewCellSize();
if (rightDistance < radius + SLOP) {
- column++;
+ column = Math.min(column + 1, mGrid.actualColumnCount);
leftDistance = rightDistance;
}
if (bottomDistance < radius + SLOP) {
- row++;
+ row = Math.min(row + 1, mGrid.actualRowCount);
topDistance = bottomDistance;
}
- boolean matchLeft = leftDistance < radius + SLOP;
- boolean matchTop = topDistance < radius + SLOP;
+ boolean createColumn = leftDistance < radius + SLOP;
+ boolean createRow = topDistance < radius + SLOP;
+ if (x1 >= bounds.x2()) {
+ createColumn = true;
+ }
+ if (y1 >= bounds.y2()) {
+ createRow = true;
+ }
- mColumnMatch = new GridMatch(SegmentType.LEFT, 0, x1, column, matchLeft, 0);
- mRowMatch = new GridMatch(SegmentType.TOP, 0, y1, row, matchTop, 0);
+ int cellWidth = leftDistance + rightDistance;
+ int cellHeight = topDistance + bottomDistance;
+ SegmentType horizontalType = SegmentType.LEFT;
+ SegmentType verticalType = SegmentType.TOP;
+ int minDistance = 10; // Don't center or right/bottom align in tiny cells
+ if (!createColumn && leftDistance > minDistance
+ && dragBounds != null && dragBounds.w < cellWidth - 10) {
+ if (rightDistance < leftDistance) {
+ horizontalType = SegmentType.RIGHT;
+ }
+
+ int centerDistance = Math.abs(cellWidth / 2 - leftDistance);
+ if (centerDistance < leftDistance / 2 && centerDistance < rightDistance / 2) {
+ horizontalType = SegmentType.CENTER_HORIZONTAL;
+ }
+ }
+ if (!createRow && topDistance > minDistance
+ && dragBounds != null && dragBounds.h < cellHeight - 10) {
+ if (bottomDistance < topDistance) {
+ verticalType = SegmentType.BOTTOM;
+ }
+ int centerDistance = Math.abs(cellHeight / 2 - topDistance);
+ if (centerDistance < topDistance / 2 && centerDistance < bottomDistance / 2) {
+ verticalType = SegmentType.CENTER_VERTICAL;
+ }
+ }
+
+ mColumnMatch = new GridMatch(horizontalType, 0, x1, column, createColumn, 0);
+ mRowMatch = new GridMatch(verticalType, 0, y1, row, createRow, 0);
+
+ StringBuilder description = new StringBuilder(50);
+ String rowString = Integer.toString(mColumnMatch.cellIndex + 1);
+ String columnString = Integer.toString(mRowMatch.cellIndex + 1);
+ if (mRowMatch.createCell && mRowMatch.cellIndex < mGrid.actualRowCount) {
+ description.append(String.format("Shift row %1$d down", mRowMatch.cellIndex + 1));
+ description.append('\n');
+ }
+ if (mColumnMatch.createCell && mColumnMatch.cellIndex < mGrid.actualColumnCount) {
+ description.append(String.format("Shift column %1$d right",
+ mColumnMatch.cellIndex + 1));
+ description.append('\n');
+ }
+ description.append(String.format("Insert into cell (%1$s,%2$s)",
+ rowString, columnString));
+ description.append('\n');
+ description.append(String.format("Align %1$s, %2$s",
+ horizontalType.name().toLowerCase(Locale.US),
+ verticalType.name().toLowerCase(Locale.US)));
+ feedback.tooltip = description.toString();
}
}
@@ -713,16 +767,46 @@ public class GridDropHandler {
String fqcn = element.getFqcn();
INode newChild = targetNode.appendChild(fqcn);
+ int column = mColumnMatch.cellIndex;
if (mColumnMatch.createCell) {
- mGrid.addColumn(mColumnMatch.cellIndex,
+ mGrid.addColumn(column,
newChild, UNDEFINED, false, UNDEFINED, UNDEFINED);
}
+ int row = mRowMatch.cellIndex;
if (mRowMatch.createCell) {
- mGrid.addRow(mRowMatch.cellIndex, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED);
+ mGrid.addRow(row, newChild, UNDEFINED, false, UNDEFINED, UNDEFINED);
}
- mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, mColumnMatch.cellIndex);
- mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, mRowMatch.cellIndex);
+ mGrid.setGridAttribute(newChild, ATTR_LAYOUT_COLUMN, column);
+ mGrid.setGridAttribute(newChild, ATTR_LAYOUT_ROW, row);
+
+ int gravity = 0;
+ if (mColumnMatch.type == SegmentType.RIGHT) {
+ gravity |= GravityHelper.GRAVITY_RIGHT;
+ } else if (mColumnMatch.type == SegmentType.CENTER_HORIZONTAL) {
+ gravity |= GravityHelper.GRAVITY_CENTER_HORIZ;
+ }
+ if (mRowMatch.type == SegmentType.BASELINE) {
+ // There *is* no baseline gravity constant, instead, leave the
+ // vertical gravity unspecified and GridLayout will treat it as
+ // baseline alignment
+ //gravity |= GravityHelper.GRAVITY_BASELINE;
+ } else if (mRowMatch.type == SegmentType.BOTTOM) {
+ gravity |= GravityHelper.GRAVITY_BOTTOM;
+ } else if (mRowMatch.type == SegmentType.CENTER_VERTICAL) {
+ gravity |= GravityHelper.GRAVITY_CENTER_VERT;
+ }
+ if (!GravityHelper.isConstrainedHorizontally(gravity)) {
+ gravity |= GravityHelper.GRAVITY_LEFT;
+ }
+ if (!GravityHelper.isConstrainedVertically(gravity)) {
+ gravity |= GravityHelper.GRAVITY_TOP;
+ }
+ mGrid.setGridAttribute(newChild, ATTR_LAYOUT_GRAVITY, getGravity(gravity));
+
+ if (mGrid.declaredColumnCount == UNDEFINED || mGrid.declaredColumnCount < column + 1) {
+ mGrid.setGridAttribute(mGrid.layout, ATTR_COLUMN_COUNT, column + 1);
+ }
return newChild;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java
index 9e7cfae..7e2d3a7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridLayoutPainter.java
@@ -282,10 +282,12 @@ public class GridLayoutPainter {
gc.drawRect(b.x + 2 * radius, b.y + 2 * radius,
b.x2() - 2 * radius, b.y2() - 2 * radius);
- int column = data.getColumnMatch().cellIndex;
- int row = data.getRowMatch().cellIndex;
- boolean createColumn = data.getColumnMatch().createCell;
- boolean createRow = data.getRowMatch().createCell;
+ GridMatch columnMatch = data.getColumnMatch();
+ GridMatch rowMatch = data.getRowMatch();
+ int column = columnMatch.cellIndex;
+ int row = rowMatch.cellIndex;
+ boolean createColumn = columnMatch.createCell;
+ boolean createRow = rowMatch.createCell;
Rect cellBounds = grid.getCellBounds(row, column, 1, 1);
@@ -312,7 +314,22 @@ public class GridLayoutPainter {
}
gc.useStyle(DrawingStyle.DROP_PREVIEW);
- mRule.drawElement(gc, first, offsetX, offsetY);
+
+ Rect bounds = first.getBounds();
+ int x = offsetX;
+ int y = offsetY;
+ if (columnMatch.type == SegmentType.RIGHT) {
+ x += cellBounds.w - bounds.w;
+ } else if (columnMatch.type == SegmentType.CENTER_HORIZONTAL) {
+ x += cellBounds.w / 2 - bounds.w / 2;
+ }
+ if (rowMatch.type == SegmentType.BOTTOM) {
+ y += cellBounds.h - bounds.h;
+ } else if (rowMatch.type == SegmentType.CENTER_VERTICAL) {
+ y += cellBounds.h / 2 - bounds.h / 2;
+ }
+
+ mRule.drawElement(gc, first, x, y);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java
index 186e7d0..9bee343 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridMatch.java
@@ -124,6 +124,7 @@ class GridMatch implements Comparable<GridMatch> {
}
return String.format("Align bottom at y=%1d", matchedLine - layout.getBounds().y);
case CENTER_VERTICAL:
+ return "Center vertically";
case UNKNOWN:
default:
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java
index fa9a11f..a453147 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/grid/GridModel.java
@@ -80,7 +80,7 @@ public class GridModel {
static final int UNDEFINED = Integer.MIN_VALUE;
/** The size of spacers in the dimension that they are not defining */
- private static final int SPACER_SIZE_DP = 1;
+ static final int SPACER_SIZE_DP = 1;
/** Attribute value used for {@link #SPACER_SIZE_DP} */
private static final String SPACER_SIZE = String.format(VALUE_N_DP, SPACER_SIZE_DP);
@@ -361,7 +361,8 @@ public class GridModel {
if (mNamespace == null) {
mNamespace = ANDROID_URI;
- if (!layout.getFqcn().equals(FQCN_GRID_LAYOUT)) {
+ String fqcn = layout.getFqcn();
+ if (!fqcn.equals(GRID_LAYOUT) && !fqcn.equals(FQCN_GRID_LAYOUT)) {
mNamespace = mRulesEngine.getAppNameSpace();
}
}
@@ -681,11 +682,26 @@ public class GridModel {
if (cellBounds != null) {
int[] xs = cellBounds.getFirst();
int[] ys = cellBounds.getSecond();
+ Rect layoutBounds = layout.getBounds();
+
+ // Handle "blank" grid layouts: insert a fake grid of CELL_COUNT^2 cells
+ // where the user can do initial placement
+ if (actualColumnCount <= 1 && actualRowCount <= 1 && mChildViews.isEmpty()) {
+ final int CELL_COUNT = 1;
+ xs = new int[CELL_COUNT + 1];
+ ys = new int[CELL_COUNT + 1];
+ int cellWidth = layoutBounds.w / CELL_COUNT;
+ int cellHeight = layoutBounds.h / CELL_COUNT;
+
+ for (int i = 0; i <= CELL_COUNT; i++) {
+ xs[i] = i * cellWidth;
+ ys[i] = i * cellHeight;
+ }
+ }
actualColumnCount = xs.length - 1;
actualRowCount = ys.length - 1;
- Rect layoutBounds = layout.getBounds();
int layoutBoundsX = layoutBounds.x;
int layoutBoundsY = layoutBounds.y;
mLeft = new int[xs.length];
@@ -1810,7 +1826,7 @@ public class GridModel {
* Data about a view in a table; this is not the same as a cell because multiple views
* can share a single cell, and a view can span many cells.
*/
- class ViewData {
+ public class ViewData {
public final INode node;
public final int index;
public int row;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
index be928b0..e246975 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/resources/platform/AttributeInfo.java
@@ -20,6 +20,7 @@ import static com.android.SdkConstants.ANDROID_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.VALUE_FALSE;
import static com.android.SdkConstants.VALUE_TRUE;
import static com.android.ide.common.api.IAttributeInfo.Format.BOOLEAN;
@@ -211,6 +212,11 @@ public class AttributeInfo implements IAttributeInfo {
// All other formats require a nonempty string
if (value.isEmpty()) {
+ // Except for flags
+ if (mFormats.contains(FLAG)) {
+ return true;
+ }
+
return false;
}
char first = value.charAt(0);
@@ -262,6 +268,14 @@ public class AttributeInfo implements IAttributeInfo {
//String name = url.substring(nameBegin);
return true;
}
+ } else if (value.startsWith(PREFIX_THEME_REF)) {
+ if (projectResources != null) {
+ return projectResources.hasResourceItem(ResourceType.ATTR,
+ value.substring(PREFIX_THEME_REF.length()));
+ } else {
+ // Until proven otherwise
+ return true;
+ }
}
}
@@ -290,7 +304,7 @@ public class AttributeInfo implements IAttributeInfo {
}
if (mFormats.contains(BOOLEAN)) {
- if (value.equals(VALUE_TRUE) || value.equals(VALUE_FALSE)) {
+ if (value.equalsIgnoreCase(VALUE_TRUE) || value.equalsIgnoreCase(VALUE_FALSE)) {
return true;
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
index e9cee47..76808e4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt;
import static com.android.SdkConstants.DOT_AIDL;
import static com.android.SdkConstants.DOT_DEP;
+import static com.android.SdkConstants.DOT_FS;
import static com.android.SdkConstants.DOT_JAVA;
import static com.android.SdkConstants.DOT_RS;
@@ -118,6 +119,8 @@ public class AdtConstants {
public final static String RE_AIDL_EXT = "\\" + DOT_AIDL + "$"; //$NON-NLS-1$ //$NON-NLS-2$
/** Regexp for rs extension, i.e. "\.rs$" */
public final static String RE_RS_EXT = "\\" + DOT_RS + "$"; //$NON-NLS-1$ //$NON-NLS-2$
+ /** Regexp for rs extension, i.e. "\.fs$" */
+ public final static String RE_FS_EXT = "\\" + DOT_FS + "$"; //$NON-NLS-1$ //$NON-NLS-2$
/** Regexp for .d extension, i.e. "\.d$" */
public final static String RE_DEP_EXT = "\\" + DOT_DEP + "$"; //$NON-NLS-1$ //$NON-NLS-2$
@@ -148,6 +151,10 @@ public class AdtConstants {
* when an AndroidClasspathContainerInitializer has succeeded in creating an
* AndroidClasspathContainer */
public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$
+ /** Marker for Android Build Tools errors.
+ * This is not cleared on each build like other markers. Instead, it's cleared
+ * when the build tools are setup in the projectState. */
+ public final static String MARKER_BUILD_TOOLS = AdtPlugin.PLUGIN_ID + ".buildToolsProblem"; //$NON-NLS-1$
/** Marker for Android Dependency errors.
* This is not cleared on each build like other markers. Instead, it's cleared
* when a LibraryClasspathContainerInitializer has succeeded in creating a
@@ -218,4 +225,7 @@ public class AdtConstants {
/** Documentation marker for elements, attributes etc that should be hidden */
public static final String DOC_HIDE = "@hide"; //$NON-NLS-1$
+
+ public static final String DEX_OPTIONS_FORCEJUMBO = "dex.force.jumbo"; //$NON-NLS-1$
+ public static final String DEX_OPTIONS_DISABLE_MERGER = "dex.disable.merger"; //$NON-NLS-1$
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index a7ef6c6..7aec8f5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
@@ -265,16 +265,6 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
// Listen on resource file edits for updates to file inclusion
IncludeFinder.start();
-
- // Parse the SDK content.
- // This is deferred in separate jobs to avoid blocking the bundle start.
- final boolean isSdkLocationValid = checkSdkLocationAndId();
- if (isSdkLocationValid) {
- // parse the SDK resources.
- // Wait 2 seconds before starting the job. This leaves some time to the
- // other bundles to initialize.
- parseSdkContent(2000 /*milliseconds*/);
- }
}
/*
@@ -303,6 +293,16 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
/** Called when the workbench has been started */
public void workbenchStarted() {
+ // Parse the SDK content.
+ // This is deferred in separate jobs to avoid blocking the bundle start.
+ final boolean isSdkLocationValid = checkSdkLocationAndId();
+ if (isSdkLocationValid) {
+ // parse the SDK resources.
+ // Wait 2 seconds before starting the job. This leaves some time to the
+ // other bundles to initialize.
+ parseSdkContent(2000 /*milliseconds*/);
+ }
+
Display display = getDisplay();
mRed = new Color(display, 0xFF, 0x00, 0x00);
@@ -326,19 +326,50 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
return sPlugin;
}
+ /**
+ * Returns the current display, if any
+ *
+ * @return the display
+ */
+ @NonNull
public static Display getDisplay() {
- IWorkbench bench = null;
synchronized (AdtPlugin.class) {
- if (sPlugin == null) {
- return null;
+ if (sPlugin != null) {
+ IWorkbench bench = sPlugin.getWorkbench();
+ if (bench != null) {
+ Display display = bench.getDisplay();
+ if (display != null) {
+ return display;
+ }
+ }
}
- bench = sPlugin.getWorkbench();
}
- if (bench != null) {
- return bench.getDisplay();
+ Display display = Display.getCurrent();
+ if (display != null) {
+ return display;
}
- return null;
+
+ return Display.getDefault();
+ }
+
+ /**
+ * Returns the shell, if any
+ *
+ * @return the shell, if any
+ */
+ @Nullable
+ public static Shell getShell() {
+ Display display = AdtPlugin.getDisplay();
+ Shell shell = display.getActiveShell();
+ if (shell == null) {
+ Shell[] shells = display.getShells();
+ if (shells.length > 0) {
+ shell = shells[0];
+ }
+ }
+
+ return shell;
}
/** Returns the adb path relative to the sdk folder */
@@ -513,6 +544,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
* @param string the string to be searched for
* @return true if the file is found and contains the given string anywhere within it
*/
+ @SuppressWarnings("resource") // Closed by streamContains
public static boolean fileContains(IFile file, String string) {
InputStream contents = null;
try {
@@ -1197,14 +1229,13 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
}
private void openSdkManager() {
- // Windows only: open the standalone external SDK Manager since we know
+ // Open the standalone external SDK Manager since we know
// that ADT on Windows is bound to be locking some SDK folders.
- // Also when this is invoked becasue SdkManagerAction.run() fails, this
+ //
+ // Also when this is invoked because SdkManagerAction.run() fails, this
// test will fail and we'll fallback on using the internal one.
- if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
- if (SdkManagerAction.openExternalSdkManager()) {
- return;
- }
+ if (SdkManagerAction.openExternalSdkManager()) {
+ return;
}
// Otherwise open the regular SDK Manager bundled within ADT
@@ -1402,7 +1433,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
// project that have been resolved before the sdk was loaded
// will have a ProjectState where the IAndroidTarget is null
// so we load the target now that the SDK is loaded.
- sdk.loadTarget(Sdk.getProjectState(iProject));
+ sdk.loadTargetAndBuildTools(Sdk.getProjectState(iProject));
list.add(javaProject);
}
}
@@ -1580,7 +1611,10 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
monitor.addFileListener(new IFileListener() {
@Override
public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (!isAndroidProject) {
+ return;
+ }
if (flags == IResourceDelta.MARKERS || !SdkConstants.EXT_XML.equals(extension)) {
// ONLY the markers changed, or not XML file: not relevant to this listener
return;
@@ -1709,7 +1743,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
(List<ITargetChangeListener>)mTargetChangeListeners.clone();
Display display = AdtPlugin.getDisplay();
- if (display == null) {
+ if (display == null || display.isDisposed()) {
return;
}
display.asyncExec(new Runnable() {
@@ -1850,7 +1884,7 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
// --------- ILogger methods -----------
@Override
- public void error(Throwable t, String format, Object... args) {
+ public void error(@Nullable Throwable t, @Nullable String format, Object... args) {
if (t != null) {
log(t, format, args);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index d5fa567..697a0bc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -18,15 +18,20 @@ package com.android.ide.eclipse.adt;
import static com.android.SdkConstants.TOOLS_PREFIX;
import static com.android.SdkConstants.TOOLS_URI;
+import static org.eclipse.ui.IWorkbenchPage.MATCH_INPUT;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.PkgProps;
@@ -34,6 +39,10 @@ import com.android.utils.XmlUtils;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
+import org.eclipse.core.filebuffers.FileBuffers;
+import org.eclipse.core.filebuffers.ITextFileBuffer;
+import org.eclipse.core.filebuffers.ITextFileBufferManager;
+import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -47,6 +56,7 @@ import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IJavaProject;
@@ -68,10 +78,16 @@ import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.editors.text.TextFileDocumentProvider;
+import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -80,6 +96,8 @@ import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
@@ -348,6 +366,47 @@ public class AdtUtils {
}
/**
+ * Looks through the open editors and returns the editors that have the
+ * given file as input.
+ *
+ * @param file the file to search for
+ * @param restore whether editors should be restored (if they have an open
+ * tab, but the editor hasn't been restored since the most recent
+ * IDE start yet
+ * @return a collection of editors
+ */
+ @NonNull
+ public static Collection<IEditorPart> findEditorsFor(@NonNull IFile file, boolean restore) {
+ FileEditorInput input = new FileEditorInput(file);
+ List<IEditorPart> result = null;
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow[] windows = workbench.getWorkbenchWindows();
+ for (IWorkbenchWindow window : windows) {
+ IWorkbenchPage[] pages = window.getPages();
+ for (IWorkbenchPage page : pages) {
+ IEditorReference[] editors = page.findEditors(input, null, MATCH_INPUT);
+ if (editors != null) {
+ for (IEditorReference reference : editors) {
+ IEditorPart editor = reference.getEditor(restore);
+ if (editor != null) {
+ if (result == null) {
+ result = new ArrayList<IEditorPart>();
+ }
+ result.add(editor);
+ }
+ }
+ }
+ }
+ }
+
+ if (result == null) {
+ return Collections.emptyList();
+ }
+
+ return result;
+ }
+
+ /**
* Attempts to convert the given {@link URL} into a {@link File}.
*
* @param url the {@link URL} to be converted
@@ -651,6 +710,23 @@ public class AdtUtils {
}
/**
+ * Returns the XML editor for the given editor part
+ *
+ * @param part the editor part to look up the editor for
+ * @return the editor or null if this part is not an XML editor
+ */
+ @Nullable
+ public static AndroidXmlEditor getXmlEditor(@NonNull IEditorPart part) {
+ if (part instanceof AndroidXmlEditor) {
+ return (AndroidXmlEditor) part;
+ } else if (part instanceof GraphicalEditorPart) {
+ ((GraphicalEditorPart) part).getEditorDelegate().getEditor();
+ }
+
+ return null;
+ }
+
+ /**
* Sets the given tools: attribute in the given XML editor document, adding
* the tools name space declaration if necessary, formatting the affected
* document region, and optionally comma-appending to an existing value and
@@ -678,11 +754,11 @@ public class AdtUtils {
editor.wrapUndoEditXmlModel(description, new Runnable() {
@Override
public void run() {
- String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI, null);
+ String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI, null, true);
if (prefix == null) {
// Add in new prefix...
prefix = XmlUtils.lookupNamespacePrefix(element,
- TOOLS_URI, TOOLS_PREFIX);
+ TOOLS_URI, TOOLS_PREFIX, true /*create*/);
if (value != null) {
// ...and ensure that the header is formatted such that
// the XML namespace declaration is placed in the right
@@ -740,41 +816,6 @@ public class AdtUtils {
}
/**
- * Returns the applicable build code (for
- * {@code android.os.Build.VERSION_CODES}) for the corresponding API level,
- * or null if it's unknown.
- *
- * @param api the API level to look up a version code for
- * @return the corresponding build code field name, or null
- */
- @Nullable
- public static String getBuildCodes(int api) {
- // See http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
- switch (api) {
- case 1: return "BASE"; //$NON-NLS-1$
- case 2: return "BASE_1_1"; //$NON-NLS-1$
- case 3: return "CUPCAKE"; //$NON-NLS-1$
- case 4: return "DONUT"; //$NON-NLS-1$
- case 5: return "ECLAIR"; //$NON-NLS-1$
- case 6: return "ECLAIR_0_1"; //$NON-NLS-1$
- case 7: return "ECLAIR_MR1"; //$NON-NLS-1$
- case 8: return "FROYO"; //$NON-NLS-1$
- case 9: return "GINGERBREAD"; //$NON-NLS-1$
- case 10: return "GINGERBREAD_MR1"; //$NON-NLS-1$
- case 11: return "HONEYCOMB"; //$NON-NLS-1$
- case 12: return "HONEYCOMB_MR1"; //$NON-NLS-1$
- case 13: return "HONEYCOMB_MR2"; //$NON-NLS-1$
- case 14: return "ICE_CREAM_SANDWICH"; //$NON-NLS-1$
- case 15: return "ICE_CREAM_SANDWICH_MR1"; //$NON-NLS-1$
- case 16: return "JELLY_BEAN"; //$NON-NLS-1$
- // If you add more versions here, also update #getAndroidName and
- // LintConstants#HIGHEST_KNOWN_API
- }
-
- return null;
- }
-
- /**
* Returns a string label for the given target, of the form
* "API 16: Android 4.1 (Jelly Bean)".
*
@@ -802,6 +843,97 @@ public class AdtUtils {
}
/**
+ * Sets the given tools: attribute in the given XML editor document, adding
+ * the tools name space declaration if necessary, and optionally
+ * comma-appending to an existing value.
+ *
+ * @param file the file associated with the element
+ * @param element the associated element
+ * @param description the description of the attribute (shown in the undo
+ * event)
+ * @param name the name of the attribute
+ * @param value the attribute value
+ * @param appendValue if true, add this value as a comma separated value to
+ * the existing attribute value, if any
+ */
+ public static void setToolsAttribute(
+ @NonNull final IFile file,
+ @NonNull final Element element,
+ @NonNull final String description,
+ @NonNull final String name,
+ @Nullable final String value,
+ final boolean appendValue) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ if (modelManager == null) {
+ return;
+ }
+
+ try {
+ IStructuredModel model = null;
+ if (model == null) {
+ model = modelManager.getModelForEdit(file);
+ }
+ if (model != null) {
+ try {
+ model.aboutToChangeModel();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Document doc = domModel.getDocument();
+ if (doc != null && element.getOwnerDocument() == doc) {
+ String prefix = XmlUtils.lookupNamespacePrefix(element, TOOLS_URI,
+ null, true);
+ if (prefix == null) {
+ // Add in new prefix...
+ prefix = XmlUtils.lookupNamespacePrefix(element,
+ TOOLS_URI, TOOLS_PREFIX, true);
+ }
+
+ String v = value;
+ if (appendValue && v != null) {
+ String prev = element.getAttributeNS(TOOLS_URI, name);
+ if (prev.length() > 0) {
+ v = prev + ',' + value;
+ }
+ }
+
+ // Use the non-namespace form of set attribute since we can't
+ // reference the namespace until the model has been reloaded
+ if (v != null) {
+ element.setAttribute(prefix + ':' + name, v);
+ } else {
+ element.removeAttribute(prefix + ':' + name);
+ }
+ }
+ }
+ } finally {
+ model.changedModel();
+ String updated = model.getStructuredDocument().get();
+ model.releaseFromEdit();
+ model.save(file);
+
+ // Must also force a save on disk since the above model.save(file) often
+ // (always?) has no effect.
+ ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
+ NullProgressMonitor monitor = new NullProgressMonitor();
+ IPath path = file.getFullPath();
+ manager.connect(path, LocationKind.IFILE, monitor);
+ try {
+ ITextFileBuffer buffer = manager.getTextFileBuffer(path,
+ LocationKind.IFILE);
+ IDocument currentDocument = buffer.getDocument();
+ currentDocument.set(updated);
+ buffer.commit(monitor, true);
+ } finally {
+ manager.disconnect(path, LocationKind.IFILE, monitor);
+ }
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ /**
* Returns the Android version and code name of the given API level
*
* @param api the api level
@@ -826,8 +958,9 @@ public class AdtUtils {
case 14: return "API 14: Android 4.0 (IceCreamSandwich)";
case 15: return "API 15: Android 4.0.3 (IceCreamSandwich)";
case 16: return "API 16: Android 4.1 (Jelly Bean)";
- // If you add more versions here, also update #getBuildCodes and
- // LintConstants#HIGHEST_KNOWN_API
+ case 17: return "API 17: Android 4.2 (Jelly Bean)";
+ // If you add more versions here, also update LintUtils#getBuildCodes and
+ // SdkConstants#HIGHEST_KNOWN_API
default: {
// Consult SDK manager to see if we know any more (later) names,
@@ -857,7 +990,7 @@ public class AdtUtils {
* @return the highest known API number
*/
public static int getHighestKnownApiLevel() {
- return SdkConstants.HIGHEST_KNOWN_API;
+ return SdkVersionInfo.HIGHEST_KNOWN_API;
}
/**
@@ -1236,6 +1369,62 @@ public class AdtUtils {
}
/**
+ * Returns all resource variations for the given file
+ *
+ * @param file resource file, which should be an XML file in one of the
+ * various resource folders, e.g. res/layout, res/values-xlarge, etc.
+ * @param includeSelf if true, include the file itself in the list,
+ * otherwise exclude it
+ * @return a list of all the resource variations
+ */
+ public static List<IFile> getResourceVariations(@Nullable IFile file, boolean includeSelf) {
+ if (file == null) {
+ return Collections.emptyList();
+ }
+
+ // Compute the set of layout files defining this layout resource
+ List<IFile> variations = new ArrayList<IFile>();
+ String name = file.getName();
+ IContainer parent = file.getParent();
+ if (parent != null) {
+ IContainer resFolder = parent.getParent();
+ if (resFolder != null) {
+ String parentName = parent.getName();
+ String prefix = parentName;
+ int qualifiers = prefix.indexOf('-');
+
+ if (qualifiers != -1) {
+ parentName = prefix.substring(0, qualifiers);
+ prefix = prefix.substring(0, qualifiers + 1);
+ } else {
+ prefix = prefix + '-';
+ }
+ try {
+ for (IResource resource : resFolder.members()) {
+ String n = resource.getName();
+ if ((n.startsWith(prefix) || n.equals(parentName))
+ && resource instanceof IContainer) {
+ IContainer layoutFolder = (IContainer) resource;
+ IResource r = layoutFolder.findMember(name);
+ if (r instanceof IFile) {
+ IFile variation = (IFile) r;
+ if (!includeSelf && file.equals(variation)) {
+ continue;
+ }
+ variations.add(variation);
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ return variations;
+ }
+
+ /**
* Returns whether the current thread is the UI thread
*
* @return true if the current thread is the UI thread
@@ -1287,4 +1476,108 @@ public class AdtUtils {
return s;
}
+
+ /**
+ * Looks up the {@link ResourceFolderType} corresponding to a given
+ * {@link ResourceType}: the folder where those resources can be found.
+ * <p>
+ * Note that {@link ResourceType#ID} is a special case: it can not just
+ * be defined in {@link ResourceFolderType#VALUES}, but it can also be
+ * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
+ * {@link ResourceFolderType#MENU} folders.
+ *
+ * @param type the resource type
+ * @return the corresponding resource folder type
+ */
+ @NonNull
+ public static ResourceFolderType getFolderTypeFor(@NonNull ResourceType type) {
+ switch (type) {
+ case ANIM:
+ return ResourceFolderType.ANIM;
+ case ANIMATOR:
+ return ResourceFolderType.ANIMATOR;
+ case ARRAY:
+ return ResourceFolderType.VALUES;
+ case COLOR:
+ return ResourceFolderType.COLOR;
+ case DRAWABLE:
+ return ResourceFolderType.DRAWABLE;
+ case INTERPOLATOR:
+ return ResourceFolderType.INTERPOLATOR;
+ case LAYOUT:
+ return ResourceFolderType.LAYOUT;
+ case MENU:
+ return ResourceFolderType.MENU;
+ case MIPMAP:
+ return ResourceFolderType.MIPMAP;
+ case RAW:
+ return ResourceFolderType.RAW;
+ case XML:
+ return ResourceFolderType.XML;
+ case ATTR:
+ case BOOL:
+ case DECLARE_STYLEABLE:
+ case DIMEN:
+ case FRACTION:
+ case ID:
+ case INTEGER:
+ case PLURALS:
+ case PUBLIC:
+ case STRING:
+ case STYLE:
+ case STYLEABLE:
+ return ResourceFolderType.VALUES;
+ default:
+ assert false : type;
+ return ResourceFolderType.VALUES;
+
+ }
+ }
+
+ /**
+ * Looks up the {@link ResourceType} defined in a given {@link ResourceFolderType}.
+ * <p>
+ * Note that for {@link ResourceFolderType#VALUES} there are many, many
+ * different types of resources that can be defined, so this method returns
+ * {@code null} for that scenario.
+ * <p>
+ * Note also that {@link ResourceType#ID} is a special case: it can not just
+ * be defined in {@link ResourceFolderType#VALUES}, but it can also be
+ * defined inline via {@code @+id} in {@link ResourceFolderType#LAYOUT} and
+ * {@link ResourceFolderType#MENU} folders.
+ *
+ * @param folderType the resource folder type
+ * @return the corresponding resource type, or null if {@code folderType} is
+ * {@link ResourceFolderType#VALUES}
+ */
+ @Nullable
+ public static ResourceType getResourceTypeFor(@NonNull ResourceFolderType folderType) {
+ switch (folderType) {
+ case ANIM:
+ return ResourceType.ANIM;
+ case ANIMATOR:
+ return ResourceType.ANIMATOR;
+ case COLOR:
+ return ResourceType.COLOR;
+ case DRAWABLE:
+ return ResourceType.DRAWABLE;
+ case INTERPOLATOR:
+ return ResourceType.INTERPOLATOR;
+ case LAYOUT:
+ return ResourceType.LAYOUT;
+ case MENU:
+ return ResourceType.MENU;
+ case MIPMAP:
+ return ResourceType.MIPMAP;
+ case RAW:
+ return ResourceType.RAW;
+ case XML:
+ return ResourceType.XML;
+ case VALUES:
+ return null;
+ default:
+ assert false : folderType;
+ return null;
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
index b9505da..dfc2e33 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/VersionCheck.java
@@ -21,7 +21,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler.Solution;
import com.android.ide.eclipse.adt.Messages;
-import com.android.sdklib.internal.repository.packages.FullRevision;
+import com.android.sdklib.repository.FullRevision;
import com.android.sdklib.repository.PkgProps;
import org.osgi.framework.Constants;
@@ -49,7 +49,7 @@ public final class VersionCheck {
/**
* The minimum version of the SDK Tools that this version of ADT requires.
*/
- private final static FullRevision MIN_TOOLS_REV = new FullRevision(20);
+ private final static FullRevision MIN_TOOLS_REV = new FullRevision(22, 0, 0, 0);
/**
* Pattern to get the minimum plugin version supported by the SDK. This is read from
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java
index 44321aa..c21c8a4 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AddSupportJarAction.java
@@ -184,7 +184,7 @@ public class AddSupportJarAction implements IObjectActionDelegate {
// and get the installation path of the library.
AdtUpdateDialog window = new AdtUpdateDialog(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
new AdtConsoleSdkLog(),
sdkLocation);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AvdManagerAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AvdManagerAction.java
index 46177b0..2597090 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AvdManagerAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/AvdManagerAction.java
@@ -55,7 +55,7 @@ public class AvdManagerAction implements IWorkbenchWindowActionDelegate, IObject
// Runs the updater window, directing all logs to the ADT console.
AvdManagerWindow window = new AvdManagerWindow(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
new AdtConsoleSdkLog(),
sdk.getSdkLocation(),
AvdInvocationContext.IDE);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
index 9cae8a4..9d33230 100755..100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/actions/SdkManagerAction.java
@@ -20,15 +20,14 @@ import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.build.DexWrapper;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.io.FileOp;
+import com.android.sdklib.repository.ISdkChangeListener;
import com.android.sdklib.util.GrabProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.Wait;
-import com.android.sdkuilib.repository.ISdkChangeListener;
import com.android.sdkuilib.repository.SdkUpdaterWindow;
import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
@@ -137,7 +136,7 @@ public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObject
final AtomicBoolean returnValue = new AtomicBoolean(false);
final CloseableProgressMonitorDialog p =
- new CloseableProgressMonitorDialog(AdtPlugin.getDisplay().getActiveShell());
+ new CloseableProgressMonitorDialog(AdtPlugin.getShell());
p.setOpenOnRun(true);
try {
p.run(true /*fork*/, true /*cancelable*/, new IRunnableWithProgress() {
@@ -262,7 +261,7 @@ public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObject
// log window now.)
SdkUpdaterWindow window = new SdkUpdaterWindow(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
new AdtConsoleSdkLog() {
@Override
public void info(@NonNull String msgFormat, Object... args) {
@@ -313,9 +312,7 @@ public class SdkManagerAction implements IWorkbenchWindowActionDelegate, IObject
if (sdk != null) {
sdk.unloadTargetData(true /*preventReload*/);
-
- DexWrapper dx = sdk.getDexWrapper();
- dx.unload();
+ sdk.unloadDexWrappers();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
index 3e2bd67..5cfeebb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/AssetType.java
@@ -68,7 +68,7 @@ public enum AssetType {
/** Whether this asset type needs a shape parameter */
boolean needsShape() {
- return this == LAUNCHER || this == NOTIFICATION;
+ return this == LAUNCHER;
}
/** Whether this asset type needs foreground and background color parameters */
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
index 426dbae..17336ad 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.assetstudio;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.DEFAULT_LAUNCHER_ICON;
-
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import com.android.annotations.NonNull;
@@ -106,7 +105,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
ModifyListener {
private final CreateAssetSetWizardState mValues;
- private static final int PREVIEW_AREA_WIDTH = 120;
+ private static final int PREVIEW_AREA_WIDTH = 144;
private boolean mShown;
@@ -481,7 +480,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
// Initial image - use the most recently used image, or the default launcher
// icon created in our default projects, if there
if (mValues.imagePath != null) {
- sImagePath = mValues.imagePath.getPath();;
+ sImagePath = mValues.imagePath.getPath();
}
if (sImagePath == null) {
IProject project = mValues.project;
@@ -1149,10 +1148,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
}
case NOTIFICATION: {
generator = new NotificationIconGenerator();
- NotificationIconGenerator.NotificationOptions notificationOptions =
- new NotificationIconGenerator.NotificationOptions();
- notificationOptions.shape = mValues.shape;
- options = notificationOptions;
+ options = new NotificationIconGenerator.NotificationOptions();
break;
}
case TAB:
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
index defaca6..98a1fab 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AaptQuickFix.java
@@ -20,6 +20,7 @@ import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.XMLNS_ANDROID;
import static com.android.SdkConstants.XMLNS_URI;
+import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
@@ -343,7 +344,7 @@ public class AaptQuickFix implements IMarkerResolutionGenerator2, IQuickAssistPr
}
private void perform() {
- Pair<ResourceType,String> resource = ResourceHelper.parseResource(mResource);
+ Pair<ResourceType,String> resource = ResourceRepository.parseResource(mResource);
ResourceType type = resource.getFirst();
String name = resource.getSecond();
String value = ""; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
index 52e887a..806fa9c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/AidlProcessor.java
@@ -17,12 +17,14 @@
package com.android.ide.eclipse.adt.internal.build;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.io.FileOp;
@@ -45,7 +47,9 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -65,6 +69,7 @@ public class AidlProcessor extends SourceProcessor {
*/
private static Pattern sAidlPattern1 = Pattern.compile("^(.+?):(\\d+):?\\s(.+)$"); //$NON-NLS-1$
+ private final static Set<String> EXTENSIONS = Collections.singleton(SdkConstants.EXT_AIDL);
private enum AidlType {
UNKNOWN, INTERFACE, PARCELABLE;
@@ -78,13 +83,14 @@ public class AidlProcessor extends SourceProcessor {
// "^\\s*interface\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?:\\{.*)?$");
- public AidlProcessor(IJavaProject javaProject, IFolder genFolder) {
- super(javaProject, genFolder);
+ public AidlProcessor(@NonNull IJavaProject javaProject, @NonNull BuildToolInfo buildToolInfo,
+ @NonNull IFolder genFolder) {
+ super(javaProject, buildToolInfo, genFolder);
}
@Override
- protected String getExtension() {
- return SdkConstants.EXT_AIDL;
+ protected Set<String> getExtensions() {
+ return EXTENSIONS;
}
@Override
@@ -92,16 +98,15 @@ public class AidlProcessor extends SourceProcessor {
return PROPERTY_COMPILE_AIDL;
}
- @SuppressWarnings("deprecation")
@Override
protected void doCompileFiles(List<IFile> sources, BaseBuilder builder,
- IProject project, IAndroidTarget projectTarget, int targetApi,
+ IProject project, IAndroidTarget projectTarget,
List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut,
IProgressMonitor monitor) throws CoreException {
// create the command line
List<String> commandList = new ArrayList<String>(
4 + sourceFolders.size() + libraryProjectsOut.size());
- commandList.add(projectTarget.getPath(IAndroidTarget.AIDL));
+ commandList.add(getBuildToolInfo().getPath(BuildToolInfo.PathId.AIDL));
commandList.add(quote("-p" + projectTarget.getPath(IAndroidTarget.ANDROID_AIDL))); //$NON-NLS-1$
// since the path are relative to the workspace and not the project itself, we need
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
index 5fb6660..da8c2ea 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/BuildHelper.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.build;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -26,6 +27,7 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.prefs.AndroidLocation.AndroidLocationException;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
import com.android.sdklib.build.ApkBuilder;
@@ -39,6 +41,9 @@ import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import com.android.sdklib.util.GrabProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.IProcessOutput;
import com.android.sdklib.util.GrabProcessOutput.Wait;
+import com.google.common.hash.HashCode;
+import com.google.common.hash.HashFunction;
+import com.google.common.hash.Hashing;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -101,9 +106,16 @@ public class BuildHelper {
private static final String COMMAND_CRUNCH = "crunch"; //$NON-NLS-1$
private static final String COMMAND_PACKAGE = "package"; //$NON-NLS-1$
+ @NonNull
private final IProject mProject;
+ @NonNull
+ private final BuildToolInfo mBuildToolInfo;
+ @NonNull
private final AndroidPrintStream mOutStream;
+ @NonNull
private final AndroidPrintStream mErrStream;
+ private final boolean mForceJumbo;
+ private final boolean mDisableDexMerger;
private final boolean mVerbose;
private final boolean mDebugMode;
@@ -132,14 +144,20 @@ public class BuildHelper {
* @param verbose
* @throws CoreException
*/
- public BuildHelper(IProject project, AndroidPrintStream outStream,
- AndroidPrintStream errStream, boolean debugMode, boolean verbose,
- ResourceMarker resMarker) throws CoreException {
+ public BuildHelper(@NonNull IProject project,
+ @NonNull BuildToolInfo buildToolInfo,
+ @NonNull AndroidPrintStream outStream,
+ @NonNull AndroidPrintStream errStream,
+ boolean forceJumbo, boolean disableDexMerger, boolean debugMode,
+ boolean verbose, ResourceMarker resMarker) throws CoreException {
mProject = project;
+ mBuildToolInfo = buildToolInfo;
mOutStream = outStream;
mErrStream = errStream;
mDebugMode = debugMode;
mVerbose = verbose;
+ mForceJumbo = forceJumbo;
+ mDisableDexMerger = disableDexMerger;
gatherPaths(resMarker);
}
@@ -683,7 +701,7 @@ public class BuildHelper {
// get the dex wrapper
Sdk sdk = Sdk.getCurrent();
- DexWrapper wrapper = sdk.getDexWrapper();
+ DexWrapper wrapper = sdk.getDexWrapper(mBuildToolInfo);
if (wrapper == null) {
throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
@@ -704,24 +722,28 @@ public class BuildHelper {
// replace the libs by their dexed versions (dexing them if needed.)
List<String> finalInputPaths = new ArrayList<String>(inputPaths.size());
- if (inputPaths.size() == 1) {
+ if (mDisableDexMerger || inputPaths.size() == 1) {
// only one input, no need to put a pre-dexed version, even if this path is
// just a jar file (case for proguard'ed builds)
finalInputPaths.addAll(inputPaths);
} else {
+
for (String input : inputPaths) {
File inputFile = new File(input);
if (inputFile.isDirectory()) {
finalInputPaths.add(input);
} else if (inputFile.isFile()) {
- File dexedLib = new File(dexedLibs, inputFile.getName());
+ String fileName = getDexFileName(inputFile);
+
+ File dexedLib = new File(dexedLibs, fileName);
String dexedLibPath = dexedLib.getAbsolutePath();
if (dexedLib.isFile() == false ||
dexedLib.lastModified() < inputFile.lastModified()) {
if (mVerbose) {
- mOutStream.println("Pre-Dexing " + input);
+ mOutStream.println(
+ String.format("Pre-Dexing %1$s -> %2$s", input, fileName));
}
if (dexedLib.isFile()) {
@@ -729,13 +751,19 @@ public class BuildHelper {
}
int res = wrapper.run(dexedLibPath, Collections.singleton(input),
- mVerbose, mOutStream, mErrStream);
+ mForceJumbo, mVerbose, mOutStream, mErrStream);
if (res != 0) {
// output error message and mark the project.
String message = String.format(Messages.Dalvik_Error_d, res);
throw new DexException(message);
}
+ } else {
+ if (mVerbose) {
+ mOutStream.println(
+ String.format("Using Pre-Dexed %1$s <- %2$s",
+ fileName, input));
+ }
}
finalInputPaths.add(dexedLibPath);
@@ -751,6 +779,7 @@ public class BuildHelper {
int res = wrapper.run(osOutFilePath,
finalInputPaths,
+ mForceJumbo,
mVerbose,
mOutStream, mErrStream);
@@ -775,6 +804,22 @@ public class BuildHelper {
}
}
+ private String getDexFileName(File inputFile) {
+ // get the filename
+ String name = inputFile.getName();
+ // remove the extension
+ int pos = name.lastIndexOf('.');
+ if (pos != -1) {
+ name = name.substring(0, pos);
+ }
+
+ // add a hash of the original file path
+ HashFunction hashFunction = Hashing.md5();
+ HashCode hashCode = hashFunction.hashString(inputFile.getAbsolutePath());
+
+ return name + "-" + hashCode.toString() + ".jar";
+ }
+
/**
* Executes aapt. If any error happen, files or the project will be marked.
* @param command The command for aapt to execute. Currently supported: package and crunch
@@ -796,7 +841,7 @@ public class BuildHelper {
String configFilter, int versionCode) throws AaptExecException, AaptResultException {
IAndroidTarget target = Sdk.getCurrent().getTarget(mProject);
- @SuppressWarnings("deprecation") String aapt = target.getPath(IAndroidTarget.AAPT);
+ String aapt = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
// Create the command line.
ArrayList<String> commandArray = new ArrayList<String>();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ConvertSwitchQuickFixProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ConvertSwitchQuickFixProcessor.java
index 5a71edc..a99dc76 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ConvertSwitchQuickFixProcessor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/ConvertSwitchQuickFixProcessor.java
@@ -185,7 +185,7 @@ public class ConvertSwitchQuickFixProcessor implements IQuickFixProcessor {
@Override
public void apply(IDocument document) {
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
ConvertSwitchDialog dialog = new ConvertSwitchDialog(shell, mExpression);
dialog.open();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java
index 710d257..1c7c2e3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/DexWrapper.java
@@ -53,6 +53,7 @@ public final class DexWrapper {
private Field mArgVerbose;
private Field mArgJarOutput;
private Field mArgFileNames;
+ private Field mArgForceJumbo;
private Field mConsoleOut;
private Field mConsoleErr;
@@ -92,6 +93,7 @@ public final class DexWrapper {
mArgJarOutput = argClass.getField("jarOutput"); //$NON-NLS-1$
mArgFileNames = argClass.getField("fileNames"); //$NON-NLS-1$
mArgVerbose = argClass.getField("verbose"); //$NON-NLS-1$
+ mArgForceJumbo = argClass.getField("forceJumbo"); //$NON-NLS-1$
mConsoleOut = consoleClass.getField("out"); //$NON-NLS-1$
mConsoleErr = consoleClass.getField("err"); //$NON-NLS-1$
@@ -140,6 +142,7 @@ public final class DexWrapper {
*
* @param osOutFilePath the OS path to the outputfile (classes.dex
* @param osFilenames list of input source files (.class and .jar files)
+ * @param forceJumbo force jumbo mode.
* @param verbose verbose mode.
* @param outStream the stdout console
* @param errStream the stderr console
@@ -147,13 +150,15 @@ public final class DexWrapper {
* @throws CoreException
*/
public synchronized int run(String osOutFilePath, Collection<String> osFilenames,
- boolean verbose, PrintStream outStream, PrintStream errStream) throws CoreException {
+ boolean forceJumbo, boolean verbose,
+ PrintStream outStream, PrintStream errStream) throws CoreException {
assert mRunMethod != null;
assert mArgConstructor != null;
assert mArgOutName != null;
assert mArgJarOutput != null;
assert mArgFileNames != null;
+ assert mArgForceJumbo != null;
assert mArgVerbose != null;
assert mConsoleOut != null;
assert mConsoleErr != null;
@@ -175,6 +180,7 @@ public final class DexWrapper {
mArgOutName.set(args, osOutFilePath);
mArgFileNames.set(args, osFilenames.toArray(new String[osFilenames.size()]));
mArgJarOutput.set(args, osOutFilePath.endsWith(SdkConstants.DOT_JAR));
+ mArgForceJumbo.set(args, forceJumbo);
mArgVerbose.set(args, verbose);
// call the run method
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/Messages.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/Messages.java
index 7a169f3..9ceba20 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/Messages.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/Messages.java
@@ -12,6 +12,8 @@ public class Messages extends NLS {
public static String AAPT_Exec_Error_d;
+ public static String AAPT_Exec_Error_Other_s;
+
public static String Added_s_s_Needs_Updating;
public static String AIDL_Exec_Error_s;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java
index 5b58c4f..af58e41 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/RenderScriptProcessor.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.build;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
@@ -25,7 +26,9 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceFolderType;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.Sets;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
@@ -47,6 +50,7 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -63,6 +67,12 @@ public class RenderScriptProcessor extends SourceProcessor {
*/
private static Pattern sLlvmPattern1 = Pattern.compile("^(.+?):(\\d+):(\\d+):\\s(.+)$"); //$NON-NLS-1$
+ private final static Set<String> EXTENSIONS = Sets.newHashSetWithExpectedSize(2);
+ static {
+ EXTENSIONS.add(SdkConstants.EXT_RS);
+ EXTENSIONS.add(SdkConstants.EXT_FS);
+ }
+
private static class RsChangeHandler extends SourceChangeHandler {
@Override
@@ -85,44 +95,71 @@ public class RenderScriptProcessor extends SourceProcessor {
// remove the file name segment
relative = relative.removeLastSegments(1);
// add the file name of a Renderscript file.
- relative = relative.append(file.getName().replaceAll(AdtConstants.RE_DEP_EXT,
- SdkConstants.DOT_RS));
-
- // now look for a match in the source folders.
- List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(
- processor.getJavaProject());
- IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
-
- for (IPath sourceFolderPath : sourceFolders) {
- IFolder sourceFolder = root.getFolder(sourceFolderPath);
- // we don't look in the 'gen' source folder as there will be no source in there.
- if (sourceFolder.exists() && sourceFolder.equals(genFolder) == false) {
- IFile sourceFile = sourceFolder.getFile(relative);
- SourceFileData data = processor.getFileData(sourceFile);
- if (data != null) {
- addFileToCompile(sourceFile);
- return true;
- }
- }
+ relative = relative.append(file.getName().replaceAll(
+ AdtConstants.RE_DEP_EXT, SdkConstants.DOT_RS));
+
+ if (!findInSourceFolders(processor, genFolder, relative)) {
+ // could be a FilterScript file?
+ relative = file.getFullPath().makeRelativeTo(genFolder.getFullPath());
+ // remove the file name segment
+ relative = relative.removeLastSegments(1);
+ // add the file name of a FilterScript file.
+ relative = relative.append(file.getName().replaceAll(
+ AdtConstants.RE_DEP_EXT, SdkConstants.DOT_FS));
+
+ return findInSourceFolders(processor, genFolder, relative);
}
+
+ return true;
}
return r;
}
+ private boolean findInSourceFolders(SourceProcessor processor, IFolder genFolder,
+ IPath relative) {
+ // now look for a match in the source folders.
+ List<IPath> sourceFolders = BaseProjectHelper.getSourceClasspaths(
+ processor.getJavaProject());
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+
+ for (IPath sourceFolderPath : sourceFolders) {
+ IFolder sourceFolder = root.getFolder(sourceFolderPath);
+ // we don't look in the 'gen' source folder as there will be no source in there.
+ if (sourceFolder.exists() && sourceFolder.equals(genFolder) == false) {
+ IFile sourceFile = sourceFolder.getFile(relative);
+ SourceFileData data = processor.getFileData(sourceFile);
+ if (data != null) {
+ addFileToCompile(sourceFile);
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
protected boolean filterResourceFolder(IContainer folder) {
return ResourceFolderType.RAW.getName().equals(folder.getName());
}
}
- public RenderScriptProcessor(IJavaProject javaProject, IFolder genFolder) {
- super(javaProject, genFolder, new RsChangeHandler());
+ private int mTargetApi = 11;
+
+ public RenderScriptProcessor(@NonNull IJavaProject javaProject,
+ @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder) {
+ super(javaProject, buildToolInfo, genFolder, new RsChangeHandler());
+ }
+
+ public void setTargetApi(int targetApi) {
+ // make sure the target api value is good. Must be 11+ or llvm-rs-cc complains.
+ mTargetApi = targetApi < 11 ? 11 : targetApi;
}
@Override
- protected String getExtension() {
- return SdkConstants.EXT_RS;
+ protected Set<String> getExtensions() {
+ return EXTENSIONS;
}
@Override
@@ -130,10 +167,9 @@ public class RenderScriptProcessor extends SourceProcessor {
return PROPERTY_COMPILE_RS;
}
- @SuppressWarnings("deprecation")
@Override
protected void doCompileFiles(List<IFile> sources, BaseBuilder builder,
- IProject project, IAndroidTarget projectTarget, int targetApi,
+ IProject project, IAndroidTarget projectTarget,
List<IPath> sourceFolders, List<IFile> notCompiledOut, List<File> libraryProjectsOut,
IProgressMonitor monitor) throws CoreException {
@@ -146,27 +182,22 @@ public class RenderScriptProcessor extends SourceProcessor {
int depIndex;
- // make sure the target api value is good. Must be 11+ or llvm-rs-cc complains.
- if (targetApi < 11) {
- targetApi = 11;
- }
-
// create the command line
String[] command = new String[15];
int index = 0;
command[index++] = quote(sdkOsPath + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER
+ SdkConstants.FN_RENDERSCRIPT);
command[index++] = "-I"; //$NON-NLS-1$
- command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS_CLANG));
+ command[index++] = quote(getBuildToolInfo().getPath(BuildToolInfo.PathId.ANDROID_RS_CLANG));
command[index++] = "-I"; //$NON-NLS-1$
- command[index++] = quote(projectTarget.getPath(IAndroidTarget.ANDROID_RS));
+ command[index++] = quote(getBuildToolInfo().getPath(BuildToolInfo.PathId.ANDROID_RS));
command[index++] = "-p"; //$NON-NLS-1$
command[index++] = quote(genFolder.getLocation().toOSString());
command[index++] = "-o"; //$NON-NLS-1$
command[index++] = quote(rawFolder.getLocation().toOSString());
command[index++] = "-target-api"; //$NON-NLS-1$
- command[index++] = Integer.toString(targetApi);
+ command[index++] = Integer.toString(mTargetApi);
command[index++] = "-d"; //$NON-NLS-1$
command[depIndex = index++] = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceChangeHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceChangeHandler.java
index 537dd61..5436798 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceChangeHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceChangeHandler.java
@@ -59,7 +59,7 @@ public class SourceChangeHandler {
public void handleSourceFile(IFile file, int kind) {
// first the file itself if this is a match for the processor's extension
- if (mProcessor.getExtension().equals(file.getFileExtension())) {
+ if (mProcessor.getExtensions().contains(file.getFileExtension())) {
if (kind == IResourceDelta.REMOVED) {
mRemoved.add(file);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
index 8e50ec6..a82d54d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/SourceProcessor.java
@@ -17,9 +17,11 @@
package com.android.ide.eclipse.adt.internal.build;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.internal.build.builders.BaseBuilder;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IFile;
@@ -38,6 +40,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -58,6 +61,7 @@ public abstract class SourceProcessor {
private final Map<IFile, SourceFileData> mFiles = new HashMap<IFile, SourceFileData>();
private final IJavaProject mJavaProject;
+ private BuildToolInfo mBuildToolInfo;
private final IFolder mGenFolder;
private final SourceChangeHandler mDeltaVisitor;
@@ -82,9 +86,11 @@ public abstract class SourceProcessor {
return path;
}
- protected SourceProcessor(IJavaProject javaProject, IFolder genFolder,
- SourceChangeHandler deltaVisitor) {
+ protected SourceProcessor(@NonNull IJavaProject javaProject,
+ @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder,
+ @NonNull SourceChangeHandler deltaVisitor) {
mJavaProject = javaProject;
+ mBuildToolInfo = buildToolInfo;
mGenFolder = genFolder;
mDeltaVisitor = deltaVisitor;
@@ -108,8 +114,13 @@ public abstract class SourceProcessor {
}
}
- protected SourceProcessor(IJavaProject javaProject, IFolder genFolder) {
- this(javaProject, genFolder, new SourceChangeHandler());
+ protected SourceProcessor(@NonNull IJavaProject javaProject,
+ @NonNull BuildToolInfo buildToolInfo, @NonNull IFolder genFolder) {
+ this(javaProject, buildToolInfo, genFolder, new SourceChangeHandler());
+ }
+
+ public void setBuildToolInfo(BuildToolInfo buildToolInfo) {
+ mBuildToolInfo = buildToolInfo;
}
@@ -167,6 +178,10 @@ public abstract class SourceProcessor {
return mJavaProject;
}
+ final BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
final IFolder getGenFolder() {
return mGenFolder;
}
@@ -210,7 +225,7 @@ public abstract class SourceProcessor {
* Returns the extension of the source files handled by this processor.
* @return
*/
- protected abstract String getExtension();
+ protected abstract Set<String> getExtensions();
protected abstract String getSavePropertyName();
@@ -219,7 +234,7 @@ public abstract class SourceProcessor {
*
*/
public final int compileFiles(BaseBuilder builder,
- IProject project, IAndroidTarget projectTarget, int minSdkVersion,
+ IProject project, IAndroidTarget projectTarget,
List<IPath> sourceFolders, List<File> libraryProjectsOut, IProgressMonitor monitor)
throws CoreException {
@@ -241,7 +256,7 @@ public abstract class SourceProcessor {
// list of files that have failed compilation.
List<IFile> stillNeedCompilation = new ArrayList<IFile>();
- doCompileFiles(mToCompile, builder, project, projectTarget, minSdkVersion, sourceFolders,
+ doCompileFiles(mToCompile, builder, project, projectTarget, sourceFolders,
stillNeedCompilation, libraryProjectsOut, monitor);
mToCompile.clear();
@@ -273,7 +288,7 @@ public abstract class SourceProcessor {
protected abstract void doCompileFiles(
List<IFile> filesToCompile, BaseBuilder builder,
- IProject project, IAndroidTarget projectTarget, int targetApi,
+ IProject project, IAndroidTarget projectTarget,
List<IPath> sourceFolders, List<IFile> notCompiledOut,
List<File> libraryProjectsOut, IProgressMonitor monitor) throws CoreException;
@@ -365,14 +380,16 @@ public abstract class SourceProcessor {
for (IResource r : members) {
// get the type of the resource
switch (r.getType()) {
- case IResource.FILE:
+ case IResource.FILE: {
// if this a file, check that the file actually exist
// and that it's the type of of file that's used in this processor
- if (r.exists() &&
- getExtension().equalsIgnoreCase(r.getFileExtension())) {
+ String extension = r.exists() ? r.getFileExtension() : null;
+ if (extension != null &&
+ getExtensions().contains(extension.toLowerCase(Locale.US))) {
mFiles.put((IFile) r, new SourceFileData((IFile) r));
}
break;
+ }
case IResource.FOLDER:
// recursively go through children
scanFolderForSourceFiles(sourceFolder, (IFolder)r);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties
index 70a3ab2..f387ab5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/build_messages.properties
@@ -14,6 +14,7 @@ Unparsed_AAPT_Errors=Unparsed aapt error(s)\! Check the console for output.
Unparsed_AIDL_Errors=Unparsed aidl error\! Check the console for output.
AAPT_Exec_Error_s=Error executing aapt. Please check aapt is present at %1$s
AAPT_Exec_Error_d=Error executing aapt: Return code %1$d
+AAPT_Exec_Error_Other_s=Error executing aapt: %1$s
Dalvik_Error_d=Conversion to Dalvik format failed with error %1$d
DX_Jar_Error=Dx.jar is not found inside the plugin. Reinstall ADT\!
Dalvik_Error_s=Conversion to Dalvik format failed: %1$s
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
index f58af56..1cbf7f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/BaseBuilder.java
@@ -16,19 +16,24 @@
package com.android.ide.eclipse.adt.internal.build.builders;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.build.BuildHelper;
import com.android.ide.eclipse.adt.internal.build.Messages;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler;
import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.XmlErrorListener;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
import com.android.io.IAbstractFile;
import com.android.io.StreamException;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.core.resources.IContainer;
@@ -64,6 +69,13 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder {
private SAXParserFactory mParserFactory;
/**
+ * The build tool to use to build. This is guaranteed to be non null after a call to
+ * {@link #abortOnBadSetup(IJavaProject, ProjectState)} since this will throw if it can't be
+ * queried.
+ */
+ protected BuildToolInfo mBuildToolInfo;
+
+ /**
* Base Resource Delta Visitor to handle XML error
*/
protected static class BaseDeltaVisitor implements XmlErrorListener {
@@ -293,9 +305,11 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder {
* display any errors.
*
* @param javaProject The {@link IJavaProject} being compiled.
+ * @param projectState the project state, optional. will be queried if null.
* @throws CoreException
*/
- protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
+ protected void abortOnBadSetup(@NonNull IJavaProject javaProject,
+ @Nullable ProjectState projectState) throws AbortBuildException, CoreException {
IProject iProject = javaProject.getProject();
// check if we have finished loading the project target.
Sdk sdk = Sdk.getCurrent();
@@ -303,8 +317,12 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder {
throw new AbortBuildException();
}
+ if (projectState == null) {
+ projectState = Sdk.getProjectState(javaProject.getProject());
+ }
+
// get the target for the project
- IAndroidTarget target = sdk.getTarget(javaProject.getProject());
+ IAndroidTarget target = projectState.getTarget();
if (target == null) {
throw new AbortBuildException();
@@ -315,6 +333,20 @@ public abstract class BaseBuilder extends IncrementalProjectBuilder {
throw new AbortBuildException();
}
+ mBuildToolInfo = projectState.getBuildToolInfo();
+ if (mBuildToolInfo == null) {
+ mBuildToolInfo = sdk.getLatestBuildTool();
+
+ if (mBuildToolInfo == null) {
+ throw new AbortBuildException();
+ } else {
+ AdtPlugin.printBuildToConsole(BuildVerbosity.VERBOSE, iProject,
+ String.format("Using default Build Tools revision %s",
+ mBuildToolInfo.getRevision())
+ );
+ }
+ }
+
// abort if there are TARGET or ADT type markers
stopOnMarker(iProject, AdtConstants.MARKER_TARGET, IResource.DEPTH_ZERO,
false /*checkSeverity*/);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ChangedFileSetHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ChangedFileSetHelper.java
index 67c7e8a..529dad2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ChangedFileSetHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ChangedFileSetHelper.java
@@ -111,6 +111,22 @@ class ChangedFileSetHelper {
}
/**
+ * Returns a {@link ChangedFileSet} for the generated R.txt file
+ * @param project the project
+ * @return a ChangeFileSet
+ */
+ static ChangedFileSet getTextSymbols(@NonNull IProject project) {
+ // input path is inside the project's android output folder
+ String path = getRelativeAndroidOut(project);
+
+ ChangedFileSet set = new ChangedFileSet(
+ "textSymbols", //$NON-NLS-1$
+ path + '/' + SdkConstants.FN_RESOURCE_TEXT);
+
+ return set;
+ }
+
+ /**
* Returns a {@link ChangedFileSet} for a project's javac output.
* @param project the project
* @return a ChangedFileSet
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
index e5b5a43..093072b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PostCompilerBuilder.java
@@ -17,6 +17,8 @@
package com.android.ide.eclipse.adt.internal.build.builders;
import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AndroidPrintStream;
@@ -314,14 +316,15 @@ public class PostCompilerBuilder extends BaseBuilder {
IJavaProject referencedJavaProject = referencedJavaProjects.get(i);
delta = getDelta(referencedJavaProject.getProject());
if (delta != null) {
+ IProject referencedProject = referencedJavaProject.getProject();
PatternBasedDeltaVisitor visitor = new PatternBasedDeltaVisitor(
- project, referencedJavaProject.getProject(),
+ project, referencedProject,
"POST:RefedProject");
- ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(project);
+ ChangedFileSet javaResCfs = ChangedFileSetHelper.getJavaResCfs(referencedProject);
visitor.addSet(javaResCfs);
- ChangedFileSet bytecodeCfs = ChangedFileSetHelper.getByteCodeCfs(project);
+ ChangedFileSet bytecodeCfs = ChangedFileSetHelper.getByteCodeCfs(referencedProject);
visitor.addSet(bytecodeCfs);
delta.accept(visitor);
@@ -340,18 +343,20 @@ public class PostCompilerBuilder extends BaseBuilder {
// Top level check to make sure the build can move forward. Only do this after recording
// delta changes.
- abortOnBadSetup(javaProject);
+ abortOnBadSetup(javaProject, projectState);
+
+ // Get the output stream. Since the builder is created for the life of the
+ // project, they can be kept around.
+ if (mOutStream == null) {
+ mOutStream = new AndroidPrintStream(project, null /*prefix*/,
+ AdtPlugin.getOutStream());
+ mErrStream = new AndroidPrintStream(project, null /*prefix*/,
+ AdtPlugin.getOutStream());
+ }
// remove older packaging markers.
removeMarkersFromContainer(javaProject.getProject(), AdtConstants.MARKER_PACKAGING);
- if (androidOutputFolder == null) {
- // mark project and exit
- markProject(AdtConstants.MARKER_PACKAGING, Messages.Failed_To_Get_Output,
- IMarker.SEVERITY_ERROR);
- return allRefProjects;
- }
-
// finished with the common init and tests. Special case of the library.
if (isLibrary) {
// check the jar output file is present, if not create it.
@@ -366,8 +371,10 @@ public class PostCompilerBuilder extends BaseBuilder {
if (DEBUG_LOG) {
AdtPlugin.log(IStatus.INFO, "%s running crunch!", project.getName());
}
- BuildHelper helper = new BuildHelper(project,
+ BuildHelper helper = new BuildHelper(project, mBuildToolInfo,
mOutStream, mErrStream,
+ false /*jumbo mode doesn't matter here*/,
+ false /*dex merger doesn't matter here*/,
true /*debugMode*/,
AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
mResourceMarker);
@@ -468,20 +475,21 @@ public class PostCompilerBuilder extends BaseBuilder {
ic.refreshLocal(IResource.DEPTH_ONE, monitor);
}
- // Get the DX output stream. Since the builder is created for the life of the
- // project, they can be kept around.
- if (mOutStream == null) {
- mOutStream = new AndroidPrintStream(project, null /*prefix*/,
- AdtPlugin.getOutStream());
- mErrStream = new AndroidPrintStream(project, null /*prefix*/,
- AdtPlugin.getOutStream());
- }
-
// we need to test all three, as we may need to make the final package
// but not the intermediary ones.
if (mPackageResources || mConvertToDex || mBuildFinalPackage) {
- BuildHelper helper = new BuildHelper(project,
+ String forceJumboStr = projectState.getProperty(
+ AdtConstants.DEX_OPTIONS_FORCEJUMBO);
+ Boolean jumbo = Boolean.valueOf(forceJumboStr);
+
+ String dexMergerStr = projectState.getProperty(
+ AdtConstants.DEX_OPTIONS_DISABLE_MERGER);
+ Boolean dexMerger = Boolean.valueOf(dexMergerStr);
+
+ BuildHelper helper = new BuildHelper(project, mBuildToolInfo,
mOutStream, mErrStream,
+ jumbo.booleanValue(),
+ dexMerger.booleanValue(),
true /*debugMode*/,
AdtPrefs.getPrefs().getBuildVerbosity() == BuildVerbosity.VERBOSE,
mResourceMarker);
@@ -635,6 +643,8 @@ public class PostCompilerBuilder extends BaseBuilder {
Messages.ApkBuilder_Update_or_Execute_manually_s,
e.getCommandLine());
+ AdtPlugin.log(e, msg);
+
return allRefProjects;
} catch (ApkCreationException e) {
String eMessage = e.getMessage();
@@ -643,6 +653,8 @@ public class PostCompilerBuilder extends BaseBuilder {
String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
+
+ AdtPlugin.log(e, msg);
} catch (AndroidLocationException e) {
String eMessage = e.getMessage();
@@ -650,6 +662,7 @@ public class PostCompilerBuilder extends BaseBuilder {
String msg = String.format(Messages.Final_Archive_Error_s, eMessage);
BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
+ AdtPlugin.log(e, msg);
} catch (NativeLibInJarException e) {
String msg = e.getMessage();
@@ -663,6 +676,7 @@ public class PostCompilerBuilder extends BaseBuilder {
AdtPlugin.printErrorToConsole(project, msg);
BaseProjectHelper.markResource(project, AdtConstants.MARKER_PACKAGING, msg,
IMarker.SEVERITY_ERROR);
+ AdtPlugin.log(e, msg);
} catch (DuplicateFileException e) {
String msg1 = String.format(
"Found duplicate file for APK: %1$s\nOrigin 1: %2$s\nOrigin 2: %3$s",
@@ -901,8 +915,10 @@ public class PostCompilerBuilder extends BaseBuilder {
}
@Override
- protected void abortOnBadSetup(IJavaProject javaProject) throws AbortBuildException {
- super.abortOnBadSetup(javaProject);
+ protected void abortOnBadSetup(
+ @NonNull IJavaProject javaProject,
+ @Nullable ProjectState projectState) throws AbortBuildException, CoreException {
+ super.abortOnBadSetup(javaProject, projectState);
IProject iProject = getProject();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
index 1507a8d..ae6b3f5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/PreCompilerBuilder.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.build.builders;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.xml.ManifestData;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -46,6 +47,7 @@ import com.android.io.StreamException;
import com.android.manifmerger.ManifestMerger;
import com.android.manifmerger.MergerLog;
import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.build.BuildConfigGenerator;
import com.android.sdklib.internal.build.SymbolLoader;
@@ -55,7 +57,9 @@ import com.android.sdklib.io.FileOp;
import com.android.utils.ILogger;
import com.android.utils.Pair;
import com.android.xml.AndroidManifest;
+import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -76,6 +80,7 @@ import org.xml.sax.SAXException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -139,6 +144,9 @@ public class PreCompilerBuilder extends BaseBuilder {
*/
private DerivedProgressMonitor mDerivedProgressMonitor;
+ private AidlProcessor mAidlProcessor;
+ private RenderScriptProcessor mRenderScriptProcessor;
+
/**
* Progress monitor waiting the end of the process to set a persistent value
* in a file. This is typically used in conjunction with <code>IResource.refresh()</code>,
@@ -258,6 +266,8 @@ public class PreCompilerBuilder extends BaseBuilder {
return null;
}
+ boolean isLibrary = projectState.isLibrary();
+
IAndroidTarget projectTarget = projectState.getTarget();
// get the libraries
@@ -267,7 +277,9 @@ public class PreCompilerBuilder extends BaseBuilder {
IJavaProject javaProject = JavaCore.create(project);
// Top level check to make sure the build can move forward.
- abortOnBadSetup(javaProject);
+ abortOnBadSetup(javaProject, projectState);
+
+ setupSourceProcessors(javaProject, projectState);
// now we need to get the classpath list
List<IPath> sourceFolderPathList = BaseProjectHelper.getSourceClasspaths(javaProject);
@@ -321,17 +333,22 @@ public class PreCompilerBuilder extends BaseBuilder {
// Notify the ResourceManager:
ResourceManager resManager = ResourceManager.getInstance();
- ProjectResources projectResources = resManager.getProjectResources(project);
if (ResourceManager.isAutoBuilding()) {
+ ProjectResources projectResources = resManager.getProjectResources(project);
+
IdeScanningContext context = new IdeScanningContext(projectResources,
project, true);
- resManager.processDelta(delta, context);
+ boolean wasCleared = projectResources.ensureInitialized();
+
+ if (!wasCleared) {
+ resManager.processDelta(delta, context);
+ }
// Check whether this project or its dependencies (libraries) have
// resources that need compilation
- if (context.needsFullAapt()) {
+ if (wasCleared || context.needsFullAapt()) {
mMustCompileResources = true;
// Must also call markAaptRequested on the project to not just
@@ -362,7 +379,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// if the main manifest didn't change, then we check for the library
// ones (will trigger manifest merging too)
- if (mMustMergeManifest == false && libProjects.size() > 0) {
+ if (libProjects.size() > 0) {
for (IProject libProject : libProjects) {
IResourceDelta delta = getDelta(libProject);
if (delta != null) {
@@ -371,12 +388,24 @@ public class PreCompilerBuilder extends BaseBuilder {
"PRE:LibManifest"); //$NON-NLS-1$
visitor.addSet(ChangedFileSetHelper.MANIFEST);
+ ChangedFileSet textSymbolCFS = null;
+ if (isLibrary == false) {
+ textSymbolCFS = ChangedFileSetHelper.getTextSymbols(
+ libProject);
+ visitor.addSet(textSymbolCFS);
+ }
+
delta.accept(visitor);
mMustMergeManifest |= visitor.checkSet(ChangedFileSetHelper.MANIFEST);
- // no need to test others.
- if (mMustMergeManifest) {
+ if (textSymbolCFS != null) {
+ mMustCompileResources |= visitor.checkSet(textSymbolCFS);
+ }
+
+ // no need to test others if we have all flags at true.
+ if (mMustMergeManifest &&
+ (mMustCompileResources || textSymbolCFS == null)) {
break;
}
}
@@ -631,10 +660,27 @@ public class PreCompilerBuilder extends BaseBuilder {
// run the source processors
int processorStatus = SourceProcessor.COMPILE_STATUS_NONE;
+
+ // get the renderscript target
+ int rsTarget = minSdkValue == -1 ? 11 : minSdkValue;
+ String rsTargetStr = projectState.getProperty(ProjectProperties.PROPERTY_RS_TARGET);
+ if (rsTargetStr != null) {
+ try {
+ rsTarget = Integer.parseInt(rsTargetStr);
+ } catch (NumberFormatException e) {
+ handleException(e, String.format(
+ "Property %s is not an integer.",
+ ProjectProperties.PROPERTY_RS_TARGET));
+ return result;
+ }
+ }
+
+ mRenderScriptProcessor.setTargetApi(rsTarget);
+
for (SourceProcessor processor : mProcessors) {
try {
processorStatus |= processor.compileFiles(this,
- project, projectTarget, minSdkValue, sourceFolderPathList,
+ project, projectTarget, sourceFolderPathList,
libProjectsOut, monitor);
} catch (Throwable t) {
handleException(t, String.format(
@@ -664,8 +710,8 @@ public class PreCompilerBuilder extends BaseBuilder {
proguardFile = androidOutputFolder.getFile(AdtConstants.FN_AAPT_PROGUARD);
}
- handleResources(project, javaPackage, projectTarget, manifestFile, libProjects,
- projectState.isLibrary(), proguardFile);
+ handleResources(project, javaPackage, projectTarget, manifestFile,
+ libProjects, isLibrary, proguardFile);
}
if (processorStatus == SourceProcessor.COMPILE_STATUS_NONE &&
@@ -721,6 +767,10 @@ public class PreCompilerBuilder extends BaseBuilder {
// Also clean up lint
EclipseLintClient.clearMarkers(project);
+
+ // clean the project repo
+ ProjectResources res = ResourceManager.getInstance().getProjectResources(project);
+ res.clear();
}
@Override
@@ -749,19 +799,26 @@ public class PreCompilerBuilder extends BaseBuilder {
mLastBuildConfigMode = v;
}
- IJavaProject javaProject = JavaCore.create(project);
-
- // load the source processors
- SourceProcessor aidlProcessor = new AidlProcessor(javaProject, mGenFolder);
- SourceProcessor renderScriptProcessor = new RenderScriptProcessor(javaProject,
- mGenFolder);
- mProcessors.add(aidlProcessor);
- mProcessors.add(renderScriptProcessor);
} catch (Throwable throwable) {
AdtPlugin.log(throwable, "Failed to finish PrecompilerBuilder#startupOnInitialize()");
}
}
+ private void setupSourceProcessors(@NonNull IJavaProject javaProject,
+ @NonNull ProjectState projectState) {
+ if (mAidlProcessor == null) {
+ mAidlProcessor = new AidlProcessor(javaProject, mBuildToolInfo, mGenFolder);
+ mRenderScriptProcessor = new RenderScriptProcessor(javaProject, mBuildToolInfo,
+ mGenFolder);
+ mProcessors.add(mAidlProcessor);
+ mProcessors.add(mRenderScriptProcessor);
+ } else {
+ mAidlProcessor.setBuildToolInfo(mBuildToolInfo);
+ mRenderScriptProcessor.setBuildToolInfo(mBuildToolInfo);
+ }
+ }
+
+ @SuppressWarnings("deprecation")
private void handleBuildConfig(@SuppressWarnings("rawtypes") Map args)
throws IOException, CoreException {
boolean debugMode = !args.containsKey(RELEASE_REQUESTED);
@@ -851,7 +908,8 @@ public class PreCompilerBuilder extends BaseBuilder {
}
@Override
- public void error(Throwable t, String errorFormat, Object... args) {
+ public void error(@Nullable Throwable t, @Nullable String errorFormat,
+ Object... args) {
errors.add(String.format(errorFormat, args));
}
}),
@@ -867,7 +925,8 @@ public class PreCompilerBuilder extends BaseBuilder {
if (merger.process(
outFile.getLocation().toFile(),
manifest.getLocation().toFile(),
- libManifests) == false) {
+ libManifests,
+ null /*injectAttributes*/, null /*packageOverride*/) == false) {
if (errors.size() > 1) {
StringBuilder sb = new StringBuilder();
for (String s : errors) {
@@ -989,6 +1048,7 @@ public class PreCompilerBuilder extends BaseBuilder {
* @param proguardFile an optional path to store proguard information
* @throws AbortBuildException
*/
+ @SuppressWarnings("deprecation")
private void execAapt(IProject project, IAndroidTarget projectTarget, String osOutputPath,
String osResPath, String osManifestPath, IFolder packageFolder,
ArrayList<IFolder> libResFolders, List<Pair<File, String>> libRFiles,
@@ -1004,8 +1064,7 @@ public class PreCompilerBuilder extends BaseBuilder {
// launch aapt: create the command line
ArrayList<String> array = new ArrayList<String>();
- @SuppressWarnings("deprecation")
- String aaptPath = projectTarget.getPath(IAndroidTarget.AAPT);
+ String aaptPath = mBuildToolInfo.getPath(BuildToolInfo.PathId.AAPT);
array.add(aaptPath);
array.add("package"); //$NON-NLS-1$
@@ -1109,24 +1168,65 @@ public class PreCompilerBuilder extends BaseBuilder {
}
// now if the project has libraries, R needs to be created for each libraries
- if (!libRFiles.isEmpty()) {
- SymbolLoader symbolValues = new SymbolLoader(new File(outputFolder, "R.txt"));
- symbolValues.load();
+ // unless this is a library.
+ if (isLibrary == false && !libRFiles.isEmpty()) {
+ File rFile = new File(outputFolder, SdkConstants.FN_RESOURCE_TEXT);
+ // if the project has no resources, the file could not exist.
+ if (rFile.isFile()) {
+ // Load the full symbols from the full R.txt file.
+ SymbolLoader fullSymbolValues = new SymbolLoader(rFile);
+ fullSymbolValues.load();
+
+ Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
+
+ // First pass processing the libraries, collecting them by packageName,
+ // and ignoring the ones that have the same package name as the application
+ // (since that R class was already created).
+
+ for (Pair<File, String> lib : libRFiles) {
+ String libPackage = lib.getSecond();
+ File rText = lib.getFirst();
+
+ if (rText.isFile()) {
+ // load the lib symbols
+ SymbolLoader libSymbols = new SymbolLoader(rText);
+ libSymbols.load();
+
+ // store these symbols by associating them with the package name.
+ libMap.put(libPackage, libSymbols);
+ }
+ }
- for (Pair<File, String> libData : libRFiles) {
- SymbolLoader symbols = new SymbolLoader(libData.getFirst());
- symbols.load();
+ // now loop on all the package names, merge all the symbols to write,
+ // and write them
+ for (String packageName : libMap.keySet()) {
+ Collection<SymbolLoader> symbols = libMap.get(packageName);
- SymbolWriter writer = new SymbolWriter(osOutputPath, libData.getSecond(),
- symbols, symbolValues);
- writer.write();
+ SymbolWriter writer = new SymbolWriter(osOutputPath, packageName,
+ fullSymbolValues);
+ for (SymbolLoader symbolLoader : symbols) {
+ writer.addSymbolsToWrite(symbolLoader);
+ }
+ writer.write();
+ }
}
}
} catch (IOException e1) {
// something happen while executing the process,
// mark the project and exit
- String msg = String.format(Messages.AAPT_Exec_Error_s, array.get(0));
+ String msg;
+ String path = array.get(0);
+ if (!new File(path).exists()) {
+ msg = String.format(Messages.AAPT_Exec_Error_s, path);
+ } else {
+ String description = e1.getLocalizedMessage();
+ if (e1.getCause() != null && e1.getCause() != e1) {
+ description = description + ": " + e1.getCause().getLocalizedMessage();
+ }
+ msg = String.format(Messages.AAPT_Exec_Error_Other_s, description);
+ }
+
markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR);
// Add workaround for the Linux problem described here:
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java
index 39f7d1f..770710d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/build/builders/ResourceManagerBuilder.java
@@ -24,8 +24,6 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
-import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.sdklib.IAndroidTarget;
import com.android.utils.Pair;
import org.eclipse.core.resources.IFolder;
@@ -87,7 +85,7 @@ public class ResourceManagerBuilder extends BaseBuilder {
// check for existing target marker, in which case we abort.
// (this means: no SDK, no target, or unresolvable target.)
try {
- abortOnBadSetup(javaProject);
+ abortOnBadSetup(javaProject, null);
} catch (AbortBuildException e) {
return null;
}
@@ -129,13 +127,6 @@ public class ResourceManagerBuilder extends BaseBuilder {
return null;
}
- // check the project has a target
- IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project);
- if (projectTarget == null) {
- // no target. marker has been set by the container initializer: exit silently.
- return null;
- }
-
// check the 'gen' source folder is present
boolean hasGenSrcFolder = false; // whether the project has a 'gen' source folder setup
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
index b39c4cb..5aac51f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssist.java
@@ -16,8 +16,11 @@
package com.android.ide.eclipse.adt.internal.editors;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_ANDROID;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.UNIT_DP;
import static com.android.SdkConstants.UNIT_IN;
import static com.android.SdkConstants.UNIT_MM;
@@ -44,6 +47,9 @@ import com.android.utils.Pair;
import com.android.utils.XmlUtils;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.ui.ISharedImages;
+import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
@@ -224,8 +230,10 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// or their values).
if (info.isInValue) {
- computeAttributeValues(proposals, offset, parent, info.name, currentNode,
- wordPrefix, info.skipEndTag, info.replaceLength);
+ if (computeAttributeValues(proposals, offset, parent, info.name, currentNode,
+ wordPrefix, info.skipEndTag, info.replaceLength)) {
+ return;
+ }
}
// Look up attribute proposals based on descriptors
@@ -461,14 +469,24 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
choices = UiResourceAttributeNode.computeResourceStringMatches(
mEditor, attributeDescriptor, value);
attrInfo.skipEndTag = false;
+ } else if (value.startsWith(PREFIX_THEME_REF)
+ && !attributeInfo.getFormats().contains(Format.REFERENCE)) {
+ choices = UiResourceAttributeNode.computeResourceStringMatches(
+ mEditor, attributeDescriptor, value);
+ attrInfo.skipEndTag = false;
}
return choices;
}
- protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ /**
+ * Compute attribute values. Return true if the complete set of values was
+ * added, so addition descriptor information should not be added.
+ */
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
+ return false;
}
protected void computeTextValues(List<ICompletionProposal> proposals, int offset,
@@ -502,8 +520,8 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
*
* @return An ElementDescriptor[] or null.
*/
- private Object[] getElementChoicesForTextNode(Node parentNode) {
- Object[] choices = null;
+ protected ElementDescriptor[] getElementChoicesForTextNode(Node parentNode) {
+ ElementDescriptor[] choices = null;
String parent;
if (parentNode.getNodeType() == Node.ELEMENT_NODE) {
// We're editing a text node which parent is an element node. Limit
@@ -549,10 +567,12 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
}
Map<String, String> nsUriMap = new HashMap<String, String>();
+ boolean haveLayoutParams = false;
for (Object choice : choices) {
String keyword = null;
String nsPrefix = null;
+ String nsUri = null;
Image icon = null;
String tooltip = null;
if (choice instanceof ElementDescriptor) {
@@ -570,11 +590,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
// Get the namespace URI for the attribute. Note that some attributes
// do not have a namespace and thus return null here.
- String nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
+ nsUri = ((AttributeDescriptor)choice).getNamespaceUri();
if (nsUri != null) {
nsPrefix = nsUriMap.get(nsUri);
if (nsPrefix == null) {
- nsPrefix = XmlUtils.lookupNamespacePrefix(currentNode, nsUri);
+ nsPrefix = XmlUtils.lookupNamespacePrefix(currentNode, nsUri, false);
nsUriMap.put(nsUri, nsPrefix);
}
}
@@ -595,6 +615,10 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
if (isAttribute) {
icon = IconFactory.getInstance().getIcon(ATTRIBUTE_ICON_FILENAME);
}
+ } else if (choice instanceof IType) {
+ IType type = (IType) choice;
+ keyword = type.getFullyQualifiedName();
+ icon = JavaUI.getSharedImages().getImage(ISharedImages.IMG_OBJS_CUNIT);
} else {
continue; // discard unknown choice
}
@@ -646,6 +670,11 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
cursorPosition++;
}
+ if (nsPrefix != null &&
+ keyword.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length())) {
+ haveLayoutParams = true;
+ }
+
// For attributes, automatically insert ns:attribute="" and place the cursor
// inside the quotes.
// Special case for attributes: insert ="" stuff and locate caret inside ""
@@ -659,10 +688,42 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
icon, // Image image
displayString, // displayString
null, // IContextInformation contextInformation
- tooltip // String additionalProposalInfo
+ tooltip, // String additionalProposalInfo
+ nsPrefix,
+ nsUri
));
}
}
+
+ if (wordPrefix.length() > 0 && haveLayoutParams
+ && !wordPrefix.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
+ // Sort layout parameters to the front if we automatically inserted some
+ // that you didn't request. For example, you typed "width" and we match both
+ // "width" and "layout_width" - should match layout_width.
+ String nsPrefix = nsUriMap.get(ANDROID_URI);
+ if (nsPrefix == null) {
+ nsPrefix = PREFIX_ANDROID;
+ } else {
+ nsPrefix += ':';
+ }
+ if (!(wordPrefix.startsWith(nsPrefix)
+ && wordPrefix.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length()))) {
+ int nextLayoutIndex = 0;
+ for (int i = 0, n = proposals.size(); i < n; i++) {
+ ICompletionProposal proposal = proposals.get(i);
+ String keyword = proposal.getDisplayString();
+ if (keyword.startsWith(nsPrefix) &&
+ keyword.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX, nsPrefix.length())
+ && i != nextLayoutIndex) {
+ // Swap to front
+ ICompletionProposal temp = proposals.get(nextLayoutIndex);
+ proposals.set(nextLayoutIndex, proposal);
+ proposals.set(i, temp);
+ nextLayoutIndex++;
+ }
+ }
+ }
+ }
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidDoubleClickStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidDoubleClickStrategy.java
new file mode 100644
index 0000000..8716487
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidDoubleClickStrategy.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors;
+
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
+import org.eclipse.wst.xml.ui.internal.doubleclick.XMLDoubleClickStrategy;
+
+/**
+ * Custom version of {@link XMLDoubleClickStrategy} which is smarter about
+ * selecting portions of resource references, etc.
+ */
+@SuppressWarnings("restriction") // XML API
+public class AndroidDoubleClickStrategy extends XMLDoubleClickStrategy {
+ /**
+ * Creates a new {@linkplain AndroidDoubleClickStrategy}
+ */
+ public AndroidDoubleClickStrategy() {
+ }
+
+ @Override
+ protected void processElementDoubleClicked() {
+ // Special case: if you click on the local name portion of an attribute pair,
+ // select only the local name. For example, if you click anywhere in the "text" region
+ // of "android:text", select just the "text" portion.
+ if (fTextRegion.getType() == DOMRegionContext.XML_TAG_ATTRIBUTE_NAME) {
+ String regionText = fStructuredDocumentRegion.getText(fTextRegion);
+ int cursor = fCaretPosition - fStructuredDocumentRegion.getStartOffset(fTextRegion);
+ int ns = regionText.indexOf(':');
+ if (cursor > ns) {
+ int start = ns + 1;
+ fTextViewer.setSelectedRange(fStructuredDocumentRegion.getStartOffset(fTextRegion)
+ + start, fTextRegion.getTextLength() - start);
+ return;
+ }
+ }
+
+ super.processElementDoubleClicked();
+ }
+
+ @Override
+ protected Point getWord(String string, int cursor) {
+ if (string == null) {
+ return null;
+ }
+
+ // Default implementation will strip off the surrounding quotes etc:
+ Point position = super.getWord(string, cursor);
+
+ assert cursor >= position.x && cursor <= position.y;
+
+ // Special case: when you click on a resource identifier name, only select the
+ // name portion
+ if (string.startsWith(PREFIX_RESOURCE_REF, position.x) ||
+ string.startsWith(PREFIX_THEME_REF, position.x)) {
+ int nameStart = string.indexOf('/', position.x + 1);
+ if (nameStart != -1 && nameStart < cursor) {
+ position.x = nameStart + 1;
+ return position;
+ }
+ }
+
+ // Special case: when you have a dotted name, such as com.android.tools.MyClass,
+ // and you click on the last part, select only that part
+ int lastDot = string.lastIndexOf('.', cursor);
+ if (lastDot >= position.x && lastDot < position.y - 1) {
+ int next = string.indexOf('.', cursor);
+ if (next == -1 || next > position.y) {
+ position.x = lastDot + 1;
+ }
+ }
+
+ return position;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java
index c5aa8cd..8a078ef 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlAutoEditStrategy.java
@@ -24,7 +24,7 @@ import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
import com.android.utils.Pair;
import org.eclipse.jface.text.BadLocationException;
@@ -148,7 +148,7 @@ public class AndroidXmlAutoEditStrategy implements IAutoEditStrategy {
StringBuilder sb = new StringBuilder(c.text);
sb.append(lineIndent);
- String oneIndentUnit = XmlFormatPreferences.create().getOneIndentUnit();
+ String oneIndentUnit = EclipseXmlFormatPreferences.create().getOneIndentUnit();
if (addIndent) {
sb.append(oneIndentUnit);
}
@@ -276,7 +276,7 @@ public class AndroidXmlAutoEditStrategy implements IAutoEditStrategy {
}
if (addIndent) {
- sb.append(XmlFormatPreferences.create()
+ sb.append(EclipseXmlFormatPreferences.create()
.getOneIndentUnit());
}
c.text = sb.toString();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index fcfdfd1..1d4e133 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -25,6 +25,7 @@ import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener;
@@ -41,6 +42,8 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
+import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.BadLocationException;
@@ -802,7 +805,16 @@ public abstract class AndroidXmlEditor extends FormEditor {
*/
private void createTextEditor() {
try {
- mTextEditor = new StructuredTextEditor();
+ mTextEditor = new StructuredTextEditor() {
+ @Override
+ protected void createActions() {
+ super.createActions();
+
+ Action action = new RenameResourceXmlTextAction(mTextEditor);
+ action.setActionDefinitionId(IJavaEditorActionDefinitionIds.RENAME_ELEMENT);
+ setAction(IJavaEditorActionDefinitionIds.RENAME_ELEMENT, action);
+ }
+ };
int index = addPage(mTextEditor, getEditorInput());
mTextPageIndex = index;
setPageText(index, mTextEditor.getTitle());
@@ -1058,7 +1070,12 @@ public abstract class AndroidXmlEditor extends FormEditor {
end = begin + 1;
}
- reformatRegion(begin, end);
+ if (mFormatChildren
+ && node == node.getOwnerDocument().getDocumentElement()) {
+ reformatDocument();
+ } else {
+ reformatRegion(begin, end);
+ }
}
}
mFormatNode = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/CompletionProposal.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/CompletionProposal.java
index 74b7dd8..2d44677 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/CompletionProposal.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/CompletionProposal.java
@@ -15,20 +15,34 @@
*/
package com.android.ide.eclipse.adt.internal.editors;
+import static com.android.SdkConstants.XMLNS;
+
import com.android.ide.common.api.IAttributeInfo;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.IDescriptorProvider;
import com.android.ide.eclipse.adt.internal.editors.descriptors.TextAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.utils.XmlUtils;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.ISourceRange;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,18 +68,21 @@ class CompletionProposal implements ICompletionProposal {
private final AndroidContentAssist mAssist;
private final Object mChoice;
private final int mCursorPosition;
- private final int mReplacementOffset;
+ private int mReplacementOffset;
private final int mReplacementLength;
private final String mReplacementString;
private final Image mImage;
private final String mDisplayString;
private final IContextInformation mContextInformation;
+ private final String mNsPrefix;
+ private final String mNsUri;
private String mAdditionalProposalInfo;
CompletionProposal(AndroidContentAssist assist,
Object choice, String replacementString, int replacementOffset,
int replacementLength, int cursorPosition, Image image, String displayString,
- IContextInformation contextInformation, String additionalProposalInfo) {
+ IContextInformation contextInformation, String additionalProposalInfo,
+ String nsPrefix, String nsUri) {
assert replacementString != null;
assert replacementOffset >= 0;
assert replacementLength >= 0;
@@ -81,6 +98,8 @@ class CompletionProposal implements ICompletionProposal {
mDisplayString = displayString;
mContextInformation = contextInformation;
mAdditionalProposalInfo = additionalProposalInfo;
+ mNsPrefix = nsPrefix;
+ mNsUri = nsUri;
}
@Override
@@ -142,6 +161,24 @@ class CompletionProposal implements ICompletionProposal {
}
}
+ } else if (mChoice instanceof IType) {
+ IType type = (IType) mChoice;
+ try {
+ ISourceRange javadocRange = type.getJavadocRange();
+ if (javadocRange != null && javadocRange.getLength() > 0) {
+ ISourceRange sourceRange = type.getSourceRange();
+ if (sourceRange != null) {
+ String source = type.getSource();
+ int start = javadocRange.getOffset() - sourceRange.getOffset();
+ int length = javadocRange.getLength();
+ String doc = source.substring(start, start + length);
+ return doc;
+ }
+ }
+ return type.getAttachedJavadoc(new NullProgressMonitor());
+ } catch (JavaModelException e) {
+ AdtPlugin.log(e, null);
+ }
}
}
@@ -151,6 +188,39 @@ class CompletionProposal implements ICompletionProposal {
@Override
public void apply(IDocument document) {
try {
+ Position position = new Position(mReplacementOffset);
+ document.addPosition(position);
+
+ // Ensure that the namespace is defined in the document
+ String prefix = mNsPrefix;
+ if (mNsUri != null && prefix != null) {
+ Document dom = DomUtilities.getDocument(mAssist.getEditor());
+ if (dom != null) {
+ Element root = dom.getDocumentElement();
+ if (root != null) {
+ // Is the namespace already defined?
+ boolean found = false;
+ NamedNodeMap attributes = root.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ String name = attribute.getName();
+ if (name.startsWith(XMLNS) && mNsUri.equals(attribute.getValue())) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ if (prefix.endsWith(":")) { //$NON-NLS-1$
+ prefix = prefix.substring(0, prefix.length() - 1);
+ }
+ XmlUtils.lookupNamespacePrefix(root, mNsUri, prefix, true);
+ }
+ }
+ }
+ }
+
+ mReplacementOffset = position.getOffset();
+ document.removePosition(position);
document.replace(mReplacementOffset, mReplacementLength, mReplacementString);
} catch (BadLocationException x) {
// ignore
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
index 18135aa..0e46255 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
@@ -22,6 +22,8 @@ import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_CONTEXT;
+import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_ON_CLICK;
import static com.android.SdkConstants.CLASS_ACTIVITY;
@@ -36,8 +38,10 @@ import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.TAG_RESOURCES;
import static com.android.SdkConstants.TAG_STYLE;
+import static com.android.SdkConstants.TOOLS_URI;
import static com.android.SdkConstants.VIEW;
import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.ide.common.resources.ResourceRepository.parseResource;
import static com.android.xml.AndroidManifest.ATTRIBUTE_NAME;
import static com.android.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
import static com.android.xml.AndroidManifest.NODE_ACTIVITY;
@@ -56,11 +60,13 @@ import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
import com.android.ide.eclipse.adt.io.IFolderWrapper;
@@ -217,7 +223,7 @@ public class Hyperlinks {
// It's a value -declaration-, nowhere else to jump
// (though we could consider jumping to the R-file; would that
// be helpful?)
- return false;
+ return !ATTR_ID.equals(attribute.getLocalName());
}
Pair<ResourceType,String> resource = parseResource(value);
@@ -342,7 +348,9 @@ public class Hyperlinks {
String tag = context.getElement().getTagName();
String attributeName = attribute.getLocalName();
return ATTR_CLASS.equals(attributeName) && (VIEW.equals(tag) || VIEW_FRAGMENT.equals(tag))
- || ATTR_NAME.equals(attributeName) && VIEW_FRAGMENT.equals(tag);
+ || ATTR_NAME.equals(attributeName) && VIEW_FRAGMENT.equals(tag)
+ || (ATTR_CONTEXT.equals(attributeName)
+ && TOOLS_URI.equals(attribute.getNamespaceURI()));
}
/** Returns true if this represents an onClick attribute specifying a method handler */
@@ -369,7 +377,18 @@ public class Hyperlinks {
/** Returns the FQCN for a class declaration at the given context */
private static String getClassFqcn(XmlContext context) {
if (isClassAttribute(context)) {
- return context.getAttribute().getValue();
+ String value = context.getAttribute().getValue();
+ if (!value.isEmpty() && value.charAt(0) == '.') {
+ IProject project = getProject();
+ if (project != null) {
+ ManifestInfo info = ManifestInfo.get(project);
+ String pkg = info.getPackage();
+ if (pkg != null) {
+ value = pkg + value;
+ }
+ }
+ }
+ return value;
} else if (isClassElement(context)) {
return context.getElement().getTagName();
}
@@ -896,24 +915,50 @@ public class Hyperlinks {
String targetTag = getTagName(type);
Element root = document.getDocumentElement();
if (root.getTagName().equals(TAG_RESOURCES)) {
- NodeList children = root.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element)child;
- if (element.getTagName().equals(targetTag)) {
- String elementName = element.getAttribute(ATTR_NAME);
- if (elementName.equals(name)) {
- IRegion region = null;
- if (element instanceof IndexedRegion) {
- IndexedRegion r = (IndexedRegion) element;
- // IndexedRegion.getLength() returns bogus values
- int length = r.getEndOffset() - r.getStartOffset();
- region = new Region(r.getStartOffset(), length);
+ NodeList topLevel = root.getChildNodes();
+ Pair<IFile, IRegion> value = findValueInChildren(name, file, targetTag, topLevel);
+ if (value == null && type == ResourceType.ATTR) {
+ for (int i = 0, n = topLevel.getLength(); i < n; i++) {
+ Node child = topLevel.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element)child;
+ String tagName = element.getTagName();
+ if (tagName.equals("declare-styleable")) {
+ NodeList children = element.getChildNodes();
+ value = findValueInChildren(name, file, targetTag, children);
+ if (value != null) {
+ return value;
}
+ }
+ }
+ }
+ }
- return Pair.of(file, region);
+ return value;
+ }
+
+ return null;
+ }
+
+ private static Pair<IFile, IRegion> findValueInChildren(String name, IFile file,
+ String targetTag, NodeList children) {
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element)child;
+ String tagName = element.getTagName();
+ if (tagName.equals(targetTag)) {
+ String elementName = element.getAttribute(ATTR_NAME);
+ if (elementName.equals(name)) {
+ IRegion region = null;
+ if (element instanceof IndexedRegion) {
+ IndexedRegion r = (IndexedRegion) element;
+ // IndexedRegion.getLength() returns bogus values
+ int length = r.getEndOffset() - r.getStartOffset();
+ region = new Region(r.getStartOffset(), length);
}
+
+ return Pair.of(file, region);
}
}
}
@@ -957,16 +1002,25 @@ public class Hyperlinks {
private static Pair<IFile, IRegion> findIdInDocument(String id, IFile file,
Document document) {
String targetAttribute = NEW_ID_PREFIX + id;
- return findIdInElement(document.getDocumentElement(), file, targetAttribute);
+ Element root = document.getDocumentElement();
+ Pair<IFile, IRegion> result = findIdInElement(root, file, targetAttribute,
+ true /*requireId*/);
+ if (result == null) {
+ result = findIdInElement(root, file, targetAttribute, false /*requireId*/);
+ }
+ return result;
}
private static Pair<IFile, IRegion> findIdInElement(
- Element root, IFile file, String targetAttribute) {
+ Element root, IFile file, String targetAttribute, boolean requireIdAttribute) {
NamedNodeMap attributes = root.getAttributes();
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Node item = attributes.item(i);
if (item instanceof Attr) {
- Attr attribute = (Attr)item;
+ Attr attribute = (Attr) item;
+ if (requireIdAttribute && !ATTR_ID.equals(attribute.getLocalName())) {
+ continue;
+ }
String value = attribute.getValue();
if (value.equals(targetAttribute)) {
// Select the element -containing- the id rather than the attribute itself
@@ -989,7 +1043,8 @@ public class Hyperlinks {
Node child = children.item(i);
if (child.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element)child;
- Pair<IFile, IRegion> result = findIdInElement(element, file, targetAttribute);
+ Pair<IFile, IRegion> result = findIdInElement(element, file, targetAttribute,
+ requireIdAttribute);
if (result != null) {
return result;
}
@@ -1097,16 +1152,6 @@ public class Hyperlinks {
return getResourceLinks(range, url, project, configuration);
}
- /** Parse a resource reference or a theme reference and return the individual parts */
- private static Pair<ResourceType,String> parseResource(String url) {
- if (url.startsWith(PREFIX_THEME_REF)) {
- url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
- return ResourceHelper.parseResource(url);
- }
-
- return ResourceHelper.parseResource(url);
- }
-
/**
* Computes hyperlinks to resource definitions for resource urls (e.g.
* {@code @android:string/ok} or {@code @layout/foo}. May create multiple links.
@@ -1141,6 +1186,22 @@ public class Hyperlinks {
}
List<ResourceFile> sourceFiles = resources.getSourceFiles(type, name,
null /*configuration*/);
+ if (sourceFiles == null) {
+ ProjectState projectState = Sdk.getProjectState(project);
+ if (projectState != null) {
+ List<IProject> libraries = projectState.getFullLibraryProjects();
+ if (libraries != null && !libraries.isEmpty()) {
+ for (IProject library : libraries) {
+ resources = ResourceManager.getInstance().getProjectResources(library);
+ sourceFiles = resources.getSourceFiles(type, name, null /*configuration*/);
+ if (sourceFiles != null && !sourceFiles.isEmpty()) {
+ break;
+ }
+ }
+ }
+ }
+ }
+
ResourceFile best = null;
if (configuration != null && sourceFiles != null && sourceFiles.size() > 0) {
List<ResourceFile> bestFiles = resources.getSourceFiles(type, name, configuration);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java
index 1d27e33..bb5d1ba 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/OutlineLabelProvider.java
@@ -24,6 +24,8 @@ import static com.android.SdkConstants.ATTR_SRC;
import static com.android.SdkConstants.ATTR_TEXT;
import static com.android.SdkConstants.DRAWABLE_PREFIX;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.VIEW;
+import static com.android.SdkConstants.VIEW_TAG;
import org.eclipse.swt.graphics.Image;
import org.eclipse.wst.xml.ui.internal.contentoutline.JFaceNodeLabelProvider;
@@ -43,6 +45,11 @@ class OutlineLabelProvider extends JFaceNodeLabelProvider {
if (element instanceof Element) {
Element e = (Element) element;
String tagName = e.getTagName();
+ if (VIEW_TAG.equals(tagName)) {
+ // Can't have both view.png and View.png; issues on case sensitive vs
+ // case insensitive file systems
+ tagName = VIEW;
+ }
IconFactory factory = IconFactory.getInstance();
Image img = factory.getIcon(tagName, null);
if (img != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java
index 777cf1d..8a4cf23 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/animator/AnimationContentAssist.java
@@ -70,7 +70,7 @@ public final class AnimationContentAssist extends AndroidContentAssist {
}
@Override
- protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
@@ -95,8 +95,8 @@ public final class AnimationContentAssist extends AndroidContentAssist {
}
- super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
- wordPrefix, skipEndTag, replaceLength);
+ return super.computeAttributeValues(proposals, offset, parentTagName, attributeName,
+ node, wordPrefix, skipEndTag, replaceLength);
} else if (parentTagName.equals(OBJECT_ANIMATOR)
&& attributeName.endsWith(PROPERTY_NAME)) {
@@ -156,12 +156,13 @@ public final class AnimationContentAssist extends AndroidContentAssist {
addMatchingProposals(proposals, pairs.toArray(), offset, node, wordPrefix,
(char) 0 /* needTag */, true /* isAttribute */, false /* isNew */,
skipEndTag /* skipEndTag */, replaceLength);
- return;
}
}
+
+ return false;
} else {
- super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
- wordPrefix, skipEndTag, replaceLength);
+ return super.computeAttributeValues(proposals, offset, parentTagName, attributeName,
+ node, wordPrefix, skipEndTag, replaceLength);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
index 55d463b..224c28f 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/common/CommonMatchingStrategy.java
@@ -47,23 +47,35 @@ public class CommonMatchingStrategy implements IEditorMatchingStrategy {
IFile file = fileInput.getFile();
if (file.getParent().getName().startsWith(FD_RES_LAYOUT)) {
ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
- if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT
- && AdtPrefs.getPrefs().isSharedLayoutEditor()) {
- LayoutEditorMatchingStrategy m = new LayoutEditorMatchingStrategy();
- return m.matches(editorRef, fileInput);
+ if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT) {
+ if (AdtPrefs.getPrefs().isSharedLayoutEditor()) {
+ LayoutEditorMatchingStrategy m = new LayoutEditorMatchingStrategy();
+ return m.matches(editorRef, fileInput);
+ } else {
+ // Skip files that don't match by name (see below). However, for
+ // layout files we can't just use editorRef.getName(), since
+ // the name sometimes includes the parent folder name (when the
+ // files are in layout- folders.
+ if (!(editorRef.getName().endsWith(file.getName()) &&
+ editorRef.getId().equals(CommonXmlEditor.ID))) {
+ return false;
+ }
+ }
+ }
+ } else {
+ // Per the IEditorMatchingStrategy documentation, editorRef.getEditorInput()
+ // is expensive so try exclude files that definitely don't match, such
+ // as those with the wrong extension or wrong file name
+ if (!(file.getName().equals(editorRef.getName()) &&
+ editorRef.getId().equals(CommonXmlEditor.ID))) {
+ return false;
}
}
- // Per the IEditorMatchingStrategy documentation, editorRef.getEditorInput()
- // is expensive so try exclude files that definitely don't match, such
- // as those with the wrong extension or wrong file name
- if (file.getName().equals(editorRef.getName()) &&
- editorRef.getId().equals(CommonXmlEditor.ID)) {
- try {
- return input.equals(editorRef.getEditorInput());
- } catch (PartInitException e) {
- AdtPlugin.log(e, null);
- }
+ try {
+ return input.equals(editorRef.getEditorInput());
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, null);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
index 9c29077..9f69e41 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.editors.formatting;
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
import static com.android.ide.eclipse.adt.internal.editors.AndroidXmlAutoEditStrategy.findLineStart;
import static com.android.ide.eclipse.adt.internal.editors.AndroidXmlAutoEditStrategy.findTextStart;
import static com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors.SELECTOR_TAG;
@@ -27,12 +28,22 @@ import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG
import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG_OPEN;
import com.android.SdkConstants;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.resources.ResourceType;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
@@ -70,7 +81,7 @@ import java.util.Queue;
* Formatter which formats XML content according to the established Android coding
* conventions. It performs the format by computing the smallest set of DOM nodes
* overlapping the formatted region, then it pretty-prints that XML region
- * using the {@link XmlPrettyPrinter}, and then it replaces the affected region
+ * using the {@link EclipseXmlPrettyPrinter}, and then it replaces the affected region
* by the pretty-printed region.
* <p>
* This strategy is also used for delegation. If the user has chosen to use the
@@ -83,6 +94,8 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
private final Queue<IDocument> mDocuments = new LinkedList<IDocument>();
private final LinkedList<TypedPosition> mPartitions = new LinkedList<TypedPosition>();
private ContextBasedFormattingStrategy mDelegate = null;
+ /** False if document is known not to be in an Android project, null until initialized */
+ private Boolean mIsAndroid;
/**
* Creates a new {@link AndroidXmlFormattingStrategy}
@@ -91,7 +104,8 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
}
private ContextBasedFormattingStrategy getDelegate() {
- if (!AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+ if (!AdtPrefs.getPrefs().getUseCustomXmlFormatter()
+ || mIsAndroid != null && !mIsAndroid.booleanValue()) {
if (mDelegate == null) {
mDelegate = new XMLFormattingStrategy();
}
@@ -158,7 +172,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
* @param length the length of the text range to be formatted
* @return a {@link TextEdit} which edits the model into a formatted document
*/
- public static TextEdit format(IStructuredModel model, int start, int length) {
+ private static TextEdit format(IStructuredModel model, int start, int length) {
int end = start + length;
TextEdit edit = new MultiTextEdit();
@@ -258,6 +272,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
int initialDepth = 0;
int replaceStart;
int replaceEnd;
+ boolean endWithNewline = false;
if (startNode == null || endNode == null) {
// Process the entire document
root = domDocument;
@@ -267,6 +282,11 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
endNode = root;
replaceStart = 0;
replaceEnd = document.getLength();
+ try {
+ endWithNewline = replaceEnd > 0 && document.getChar(replaceEnd - 1) == '\n';
+ } catch (BadLocationException e) {
+ // Can't happen
+ }
} else {
root = DomUtilities.getCommonAncestor(startNode, endNode);
initialDepth = root != null ? DomUtilities.getDepth(root) - 1 : 0;
@@ -321,9 +341,10 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
}
XmlFormatStyle style = guessStyle(model, domDocument);
- XmlFormatPreferences prefs = XmlFormatPreferences.create();
+ XmlFormatPreferences prefs = EclipseXmlFormatPreferences.create();
String delimiter = TextUtilities.getDefaultLineDelimiter(document);
- XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, delimiter);
+ XmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(prefs, style, delimiter);
+ printer.setEndWithNewline(endWithNewline);
if (indentationLevels != null) {
printer.setIndentationLevels(indentationLevels);
@@ -360,7 +381,8 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
* adjusted (for example to make the edit smaller if the beginning and/or end is
* identical, and so on)
*/
- private static ReplaceEdit createReplaceEdit(IStructuredDocument document, int replaceStart,
+ @VisibleForTesting
+ static ReplaceEdit createReplaceEdit(IDocument document, int replaceStart,
int replaceEnd, String formatted, XmlFormatPreferences prefs) {
// If replacing a node somewhere in the middle, start the replacement at the
// beginning of the current line
@@ -399,7 +421,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
if (c == '\n') {
beginsWithNewline = true;
break;
- } else if (!Character.isWhitespace(c)) {
+ } else if (!Character.isWhitespace(c)) { // \r is whitespace so is handled correctly
break;
}
}
@@ -411,6 +433,9 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
replaceStart = prevNewlineIndex;
}
prevNewlineIndex = index;
+ if (index > 0 && document.getChar(index - 1) == '\r') {
+ prevNewlineIndex--;
+ }
} else if (!Character.isWhitespace(c)) {
break;
}
@@ -423,16 +448,16 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
}
// Search forwards too
- prevNewlineIndex = -1;
+ int nextNewlineIndex = -1;
try {
int max = document.getLength();
for (index = replaceEnd; index < max; index++) {
char c = document.getChar(index);
if (c == '\n') {
- if (prevNewlineIndex != -1) {
- replaceEnd = prevNewlineIndex + 1;
+ if (nextNewlineIndex != -1) {
+ replaceEnd = nextNewlineIndex + 1;
}
- prevNewlineIndex = index;
+ nextNewlineIndex = index;
} else if (!Character.isWhitespace(c)) {
break;
}
@@ -440,7 +465,6 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
} catch (BadLocationException e) {
AdtPlugin.log(e, null);
}
-
boolean endsWithNewline = false;
for (int i = formatted.length() - 1; i >= 0; i--) {
char c = formatted.charAt(i);
@@ -452,8 +476,8 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
}
}
- if (prefs.removeEmptyLines && prevNewlineIndex != -1 && endsWithNewline) {
- replaceEnd = prevNewlineIndex + 1;
+ if (prefs.removeEmptyLines && nextNewlineIndex != -1 && endsWithNewline) {
+ replaceEnd = nextNewlineIndex + 1;
}
// Figure out how much of the before and after strings are identical and narrow
@@ -503,7 +527,10 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
static XmlFormatStyle guessStyle(IStructuredModel model, Document domDocument) {
// The "layout" style is used for most XML resource file types:
// layouts, color-lists and state-lists, animations, drawables, menus, etc
- XmlFormatStyle style = XmlFormatStyle.LAYOUT;
+ XmlFormatStyle style = XmlFormatStyle.get(domDocument);
+ if (style == XmlFormatStyle.FILE) {
+ style = XmlFormatStyle.LAYOUT;
+ }
// The "resource" style is used for most value-based XML files:
// strings, dimensions, booleans, colors, integers, plurals,
@@ -532,7 +559,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
String[] segments = resourceFolder.split("-"); //$NON-NLS-1$
ResourceType type = ResourceType.getEnum(segments[0]);
if (type != null) {
- style = XmlFormatStyle.get(type);
+ style = EclipseXmlPrettyPrinter.get(type);
}
}
}
@@ -541,6 +568,50 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
return style;
}
+ private Boolean isAndroid(IDocument document) {
+ if (mIsAndroid == null) {
+ // Look up the corresponding IResource for this document. This isn't
+ // readily available, so take advantage of the structured model's base location
+ // string and convert it to an IResource to look up its project.
+ if (document instanceof IStructuredDocument) {
+ IStructuredDocument structuredDocument = (IStructuredDocument) document;
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+
+ IStructuredModel model = modelManager.getModelForRead(structuredDocument);
+ if (model != null) {
+ String location = model.getBaseLocation();
+ model.releaseFromRead();
+ if (location != null) {
+ if (!location.endsWith(ANDROID_MANIFEST_XML)
+ && !location.contains("/res/")) { //$NON-NLS-1$
+ // See if it looks like a foreign document
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ IWorkspaceRoot root = workspace.getRoot();
+ IResource member = root.findMember(location);
+ if (member.exists()) {
+ IProject project = member.getProject();
+ if (project.isAccessible() &&
+ !BaseProjectHelper.isAndroidProject(project)) {
+ mIsAndroid = false;
+ return false;
+ }
+ }
+ }
+ // Ignore Maven POM files even in Android projects
+ if (location.endsWith("/pom.xml")) { //$NON-NLS-1$
+ mIsAndroid = false;
+ return false;
+ }
+ }
+ }
+ }
+
+ mIsAndroid = true;
+ }
+
+ return mIsAndroid.booleanValue();
+ }
+
@Override
public void formatterStarts(final IFormattingContext context) {
// Use Eclipse XML formatter instead?
@@ -563,6 +634,15 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
IDocument document = (IDocument) context.getProperty(CONTEXT_MEDIUM);
mPartitions.offer(partition);
mDocuments.offer(document);
+
+ if (!isAndroid(document)) {
+ // It's some foreign type of project: use default
+ // formatter
+ delegate = getDelegate();
+ if (delegate != null) {
+ delegate.formatterStarts(context);
+ }
+ }
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatPreferences.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlFormatPreferences.java
index 04441fd..6c00b8e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatPreferences.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlFormatPreferences.java
@@ -15,60 +15,41 @@
*/
package com.android.ide.eclipse.adt.internal.editors.formatting;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatPreferences;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
-import com.android.ide.eclipse.adt.internal.preferences.AttributeSortOrder;
+import com.android.ide.common.xml.XmlAttributeSortOrder;
import org.eclipse.core.runtime.Preferences;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ui.internal.editors.text.EditorsPlugin;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.xml.core.internal.XMLCorePlugin;
import org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames;
+import org.w3c.dom.Attr;
+
+import java.util.Comparator;
/**
* Formatting preferences used by the Android XML formatter.
*/
-public class XmlFormatPreferences {
- /** Use the Eclipse indent (tab/space, indent size) settings? */
- public boolean useEclipseIndent = false;
-
- /** Remove empty lines in all cases? */
- public boolean removeEmptyLines = false;
-
- /** Reformat the text and comment blocks? */
- public boolean reflowText = false;
-
- /** Join lines when reformatting text and comment blocks? */
- public boolean joinLines = false;
-
- /** Can attributes appear on the same line as the opening line if there is just one of them? */
- public boolean oneAttributeOnFirstLine = true;
-
- /** The sorting order to use when formatting */
- public AttributeSortOrder sortAttributes = AttributeSortOrder.LOGICAL;
-
- /** Should there be a space before the closing > or /> ? */
- public boolean spaceBeforeClose = true;
-
- /** The string to insert for each indentation level */
- private String mOneIndentUnit = " "; //$NON-NLS-1$
-
- /** Tab width (number of spaces to display for a tab) */
- private int mTabWidth = -1; // -1: uninitialized
-
+public class EclipseXmlFormatPreferences extends XmlFormatPreferences {
@VisibleForTesting
- protected XmlFormatPreferences() {
+ protected EclipseXmlFormatPreferences() {
}
/**
- * Creates a new {@link XmlFormatPreferences} based on the current settings
+ * Creates a new {@link EclipseXmlFormatPreferences} based on the current settings
* in {@link AdtPrefs}
*
- * @return an {@link XmlFormatPreferences} object
+ * @return an {@link EclipseXmlFormatPreferences} object
*/
- public static XmlFormatPreferences create() {
- XmlFormatPreferences p = new XmlFormatPreferences();
+ @NonNull
+ public static EclipseXmlFormatPreferences create() {
+ EclipseXmlFormatPreferences p = new EclipseXmlFormatPreferences();
AdtPrefs prefs = AdtPrefs.getPrefs();
p.useEclipseIndent = prefs.isUseEclipseIndent();
@@ -80,14 +61,37 @@ public class XmlFormatPreferences {
return p;
}
+ @Override
+ @Nullable
+ public Comparator<Attr> getAttributeComparator() {
+ // Can't just skip sorting; the attribute table moves attributes out of order
+ // due to hashing, so sort by node positions
+ if (sortAttributes == XmlAttributeSortOrder.NO_SORTING) {
+ return EXISTING_ORDER_COMPARATOR;
+ }
+ return sortAttributes.getAttributeComparator();
+ }
+
+ private static final Comparator<Attr> EXISTING_ORDER_COMPARATOR = new Comparator<Attr>() {
+ @Override
+ public int compare(Attr attr1, Attr attr2) {
+ IndexedRegion region1 = (IndexedRegion) attr1;
+ IndexedRegion region2 = (IndexedRegion) attr2;
+
+ return region1.getStartOffset() - region2.getStartOffset();
+ }
+ };
+
// The XML module settings do not have a public API. We should replace this with JDT
// settings anyway since that's more likely what users have configured and want applied
// to their XML files
+
/**
* Returns the string to use to indent one indentation level
*
* @return the string used to indent one indentation level
*/
+ @Override
@SuppressWarnings({
"restriction", "deprecation"
})
@@ -119,6 +123,7 @@ public class XmlFormatPreferences {
*
* @return the number of spaces used to display a single tab character
*/
+ @Override
@SuppressWarnings("restriction") // Editor settings
public int getTabWidth() {
if (mTabWidth == -1) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlPrettyPrinter.java
new file mode 100644
index 0000000..d3f7ec8
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/EclipseXmlPrettyPrinter.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.formatting;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.xml.XmlFormatPreferences;
+import com.android.ide.common.xml.XmlFormatStyle;
+import com.android.ide.common.xml.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.SdkUtils;
+import com.android.utils.XmlUtils;
+
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * Eclipse customization of the {@link EclipseXmlPrettyPrinter} which takes advantage of the
+ * Eclipse DOM Api to track additional information, such as whether an element with no children
+ * was of the open form ({@code <foo></foo>}) or the closed form ({@code <foo/>}), the ability to
+ * look up the original source (for proper entity handling), the ability to preserve attribute
+ * source order, etc.
+ */
+@SuppressWarnings("restriction") // WST XML API
+public class EclipseXmlPrettyPrinter extends XmlPrettyPrinter {
+
+ /**
+ * Creates a new {@link com.android.ide.common.xml.XmlPrettyPrinter}
+ *
+ * @param prefs the preferences to format with
+ * @param style the style to format with
+ * @param lineSeparator the line separator to use, such as "\n" (can be null, in which case the
+ * system default is looked up via the line.separator property)
+ */
+ public EclipseXmlPrettyPrinter(
+ XmlFormatPreferences prefs,
+ XmlFormatStyle style,
+ String lineSeparator) {
+ super(prefs, style, lineSeparator == null ? getDefaultLineSeparator() : lineSeparator);
+ }
+
+ /**
+ * Pretty-prints the given XML document, which must be well-formed. If it is not,
+ * the original unformatted XML document is returned
+ *
+ * @param xml the XML content to format
+ * @param prefs the preferences to format with
+ * @param style the style to format with
+ * @param lineSeparator the line separator to use, such as "\n" (can be null, in which
+ * case the system default is looked up via the line.separator property)
+ * @return the formatted document (or if a parsing error occurred, returns the
+ * unformatted document)
+ */
+ @NonNull
+ public static String prettyPrint(
+ @NonNull String xml,
+ @NonNull XmlFormatPreferences prefs,
+ @NonNull XmlFormatStyle style,
+ @Nullable String lineSeparator) {
+ Document document = DomUtilities.parseStructuredDocument(xml);
+ if (document != null) {
+ EclipseXmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(prefs, style,
+ lineSeparator);
+ if (xml.endsWith("\n")) { //$NON-NLS-1$
+ printer.setEndWithNewline(true);
+ }
+
+ StringBuilder sb = new StringBuilder(3 * xml.length() / 2);
+ printer.prettyPrint(-1, document, null, null, sb, false /*openTagOnly*/);
+ return sb.toString();
+ } else {
+ // Parser error: just return the unformatted content
+ return xml;
+ }
+ }
+
+ @NonNull
+ public static String prettyPrint(@NonNull Node node, boolean endWithNewline) {
+ return prettyPrint(node, EclipseXmlFormatPreferences.create(), XmlFormatStyle.get(node),
+ null, endWithNewline);
+ }
+
+ private static String getDefaultLineSeparator() {
+ org.eclipse.jface.text.Document blank = new org.eclipse.jface.text.Document();
+ String lineSeparator = TextUtilities.getDefaultLineDelimiter(blank);
+ if (lineSeparator == null) {
+ lineSeparator = SdkUtils.getLineSeparator();
+ }
+
+ return lineSeparator;
+ }
+
+ /**
+ * Pretty prints the given node
+ *
+ * @param node the node, usually a document, to be printed
+ * @param prefs the formatting preferences
+ * @param style the formatting style to use
+ * @param lineSeparator the line separator to use, or null to use the
+ * default
+ * @return a formatted string
+ */
+ @NonNull
+ public static String prettyPrint(
+ @NonNull Node node,
+ @NonNull XmlFormatPreferences prefs,
+ @NonNull XmlFormatStyle style,
+ @Nullable String lineSeparator,
+ boolean endWithNewline) {
+ XmlPrettyPrinter printer = new EclipseXmlPrettyPrinter(prefs, style, lineSeparator);
+ printer.setEndWithNewline(endWithNewline);
+ StringBuilder sb = new StringBuilder(1000);
+ printer.prettyPrint(-1, node, null, null, sb, false /*openTagOnly*/);
+ String xml = sb.toString();
+ if (node.getNodeType() == Node.DOCUMENT_NODE && !xml.startsWith("<?")) { //$NON-NLS-1$
+ xml = XmlUtils.XML_PROLOG + xml;
+ }
+ return xml;
+ }
+
+ @Nullable
+ @Override
+ protected String getSource(@NonNull Node node) {
+ // In Eclipse, org.w3c.dom.DocumentType.getTextContent() returns null
+ if (node instanceof IDOMNode) {
+ // Get the original source string. This will contain the actual entities
+ // such as "&gt;" instead of ">" which it gets turned into for the DOM nodes.
+ // By operating on source we can preserve the user's entities rather than
+ // having &gt; for example always turned into >.
+ IDOMNode textImpl = (IDOMNode) node;
+ return textImpl.getSource();
+ }
+
+ return super.getSource(node);
+ }
+
+ @Override
+ protected boolean isEmptyTag(Element element) {
+ if (element instanceof IDOMElement) {
+ IDOMElement elementImpl = (IDOMElement) element;
+ if (elementImpl.isEmptyTag()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for a resource of the given type
+ *
+ * @param resourceType the type of resource to be formatted
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle get(ResourceType resourceType) {
+ switch (resourceType) {
+ case ARRAY:
+ case ATTR:
+ case BOOL:
+ case DECLARE_STYLEABLE:
+ case DIMEN:
+ case FRACTION:
+ case ID:
+ case INTEGER:
+ case STRING:
+ case PLURALS:
+ case STYLE:
+ case STYLEABLE:
+ case COLOR:
+ return XmlFormatStyle.RESOURCE;
+
+ case LAYOUT:
+ return XmlFormatStyle.LAYOUT;
+
+ case DRAWABLE:
+ case MENU:
+ case ANIM:
+ case ANIMATOR:
+ case INTERPOLATOR:
+ default:
+ return XmlFormatStyle.FILE;
+ }
+ }
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for resource files in the given resource
+ * folder
+ *
+ * @param folderType the type of folder containing the resource file
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle getForFolderType(ResourceFolderType folderType) {
+ switch (folderType) {
+ case LAYOUT:
+ return XmlFormatStyle.LAYOUT;
+ case COLOR:
+ case VALUES:
+ return XmlFormatStyle.RESOURCE;
+ case ANIM:
+ case ANIMATOR:
+ case DRAWABLE:
+ case INTERPOLATOR:
+ case MENU:
+ default:
+ return XmlFormatStyle.FILE;
+ }
+ }
+
+ /**
+ * Returns the {@link XmlFormatStyle} to use for resource files of the given path.
+ *
+ * @param path the path to the resource file
+ * @return the suitable format style to use
+ */
+ public static XmlFormatStyle getForFile(IPath path) {
+ if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(path.lastSegment())) {
+ return XmlFormatStyle.MANIFEST;
+ }
+
+ if (path.segmentCount() > 2) {
+ String parentName = path.segment(path.segmentCount() - 2);
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
+ return getForFolderType(folderType);
+ }
+
+ return XmlFormatStyle.FILE;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatStyle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatStyle.java
deleted file mode 100644
index cb6ee5d..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatStyle.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.eclipse.adt.internal.editors.formatting;
-
-import com.android.SdkConstants;
-import com.android.resources.ResourceFolderType;
-import com.android.resources.ResourceType;
-
-import org.eclipse.core.runtime.IPath;
-
-/**
- * Style to use when printing the XML. Different types of Android XML files use slightly
- * different preferred formats. For example, in layout files there is typically always a
- * newline between successive elements, whereas in a manifest file there is typically only
- * newlines between different types of elements. As another example, in resource files,
- * the format is typically much more compact: the text content of {@code <item>} tags is
- * included on the same line whereas for other layout styles the children are typically
- * placed on a line of their own.
- */
-public enum XmlFormatStyle {
- /** Layout formatting style: blank lines between elements, attributes on separate lines */
- LAYOUT,
-
- /** Similar to layout formatting style, but no blank lines inside opening elements */
- FILE,
-
- /** Resource style: one line per complete element including text child content */
- RESOURCE,
-
- /**
- * Similar to layout style, but no newlines between related elements such as
- * successive {@code <uses-permission>} declarations, and no newlines inside
- * the second level elements (so an {@code <activity>} declaration appears as a
- * single block with no whitespace within it)
- */
- MANIFEST;
-
- /**
- * Returns the {@link XmlFormatStyle} to use for a resource of the given type
- *
- * @param resourceType the type of resource to be formatted
- * @return the suitable format style to use
- */
- public static XmlFormatStyle get(ResourceType resourceType) {
- switch (resourceType) {
- case ARRAY:
- case ATTR:
- case BOOL:
- case DECLARE_STYLEABLE:
- case DIMEN:
- case FRACTION:
- case ID:
- case INTEGER:
- case STRING:
- case PLURALS:
- case STYLE:
- case STYLEABLE:
- case COLOR:
- return RESOURCE;
-
- case LAYOUT:
- return LAYOUT;
-
- case DRAWABLE:
- case MENU:
- case ANIM:
- case ANIMATOR:
- case INTERPOLATOR:
- default:
- return FILE;
- }
- }
-
- /**
- * Returns the {@link XmlFormatStyle} to use for resource files in the given resource
- * folder
- *
- * @param folderType the type of folder containing the resource file
- * @return the suitable format style to use
- */
- public static XmlFormatStyle getForFolderType(ResourceFolderType folderType) {
- switch (folderType) {
- case LAYOUT:
- return LAYOUT;
- case COLOR:
- case VALUES:
- return RESOURCE;
- case ANIM:
- case ANIMATOR:
- case DRAWABLE:
- case INTERPOLATOR:
- case MENU:
- default:
- return FILE;
- }
- }
-
- /**
- * Returns the {@link XmlFormatStyle} to use for resource files of the given path.
- *
- * @param path the path to the resource file
- * @return the suitable format style to use
- */
- public static XmlFormatStyle getForFile(IPath path) {
- if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(path.lastSegment())) {
- return MANIFEST;
- }
-
- if (path.segmentCount() > 2) {
- String parentName = path.segment(path.segmentCount() - 2);
- ResourceFolderType folderType = ResourceFolderType.getFolderType(parentName);
- return getForFolderType(folderType);
- }
-
- return FILE;
- }
-} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
deleted file mode 100644
index 1dd32c7..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
+++ /dev/null
@@ -1,976 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.eclipse.adt.internal.editors.formatting;
-
-import static com.android.SdkConstants.TAG_COLOR;
-import static com.android.SdkConstants.TAG_DIMEN;
-import static com.android.SdkConstants.TAG_ITEM;
-import static com.android.SdkConstants.TAG_STRING;
-import static com.android.SdkConstants.TAG_STYLE;
-import static com.android.SdkConstants.XMLNS;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
-import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
-import com.android.utils.SdkUtils;
-import com.android.utils.XmlUtils;
-
-import org.eclipse.wst.xml.core.internal.document.DocumentTypeImpl;
-import org.eclipse.wst.xml.core.internal.document.ElementImpl;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Visitor which walks over the subtree of the DOM to be formatted and pretty prints
- * the DOM into the given {@link StringBuilder}
- */
-@SuppressWarnings("restriction")
-public class XmlPrettyPrinter {
- private static final String COMMENT_BEGIN = "<!--"; //$NON-NLS-1$
- private static final String COMMENT_END = "-->"; //$NON-NLS-1$
-
- /** The style to print the XML in */
- private final XmlFormatStyle mStyle;
- /** Formatting preferences to use when formatting the XML */
- private final XmlFormatPreferences mPrefs;
- /** Start node to start formatting at */
- private Node mStartNode;
- /** Start node to stop formatting after */
- private Node mEndNode;
- /** Whether the visitor is currently in range */
- private boolean mInRange;
- /** Output builder */
- private StringBuilder mOut;
- /** String to insert for a single indentation level */
- private String mIndentString;
- /** Line separator to use */
- private String mLineSeparator;
- /** If true, we're only formatting an open tag */
- private boolean mOpenTagOnly;
- /** List of indentation to use for each given depth */
- private String[] mIndentationLevels;
-
- /**
- * Creates a new {@link XmlPrettyPrinter}
- *
- * @param prefs the preferences to format with
- * @param style the style to format with
- * @param lineSeparator the line separator to use, such as "\n" (can be null, in which
- * case the system default is looked up via the line.separator property)
- */
- public XmlPrettyPrinter(XmlFormatPreferences prefs, XmlFormatStyle style,
- String lineSeparator) {
- mPrefs = prefs;
- mStyle = style;
- if (lineSeparator == null) {
- lineSeparator = SdkUtils.getLineSeparator();
- }
- mLineSeparator = lineSeparator;
- }
-
- /**
- * Sets the indentation levels to use (indentation string to use for each depth,
- * indexed by depth
- *
- * @param indentationLevels an array of strings to use for the various indentation
- * levels
- */
- public void setIndentationLevels(String[] indentationLevels) {
- mIndentationLevels = indentationLevels;
- }
-
- /**
- * Pretty-prints the given XML document, which must be well-formed. If it is not,
- * the original unformatted XML document is returned
- *
- * @param xml the XML content to format
- * @param prefs the preferences to format with
- * @param style the style to format with
- * @param lineSeparator the line separator to use, such as "\n" (can be null, in which
- * case the system default is looked up via the line.separator property)
- * @return the formatted document (or if a parsing error occurred, returns the
- * unformatted document)
- */
- @NonNull
- public static String prettyPrint(
- @NonNull String xml,
- @NonNull XmlFormatPreferences prefs,
- @NonNull XmlFormatStyle style,
- @Nullable String lineSeparator) {
- Document document = DomUtilities.parseStructuredDocument(xml);
- if (document != null) {
- XmlPrettyPrinter printer = new XmlPrettyPrinter(prefs, style, lineSeparator);
- StringBuilder sb = new StringBuilder(3 * xml.length() / 2);
- printer.prettyPrint(-1, document, null, null, sb, false /*openTagOnly*/);
- return sb.toString();
- } else {
- // Parser error: just return the unformatted content
- return xml;
- }
- }
-
- /**
- * Start pretty-printing at the given node, which must either be the
- * startNode or contain it as a descendant.
- *
- * @param rootDepth the depth of the given node, used to determine indentation
- * @param root the node to start pretty printing from (which may not itself be
- * included in the start to end node range but should contain it)
- * @param startNode the node to start formatting at
- * @param endNode the node to end formatting at
- * @param out the {@link StringBuilder} to pretty print into
- * @param openTagOnly if true, only format the open tag of the startNode (and nothing
- * else)
- */
- public void prettyPrint(int rootDepth, Node root, Node startNode, Node endNode,
- StringBuilder out, boolean openTagOnly) {
- if (startNode == null) {
- startNode = root;
- }
- if (endNode == null) {
- endNode = root;
- }
- assert !openTagOnly || startNode == endNode;
-
- mStartNode = startNode;
- mOpenTagOnly = openTagOnly;
- mEndNode = endNode;
- mOut = out;
- mInRange = false;
- mIndentString = mPrefs.getOneIndentUnit();
-
- visitNode(rootDepth, root);
- }
-
- /** Visit the given node at the given depth */
- private void visitNode(int depth, Node node) {
- if (node == mStartNode) {
- mInRange = true;
- }
-
- if (mInRange) {
- visitBeforeChildren(depth, node);
- if (mOpenTagOnly && mStartNode == node) {
- mInRange = false;
- return;
- }
- }
-
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- visitNode(depth + 1, child);
- }
-
- if (mInRange) {
- visitAfterChildren(depth, node);
- }
-
- if (node == mEndNode) {
- mInRange = false;
- }
- }
-
- private void visitBeforeChildren(int depth, Node node) {
- short type = node.getNodeType();
- switch (type) {
- case Node.DOCUMENT_NODE:
- case Node.DOCUMENT_FRAGMENT_NODE:
- // Nothing to do
- break;
-
- case Node.ATTRIBUTE_NODE:
- // Handled as part of processing elements
- break;
-
- case Node.ELEMENT_NODE: {
- printOpenElementTag(depth, node);
- break;
- }
-
- case Node.TEXT_NODE: {
- printText(node);
- break;
- }
-
- case Node.CDATA_SECTION_NODE:
- printCharacterData(depth, node);
- break;
-
- case Node.PROCESSING_INSTRUCTION_NODE:
- printProcessingInstruction(node);
- break;
-
- case Node.COMMENT_NODE: {
- printComment(depth, node);
- break;
- }
-
- case Node.DOCUMENT_TYPE_NODE:
- printDocType(node);
- break;
-
- case Node.ENTITY_REFERENCE_NODE:
- case Node.ENTITY_NODE:
- case Node.NOTATION_NODE:
- break;
- default:
- assert false : type;
- }
- }
-
- private void visitAfterChildren(int depth, Node node) {
- short type = node.getNodeType();
- switch (type) {
- case Node.ATTRIBUTE_NODE:
- // Handled as part of processing elements
- break;
- case Node.ELEMENT_NODE: {
- printCloseElementTag(depth, node);
- break;
- }
- }
- }
-
- private void printProcessingInstruction(Node node) {
- mOut.append("<?xml "); //$NON-NLS-1$
- mOut.append(node.getNodeValue().trim());
- mOut.append('?').append('>').append(mLineSeparator);
- }
-
- private void printDocType(Node node) {
- // In Eclipse, org.w3c.dom.DocumentType.getTextContent() returns null
- if (node instanceof DocumentTypeImpl) {
- String content = ((DocumentTypeImpl) node).getSource();
- mOut.append(content);
- mOut.append(mLineSeparator);
- }
- }
-
- private void printCharacterData(int depth, Node node) {
- String nodeValue = node.getNodeValue();
- boolean separateLine = nodeValue.indexOf('\n') != -1;
- if (separateLine && !endsWithLineSeparator()) {
- mOut.append(mLineSeparator);
- }
- mOut.append("<![CDATA["); //$NON-NLS-1$
- mOut.append(nodeValue);
- mOut.append("]]>"); //$NON-NLS-1$
- if (separateLine) {
- mOut.append(mLineSeparator);
- }
- }
-
- private void printText(Node node) {
- boolean escape = true;
- String text = node.getNodeValue();
-
- if (node instanceof IDOMNode) {
- // Get the original source string. This will contain the actual entities
- // such as "&gt;" instead of ">" which it gets turned into for the DOM nodes.
- // By operating on source we can preserve the user's entities rather than
- // having &gt; for example always turned into >.
- IDOMNode textImpl = (IDOMNode) node;
- text = textImpl.getSource();
- escape = false;
- }
-
- // Most text nodes are just whitespace for formatting (which we're replacing)
- // so look for actual text content and extract that part out
- String trimmed = text.trim();
- if (trimmed.length() > 0) {
- // TODO: Reformat the contents if it is too wide?
-
- // Note that we append the actual text content, NOT the trimmed content,
- // since the whitespace may be significant, e.g.
- // <string name="toast_sync_error">Sync error: <xliff:g id="error">%1$s</xliff:g>...
-
- // However, we should remove all blank lines in the prefix and suffix of the
- // text node, or we will end up inserting additional blank lines each time you're
- // formatting a text node within an outer element (which also adds spacing lines)
- int lastPrefixNewline = -1;
- for (int i = 0, n = text.length(); i < n; i++) {
- char c = text.charAt(i);
- if (c == '\n') {
- lastPrefixNewline = i;
- } else if (!Character.isWhitespace(c)) {
- break;
- }
- }
- int firstSuffixNewline = -1;
- for (int i = text.length() - 1; i >= 0; i--) {
- char c = text.charAt(i);
- if (c == '\n') {
- firstSuffixNewline = i;
- } else if (!Character.isWhitespace(c)) {
- break;
- }
- }
- if (lastPrefixNewline != -1 || firstSuffixNewline != -1) {
- if (firstSuffixNewline == -1) {
- firstSuffixNewline = text.length();
- }
- text = text.substring(lastPrefixNewline + 1, firstSuffixNewline);
- }
-
- if (escape) {
- XmlUtils.appendXmlTextValue(mOut, text);
- } else {
- // Text is already escaped
- mOut.append(text);
- }
-
- if (mStyle != XmlFormatStyle.RESOURCE) {
- mOut.append(mLineSeparator);
- }
- }
- }
-
- private void printComment(int depth, Node node) {
- String comment = node.getNodeValue();
- boolean multiLine = comment.indexOf('\n') != -1;
- String trimmed = comment.trim();
-
- // See if this is an "end-of-the-line" comment, e.g. it is not a multi-line
- // comment and it appears on the same line as an opening or closing element tag;
- // if so, continue to place it as a suffix comment
- boolean isSuffixComment = false;
- if (!multiLine) {
- Node previous = node.getPreviousSibling();
- isSuffixComment = true;
- while (previous != null) {
- short type = previous.getNodeType();
- if (type == Node.TEXT_NODE || type == Node.COMMENT_NODE) {
- if (previous.getNodeValue().indexOf('\n') != -1) {
- isSuffixComment = false;
- break;
- }
- } else {
- break;
- }
- previous = previous.getPreviousSibling();
- }
- if (isSuffixComment) {
- // Remove newline added by element open tag or element close tag
- if (endsWithLineSeparator()) {
- removeLastLineSeparator();
- }
- mOut.append(' ');
- }
- }
-
- // Put the comment on a line on its own? Only if it was separated by a blank line
- // in the previous version of the document. In other words, if the document
- // adds blank lines between comments this formatter will preserve that fact, and vice
- // versa for a tightly formatted document it will preserve that convention as well.
- if (!mPrefs.removeEmptyLines && depth > 0 && !isSuffixComment) {
- Node curr = node.getPreviousSibling();
- if (curr == null) {
- mOut.append(mLineSeparator);
- } else if (curr.getNodeType() == Node.TEXT_NODE) {
- String text = curr.getNodeValue();
- // Count how many newlines we find in the trailing whitespace of the
- // text node
- int newLines = 0;
- for (int i = text.length() - 1; i >= 0; i--) {
- char c = text.charAt(i);
- if (Character.isWhitespace(c)) {
- if (c == '\n') {
- newLines++;
- if (newLines == 2) {
- break;
- }
- }
- } else {
- break;
- }
- }
- if (newLines >= 2) {
- mOut.append(mLineSeparator);
- } else if (text.trim().length() == 0 && curr.getPreviousSibling() == null) {
- // Comment before first child in node
- mOut.append(mLineSeparator);
- }
- }
- }
-
-
- // TODO: Reformat the comment text?
- if (!multiLine) {
- if (!isSuffixComment) {
- indent(depth);
- }
- mOut.append(COMMENT_BEGIN).append(' ');
- mOut.append(trimmed);
- mOut.append(' ').append(COMMENT_END);
- mOut.append(mLineSeparator);
- } else {
- // Strip off blank lines at the beginning and end of the comment text.
- // Find last newline at the beginning of the text:
- int index = 0;
- int end = comment.length();
- int recentNewline = -1;
- while (index < end) {
- char c = comment.charAt(index);
- if (c == '\n') {
- recentNewline = index;
- }
- if (!Character.isWhitespace(c)) {
- break;
- }
- index++;
- }
-
- int start = recentNewline + 1;
-
- // Find last newline at the end of the text
- index = end - 1;
- recentNewline = -1;
- while (index > start) {
- char c = comment.charAt(index);
- if (c == '\n') {
- recentNewline = index;
- }
- if (!Character.isWhitespace(c)) {
- break;
- }
- index--;
- }
-
- end = recentNewline == -1 ? index + 1 : recentNewline;
- if (start >= end) {
- // It's a blank comment like <!-- \n\n--> - just clean it up
- if (!isSuffixComment) {
- indent(depth);
- }
- mOut.append(COMMENT_BEGIN).append(' ').append(COMMENT_END);
- mOut.append(mLineSeparator);
- return;
- }
-
- trimmed = comment.substring(start, end);
-
- // When stripping out prefix and suffix blank lines we might have ended up
- // with a single line comment again so check and format single line comments
- // without newlines inside the <!-- --> delimiters
- multiLine = trimmed.indexOf('\n') != -1;
- if (multiLine) {
- indent(depth);
- mOut.append(COMMENT_BEGIN);
- mOut.append(mLineSeparator);
-
- // See if we need to add extra spacing to keep alignment. Consider a comment
- // like this:
- // <!-- Deprecated strings - Move the identifiers to this section,
- // and remove the actual text. -->
- // This String will be
- // " Deprecated strings - Move the identifiers to this section,\n" +
- // " and remove the actual text. -->"
- // where the left side column no longer lines up.
- // To fix this, we need to insert some extra whitespace into the first line
- // of the string; in particular, the exact number of characters that the
- // first line of the comment was indented with!
-
- // However, if the comment started like this:
- // <!--
- // /** Copyright
- // -->
- // then obviously the align-indent is 0, so we only want to compute an
- // align indent when we don't find a newline before the content
- boolean startsWithNewline = false;
- for (int i = 0; i < start; i++) {
- if (comment.charAt(i) == '\n') {
- startsWithNewline = true;
- break;
- }
- }
- if (!startsWithNewline) {
- Node previous = node.getPreviousSibling();
- if (previous != null && previous.getNodeType() == Node.TEXT_NODE) {
- String prevText = previous.getNodeValue();
- int indentation = COMMENT_BEGIN.length();
- for (int i = prevText.length() - 1; i >= 0; i--) {
- char c = prevText.charAt(i);
- if (c == '\n') {
- break;
- } else {
- indentation += (c == '\t') ? mPrefs.getTabWidth() : 1;
- }
- }
-
- // See if the next line after the newline has indentation; if it doesn't,
- // leave things alone. This fixes a case like this:
- // <!-- This is the
- // comment block -->
- // such that it doesn't turn it into
- // <!--
- // This is the
- // comment block
- // -->
- // In this case we instead want
- // <!--
- // This is the
- // comment block
- // -->
- int minIndent = Integer.MAX_VALUE;
- String[] lines = trimmed.split("\n"); //$NON-NLS-1$
- // Skip line 0 since we know that it doesn't start with a newline
- for (int i = 1; i < lines.length; i++) {
- int indent = 0;
- String line = lines[i];
- for (int j = 0; j < line.length(); j++) {
- char c = line.charAt(j);
- if (!Character.isWhitespace(c)) {
- // Only set minIndent if there's text content on the line;
- // blank lines can exist in the comment without affecting
- // the overall minimum indentation boundary.
- if (indent < minIndent) {
- minIndent = indent;
- }
- break;
- } else {
- indent += (c == '\t') ? mPrefs.getTabWidth() : 1;
- }
- }
- }
-
- if (minIndent < indentation) {
- indentation = minIndent;
-
- // Subtract any indentation that is already present on the line
- String line = lines[0];
- for (int j = 0; j < line.length(); j++) {
- char c = line.charAt(j);
- if (!Character.isWhitespace(c)) {
- break;
- } else {
- indentation -= (c == '\t') ? mPrefs.getTabWidth() : 1;
- }
- }
- }
-
- for (int i = 0; i < indentation; i++) {
- mOut.append(' ');
- }
-
- if (indentation < 0) {
- boolean prefixIsSpace = true;
- for (int i = 0; i < -indentation && i < trimmed.length(); i++) {
- if (!Character.isWhitespace(trimmed.charAt(i))) {
- prefixIsSpace = false;
- break;
- }
- }
- if (prefixIsSpace) {
- trimmed = trimmed.substring(-indentation);
- }
- }
- }
- }
-
- mOut.append(trimmed);
- mOut.append(mLineSeparator);
- indent(depth);
- mOut.append(COMMENT_END);
- mOut.append(mLineSeparator);
- } else {
- mOut.append(COMMENT_BEGIN).append(' ');
- mOut.append(trimmed);
- mOut.append(' ').append(COMMENT_END);
- mOut.append(mLineSeparator);
- }
- }
-
- // Preserve whitespace after comment: See if the original document had two or
- // more newlines after the comment, and if so have a blank line between this
- // comment and the next
- Node next = node.getNextSibling();
- if (!mPrefs.removeEmptyLines && next != null && next.getNodeType() == Node.TEXT_NODE) {
- String text = next.getNodeValue();
- int newLinesBeforeText = 0;
- for (int i = 0, n = text.length(); i < n; i++) {
- char c = text.charAt(i);
- if (c == '\n') {
- newLinesBeforeText++;
- if (newLinesBeforeText == 2) {
- // Yes
- mOut.append(mLineSeparator);
- break;
- }
- } else if (!Character.isWhitespace(c)) {
- break;
- }
- }
- }
- }
-
- private boolean endsWithLineSeparator() {
- int separatorLength = mLineSeparator.length();
- if (mOut.length() >= separatorLength) {
- for (int i = 0, j = mOut.length() - separatorLength; i < separatorLength; i++) {
- if (mOut.charAt(j) != mLineSeparator.charAt(i)) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- private void removeLastLineSeparator() {
- mOut.setLength(mOut.length() - mLineSeparator.length());
- }
-
- private void printOpenElementTag(int depth, Node node) {
- Element element = (Element) node;
- if (newlineBeforeElementOpen(element, depth)) {
- mOut.append(mLineSeparator);
- }
- if (indentBeforeElementOpen(element, depth)) {
- indent(depth);
- }
- mOut.append('<').append(element.getTagName());
-
- NamedNodeMap attributes = element.getAttributes();
- int attributeCount = attributes.getLength();
- if (attributeCount > 0) {
- // Sort the attributes
- List<Attr> attributeList = new ArrayList<Attr>();
- for (int i = 0, n = attributeCount; i < n; i++) {
- attributeList.add((Attr) attributes.item(i));
- }
- Comparator<Attr> comparator = mPrefs.sortAttributes.getAttributeComparator();
- Collections.sort(attributeList, comparator);
-
- // Put the single attribute on the same line as the element tag?
- boolean singleLine = mPrefs.oneAttributeOnFirstLine && attributeCount == 1
- // In resource files we always put all the attributes (which is
- // usually just zero, one or two) on the same line
- || mStyle == XmlFormatStyle.RESOURCE;
-
- // We also place the namespace declaration on the same line as the root element,
- // but this doesn't also imply singleLine handling; subsequent attributes end up
- // on their own lines
- boolean indentNextAttribute;
- if (singleLine || (depth == 0 && XMLNS.equals(attributeList.get(0).getPrefix()))) {
- mOut.append(' ');
- indentNextAttribute = false;
- } else {
- mOut.append(mLineSeparator);
- indentNextAttribute = true;
- }
-
- Attr last = attributeList.get(attributeCount - 1);
- for (Attr attribute : attributeList) {
- if (indentNextAttribute) {
- indent(depth + 1);
- }
- mOut.append(attribute.getName());
- mOut.append('=').append('"');
- XmlUtils.appendXmlAttributeValue(mOut, attribute.getValue());
- mOut.append('"');
-
- // Don't add a newline at the last attribute line; the > should
- // immediately follow the last attribute
- if (attribute != last) {
- mOut.append(singleLine ? " " : mLineSeparator); //$NON-NLS-1$
- indentNextAttribute = !singleLine;
- }
- }
- }
-
- boolean isClosed = isEmptyTag(element);
-
- // Add a space before the > or /> ? In resource files, only do this when closing the
- // element
- if (mPrefs.spaceBeforeClose && (mStyle != XmlFormatStyle.RESOURCE || isClosed)
- // in <selector> files etc still treat the <item> entries as in resource files
- && !TAG_ITEM.equals(element.getTagName())
- && (isClosed || element.getAttributes().getLength() > 0)) {
- mOut.append(' ');
- }
-
- if (isClosed) {
- mOut.append('/');
- }
-
- mOut.append('>');
-
- if (newlineAfterElementOpen(element, depth, isClosed)) {
- mOut.append(mLineSeparator);
- }
- }
-
- private void printCloseElementTag(int depth, Node node) {
- Element element = (Element) node;
- if (isEmptyTag(element)) {
- // Empty tag: Already handled as part of opening tag
- return;
- }
-
- // Put the closing declaration on its own line - unless it's a compact
- // resource file format
- // If the element had element children, separate the end tag from them
- if (newlineBeforeElementClose(element, depth)) {
- mOut.append(mLineSeparator);
- }
- if (indentBeforeElementClose(element, depth)) {
- indent(depth);
- }
- mOut.append('<').append('/');
- mOut.append(node.getNodeName());
- mOut.append('>');
-
- if (newlineAfterElementClose(element, depth)) {
- mOut.append(mLineSeparator);
- }
- }
-
- private boolean newlineBeforeElementOpen(Element element, int depth) {
- if (hasBlankLineAbove()) {
- return false;
- }
-
- if (mPrefs.removeEmptyLines || depth <= 0) {
- return false;
- }
-
- if (isMarkupElement(element)) {
- return false;
- }
-
- // See if this element should be separated from the previous element.
- // This is the case if we are not compressing whitespace (checked above),
- // or if we are not immediately following a comment (in which case the
- // newline would have been added above it), or if we are not in a formatting
- // style where
- if (mStyle == XmlFormatStyle.LAYOUT) {
- // In layouts we always separate elements
- return true;
- }
-
- if (mStyle == XmlFormatStyle.MANIFEST || mStyle == XmlFormatStyle.RESOURCE
- || mStyle == XmlFormatStyle.FILE) {
- Node curr = element.getPreviousSibling();
-
- // <style> elements are traditionally separated unless it follows a comment
- if (TAG_STYLE.equals(element.getTagName())) {
- if (curr == null
- || curr.getNodeType() == Node.ELEMENT_NODE
- || (curr.getNodeType() == Node.TEXT_NODE
- && curr.getNodeValue().trim().length() == 0
- && (curr.getPreviousSibling() == null
- || curr.getPreviousSibling().getNodeType()
- == Node.ELEMENT_NODE))) {
- return true;
- }
- }
-
- // In all other styles, we separate elements if they have a different tag than
- // the previous one (but we don't insert a newline inside tags)
- while (curr != null) {
- short nodeType = curr.getNodeType();
- if (nodeType == Node.ELEMENT_NODE) {
- Element sibling = (Element) curr;
- if (!element.getTagName().equals(sibling.getTagName())) {
- return true;
- }
- break;
- } else if (nodeType == Node.TEXT_NODE) {
- String text = curr.getNodeValue();
- if (text.trim().length() > 0) {
- break;
- }
- // If there is just whitespace, continue looking for a previous sibling
- } else {
- // Any other previous node type, such as a comment, means we don't
- // continue looking: this element should not be separated
- break;
- }
- curr = curr.getPreviousSibling();
- }
- if (curr == null && depth <= 1) {
- // Insert new line inside tag if it's the first element inside the root tag
- return true;
- }
-
- return false;
- }
-
- return false;
- }
-
- private boolean indentBeforeElementOpen(Element element, int depth) {
- if (isMarkupElement(element)) {
- return false;
- }
-
- if (element.getParentNode().getNodeType() == Node.ELEMENT_NODE
- && keepElementAsSingleLine(depth - 1, (Element) element.getParentNode())) {
- return false;
- }
-
- return true;
- }
-
- private boolean indentBeforeElementClose(Element element, int depth) {
- if (isMarkupElement(element)) {
- return false;
- }
-
- char lastOutChar = mOut.charAt(mOut.length() - 1);
- char lastDelimiterChar = mLineSeparator.charAt(mLineSeparator.length() - 1);
- return lastOutChar == lastDelimiterChar;
- }
-
- private boolean newlineAfterElementOpen(Element element, int depth, boolean isClosed) {
- if (hasBlankLineAbove()) {
- return false;
- }
-
- if (isMarkupElement(element)) {
- return false;
- }
-
- // In resource files we keep the child content directly on the same
- // line as the element (unless it has children). in other files, separate them
- return isClosed || !keepElementAsSingleLine(depth, element);
- }
-
- private boolean newlineBeforeElementClose(Element element, int depth) {
- if (hasBlankLineAbove()) {
- return false;
- }
-
- if (isMarkupElement(element)) {
- return false;
- }
-
- return depth == 0 && !mPrefs.removeEmptyLines;
- }
-
- private boolean hasBlankLineAbove() {
- if (mOut.length() < 2 * mLineSeparator.length()) {
- return false;
- }
-
- return SdkUtils.endsWith(mOut, mLineSeparator) &&
- SdkUtils.endsWith(mOut, mOut.length() - mLineSeparator.length(), mLineSeparator);
- }
-
- private boolean newlineAfterElementClose(Element element, int depth) {
- if (hasBlankLineAbove()) {
- return false;
- }
-
- if (isMarkupElement(element)) {
- return false;
- }
-
- return element.getParentNode().getNodeType() == Node.ELEMENT_NODE
- && !keepElementAsSingleLine(depth - 1, (Element) element.getParentNode());
- }
-
- private boolean isMarkupElement(Element element) {
- // The documentation suggests that the allowed tags are <u>, <b> and <i>:
- // developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
- // However, the full set of tags accepted by Html.fromHtml is much larger. Therefore,
- // instead consider *any* element nested inside a <string> definition to be a markup
- // element. See frameworks/base/core/java/android/text/Html.java and look for
- // HtmlToSpannedConverter#handleStartTag.
-
- if (mStyle != XmlFormatStyle.RESOURCE) {
- return false;
- }
-
- Node curr = element.getParentNode();
- while (curr != null) {
- if (TAG_STRING.equals(curr.getNodeName())) {
- return true;
- }
-
- curr = curr.getParentNode();
- }
-
- return false;
- }
-
- /**
- * TODO: Explain why we need to do per-tag decisions on whether to keep them on the
- * same line or not. Show that we can't just do it by depth, or by file type.
- * (style versus plurals example)
- * @param tag
- * @return
- */
- private boolean isSingleLineTag(Element element) {
- String tag = element.getTagName();
-
- return (tag.equals(TAG_ITEM) && mStyle == XmlFormatStyle.RESOURCE)
- || tag.equals(TAG_STRING)
- || tag.equals(TAG_DIMEN)
- || tag.equals(TAG_COLOR);
- }
-
- private boolean keepElementAsSingleLine(int depth, Element element) {
- if (depth == 0) {
- return false;
- }
-
- return isSingleLineTag(element)
- || (mStyle == XmlFormatStyle.RESOURCE
- && !DomUtilities.hasElementChildren(element));
- }
-
- private void indent(int depth) {
- int i = 0;
-
- if (mIndentationLevels != null) {
- for (int j = Math.min(depth, mIndentationLevels.length - 1); j >= 0; j--) {
- String indent = mIndentationLevels[j];
- if (indent != null) {
- mOut.append(indent);
- i = j;
- break;
- }
- }
- }
-
- for (; i < depth; i++) {
- mOut.append(mIndentString);
- }
- }
-
- private boolean isEmptyTag(Element element) {
- boolean isClosed = false;
- if (element instanceof ElementImpl) {
- ElementImpl elementImpl = (ElementImpl) element;
- if (elementImpl.isEmptyTag()) {
- isClosed = true;
- }
- }
- return isClosed;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java
index 90ea2a5..c77c853 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ContextPullParser.java
@@ -16,9 +16,15 @@
package com.android.ide.eclipse.adt.internal.editors.layout;
+import static com.android.SdkConstants.ATTR_IGNORE;
import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
+import static com.android.SdkConstants.GRID_VIEW;
+import static com.android.SdkConstants.LIST_VIEW;
+import static com.android.SdkConstants.SPINNER;
+import static com.android.SdkConstants.TOOLS_URI;
import static com.android.SdkConstants.VALUE_FILL_PARENT;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VIEW_FRAGMENT;
@@ -28,12 +34,14 @@ import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMet
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.IProjectCallback;
-import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.common.resources.ValueResourceParser;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata;
+import com.google.common.collect.Maps;
import org.kxml2.io.KXmlParser;
import java.io.File;
+import java.util.Map;
/**
* Modified {@link KXmlParser} that adds the methods of {@link ILayoutPullParser}, and
@@ -80,7 +88,37 @@ public class ContextPullParser extends KXmlParser implements ILayoutPullParser {
@Override
public Object getViewCookie() {
- return null; // never any key to return
+ String name = super.getName();
+ if (name == null) {
+ return null;
+ }
+
+ // Store tools attributes if this looks like a layout we'll need adapter view
+ // bindings for in the ProjectCallback.
+ if (LIST_VIEW.equals(name)
+ || EXPANDABLE_LIST_VIEW.equals(name)
+ || GRID_VIEW.equals(name)
+ || SPINNER.equals(name)) {
+ Map<String, String> map = null;
+ int count = getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String namespace = getAttributeNamespace(i);
+ if (namespace != null && namespace.equals(TOOLS_URI)) {
+ String attribute = getAttributeName(i);
+ if (attribute.equals(ATTR_IGNORE)) {
+ continue;
+ }
+ if (map == null) {
+ map = Maps.newHashMapWithExpectedSize(4);
+ }
+ map.put(attribute, getAttributeValue(i));
+ }
+ }
+
+ return map;
+ }
+
+ return null;
}
// --- KXMLParser override
@@ -99,12 +137,13 @@ public class ContextPullParser extends KXmlParser implements ILayoutPullParser {
mFragmentLayout = null;
}
+
return name;
}
@Override
public String getAttributeValue(String namespace, String localName) {
- if (localName.equals(ATTR_LAYOUT) && mFragmentLayout != null) {
+ if (ATTR_LAYOUT.equals(localName) && mFragmentLayout != null) {
return mFragmentLayout;
}
@@ -119,10 +158,8 @@ public class ContextPullParser extends KXmlParser implements ILayoutPullParser {
return VALUE_FILL_PARENT;
}
- // Handle unicode escapes
- if (value != null && value.indexOf('\\') != -1) {
- value = AdtUtils.replaceUnicodeEscapes(value);
- }
+ // Handle unicode escapes etc
+ value = ValueResourceParser.unescapeResourceString(value, false, false);
return value;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java
index 7efa34a..99549ab 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutContentAssist.java
@@ -16,13 +16,48 @@
package com.android.ide.eclipse.adt.internal.editors.layout;
+import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_CONTEXT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_ACTIVITY;
+import static com.android.SdkConstants.CLASS_FRAGMENT;
+import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_TAG;
+
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.CustomViewDescriptorService;
+import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CustomViewFinder;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.google.common.collect.Lists;
+import com.google.common.collect.ObjectArrays;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.w3c.dom.Node;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
/**
* Content Assist Processor for /res/layout XML files
*/
@@ -55,6 +90,145 @@ public final class LayoutContentAssist extends AndroidContentAssist {
}
}
+ if (choices == null && parent.length() >= 1 && Character.isLowerCase(parent.charAt(0))) {
+ // Custom view prefix?
+ List<ElementDescriptor> descriptors = getCustomViews();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ List<ElementDescriptor> matches = Lists.newArrayList();
+ for (ElementDescriptor descriptor : descriptors) {
+ if (descriptor.getXmlLocalName().startsWith(parent)) {
+ matches.add(descriptor);
+ }
+ }
+ if (!matches.isEmpty()) {
+ return matches.toArray(new ElementDescriptor[matches.size()]);
+ }
+ }
+ }
+
return choices;
}
+
+ @Override
+ protected ElementDescriptor[] getElementChoicesForTextNode(Node parentNode) {
+ ElementDescriptor[] choices = super.getElementChoicesForTextNode(parentNode);
+
+ // Add in custom views, if any
+ List<ElementDescriptor> descriptors = getCustomViews();
+ if (descriptors != null && !descriptors.isEmpty()) {
+ ElementDescriptor[] array = descriptors.toArray(
+ new ElementDescriptor[descriptors.size()]);
+ choices = ObjectArrays.concat(choices, array, ElementDescriptor.class);
+ choices = sort(choices);
+ }
+
+ return choices;
+ }
+
+ @Nullable
+ private List<ElementDescriptor> getCustomViews() {
+ // Add in custom views, if any
+ IProject project = mEditor.getProject();
+ CustomViewFinder finder = CustomViewFinder.get(project);
+ Collection<String> views = finder.getAllViews();
+ if (views == null) {
+ finder.refresh();
+ views = finder.getAllViews();
+ }
+ if (views != null && !views.isEmpty()) {
+ List<ElementDescriptor> descriptors = Lists.newArrayListWithExpectedSize(views.size());
+ CustomViewDescriptorService customViews = CustomViewDescriptorService.getInstance();
+ for (String fqcn : views) {
+ ViewElementDescriptor descriptor = customViews.getDescriptor(project, fqcn);
+ if (descriptor != null) {
+ descriptors.add(descriptor);
+ }
+ }
+
+ return descriptors;
+ }
+
+ return null;
+ }
+
+ @Override
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ String parentTagName, String attributeName, Node node, String wordPrefix,
+ boolean skipEndTag, int replaceLength) {
+ super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
+ wordPrefix, skipEndTag, replaceLength);
+
+ boolean projectOnly = false;
+ List<String> superClasses = null;
+ if (VIEW_FRAGMENT.equals(parentTagName) && (attributeName.endsWith(ATTR_NAME)
+ || attributeName.equals(ATTR_CLASS))) {
+ // Insert fragment class matches
+ superClasses = Arrays.asList(CLASS_V4_FRAGMENT, CLASS_FRAGMENT);
+ } else if (VIEW_TAG.equals(parentTagName) && attributeName.endsWith(ATTR_CLASS)) {
+ // Insert custom view matches
+ superClasses = Collections.singletonList(CLASS_VIEW);
+ projectOnly = true;
+ } else if (attributeName.endsWith(ATTR_CONTEXT)) {
+ // Insert activity matches
+ superClasses = Collections.singletonList(CLASS_ACTIVITY);
+ }
+
+ if (superClasses != null) {
+ IProject project = mEditor.getProject();
+ if (project == null) {
+ return false;
+ }
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ IType type = javaProject.findType(superClasses.get(0));
+ Set<IType> elements = new HashSet<IType>();
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
+ IType[] allSubtypes = hierarchy.getAllSubtypes(type);
+ for (IType subType : allSubtypes) {
+ if (!projectOnly || subType.getResource() != null) {
+ elements.add(subType);
+ }
+ }
+ }
+ assert superClasses.size() <= 2; // If more, need to do additional work below
+ if (superClasses.size() == 2) {
+ type = javaProject.findType(superClasses.get(1));
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newTypeHierarchy(
+ new NullProgressMonitor());
+ IType[] allSubtypes = hierarchy.getAllSubtypes(type);
+ for (IType subType : allSubtypes) {
+ if (!projectOnly || subType.getResource() != null) {
+ elements.add(subType);
+ }
+ }
+ }
+ }
+
+ List<IType> sorted = new ArrayList<IType>(elements);
+ Collections.sort(sorted, new Comparator<IType>() {
+ @Override
+ public int compare(IType type1, IType type2) {
+ String fqcn1 = type1.getFullyQualifiedName();
+ String fqcn2 = type2.getFullyQualifiedName();
+ int category1 = fqcn1.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1;
+ int category2 = fqcn2.startsWith(ANDROID_PKG_PREFIX) ? 1 : -1;
+ if (category1 != category2) {
+ return category1 - category2;
+ }
+ return fqcn1.compareTo(fqcn2);
+ }
+ });
+ addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix,
+ (char) 0, false /* isAttribute */, false /* isNew */,
+ false /* skipEndTag */, replaceLength);
+ return true;
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return false;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index fc81ac4..1015d7d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -44,6 +44,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElement
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceFolderType;
@@ -260,6 +261,9 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
if (input instanceof FileEditorInput) {
FileEditorInput fileInput = (FileEditorInput)input;
editedFile = fileInput.getFile();
+ if (!editedFile.isAccessible()) {
+ return;
+ }
} else {
AdtPlugin.log(IStatus.ERROR,
"Input is not of type FileEditorInput: %1$s", //$NON-NLS-1$
@@ -753,7 +757,8 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
@Override
public String delegateGetPartName() {
IEditorInput editorInput = getEditor().getEditorInput();
- if (editorInput instanceof IFileEditorInput) {
+ if (!AdtPrefs.getPrefs().isSharedLayoutEditor()
+ && editorInput instanceof IFileEditorInput) {
IFileEditorInput fileInput = (IFileEditorInput) editorInput;
IFile file = fileInput.getFile();
IContainer parent = file.getParent();
@@ -910,6 +915,7 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
if (mGraphicalEditor != null) {
mGraphicalEditor.onTargetChange();
mGraphicalEditor.reloadPalette();
+ mGraphicalEditor.getCanvasControl().syncPreviewMode();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
index d9e798e..4e4429d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutReloadMonitor.java
@@ -175,7 +175,7 @@ public final class LayoutReloadMonitor {
*/
@Override
public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
// This listener only cares about .class files and AndroidManifest.xml files
if (!(SdkConstants.EXT_CLASS.equals(extension)
|| SdkConstants.EXT_XML.equals(extension)
@@ -186,15 +186,7 @@ public final class LayoutReloadMonitor {
// get the file's project
IProject project = file.getProject();
- boolean hasAndroidNature = false;
- try {
- hasAndroidNature = project.hasNature(AdtConstants.NATURE_DEFAULT);
- } catch (CoreException e) {
- // do nothing if the nature cannot be queried.
- return;
- }
-
- if (hasAndroidNature) {
+ if (isAndroidProject) {
// project is an Android project, it's the one being affected
// directly by its own file change.
processFileChanged(file, project, extension);
@@ -204,16 +196,14 @@ public final class LayoutReloadMonitor {
for (IProject p : referencingProjects) {
try {
- hasAndroidNature = p.hasNature(AdtConstants.NATURE_DEFAULT);
+ boolean hasAndroidNature = p.hasNature(AdtConstants.NATURE_DEFAULT);
+ if (hasAndroidNature) {
+ // the changed project is a dependency on an Android project,
+ // update the main project.
+ processFileChanged(file, p, extension);
+ }
} catch (CoreException e) {
// do nothing if the nature cannot be queried.
- continue;
- }
-
- if (hasAndroidNature) {
- // the changed project is a dependency on an Android project,
- // update the main project.
- processFileChanged(file, p, extension);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
index 98f5317..74c033c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/ProjectCallback.java
@@ -18,11 +18,13 @@ package com.android.ide.eclipse.adt.internal.editors.layout;
import static com.android.SdkConstants.ANDROID_PKG_PREFIX;
import static com.android.SdkConstants.CALENDAR_VIEW;
+import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
import static com.android.SdkConstants.FQCN_GRID_VIEW;
import static com.android.SdkConstants.FQCN_SPINNER;
import static com.android.SdkConstants.GRID_VIEW;
import static com.android.SdkConstants.LIST_VIEW;
+import static com.android.SdkConstants.SPINNER;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;
@@ -49,18 +51,22 @@ import com.android.ide.eclipse.adt.internal.resources.manager.ProjectClassLoader
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.resources.ResourceType;
import com.android.util.Pair;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
import org.eclipse.core.resources.IProject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
@@ -140,6 +146,11 @@ public final class ProjectCallback extends LegacyCallback {
throws ClassNotFoundException, Exception {
mUsed = true;
+ if (className == null) {
+ // Just make a plain <View> if you specify <view> without a class= attribute.
+ className = CLASS_VIEW;
+ }
+
// look for a cached version
Class<?> clazz = mLoadedClasses.get(className);
if (clazz != null) {
@@ -454,12 +465,15 @@ public final class ProjectCallback extends LegacyCallback {
ContextPullParser parser = new ContextPullParser(this, xml);
try {
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$
+ String xmlText = Files.toString(xml, Charsets.UTF_8);
+ parser.setInput(new StringReader(xmlText));
return parser;
} catch (XmlPullParserException e) {
AdtPlugin.log(e, null);
} catch (FileNotFoundException e) {
// Shouldn't happen since we check isFile() above
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
}
}
@@ -560,8 +574,8 @@ public final class ProjectCallback extends LegacyCallback {
}
@Override
- public AdapterBinding getAdapterBinding(final ResourceReference adapterView, final Object adapterCookie,
- final Object viewObject) {
+ public AdapterBinding getAdapterBinding(final ResourceReference adapterView,
+ final Object adapterCookie, final Object viewObject) {
// Look for user-recorded preference for layout to be used for previews
if (adapterCookie instanceof UiViewElementNode) {
UiViewElementNode uiNode = (UiViewElementNode) adapterCookie;
@@ -569,6 +583,13 @@ public final class ProjectCallback extends LegacyCallback {
if (binding != null) {
return binding;
}
+ } else if (adapterCookie instanceof Map<?,?>) {
+ @SuppressWarnings("unchecked")
+ Map<String, String> map = (Map<String, String>) adapterCookie;
+ AdapterBinding binding = LayoutMetadata.getNodeBinding(viewObject, map);
+ if (binding != null) {
+ return binding;
+ }
}
if (viewObject == null) {
@@ -598,7 +619,7 @@ public final class ProjectCallback extends LegacyCallback {
if (listFqcn.endsWith(EXPANDABLE_LIST_VIEW)) {
binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_EXPANDABLE_LIST_ITEM,
true /* isFramework */, 1));
- } else if (listFqcn.equals(FQCN_SPINNER)) {
+ } else if (listFqcn.equals(SPINNER)) {
binding.addItem(new DataBindingItem(LayoutMetadata.DEFAULT_SPINNER_ITEM,
true /* isFramework */, 1));
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
index 9553bc8..e8e0d79 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/UiElementPullParser.java
@@ -36,7 +36,7 @@ import static com.android.SdkConstants.VIEW_INCLUDE;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.ViewInfo;
-import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.common.resources.ValueResourceParser;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.FragmentMenu;
@@ -79,7 +79,6 @@ public class UiElementPullParser extends BasePullParser {
private boolean mIncreaseExistingPadding = false;
private LayoutDescriptors mDescriptors;
private final Density mDensity;
- private final float mXdpi;
/**
* Number of pixels to pad views with in exploded-rendering mode.
@@ -114,18 +113,16 @@ public class UiElementPullParser extends BasePullParser {
* nodes are not individually exploded (but they may all be exploded with the
* explodeRendering parameter.
* @param density the density factor for the screen.
- * @param xdpi the screen actual dpi in X
* @param project Project containing this layout.
*/
public UiElementPullParser(UiElementNode top, boolean explodeRendering,
Set<UiElementNode> explodeNodes,
- Density density, float xdpi, IProject project) {
+ Density density, IProject project) {
super();
mRoot = top;
mExplodedRendering = explodeRendering;
mExplodeNodes = explodeNodes;
mDensity = density;
- mXdpi = xdpi;
if (mExplodedRendering) {
// get the layout descriptor
IAndroidTarget target = Sdk.getCurrent().getTarget(project);
@@ -401,10 +398,8 @@ public class UiElementPullParser extends BasePullParser {
return VALUE_FILL_PARENT;
}
- // Handle unicode escapes
- if (value.indexOf('\\') != -1) {
- value = AdtUtils.replaceUnicodeEscapes(value);
- }
+ // Handle unicode escapes etc
+ value = ValueResourceParser.unescapeResourceString(value, false, false);
return value;
}
@@ -631,13 +626,13 @@ public class UiElementPullParser extends BasePullParser {
f *= (float)mDensity.getDpiValue() / Density.DEFAULT_DENSITY;
break;
case COMPLEX_UNIT_PT:
- f *= mXdpi * (1.0f / 72);
+ f *= mDensity.getDpiValue() * (1.0f / 72);
break;
case COMPLEX_UNIT_IN:
- f *= mXdpi;
+ f *= mDensity.getDpiValue();
break;
case COMPLEX_UNIT_MM:
- f *= mXdpi * (1.0f / 25.4f);
+ f *= mDensity.getDpiValue() * (1.0f / 25.4f);
break;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
index 1f85a32..36cd0fb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ActivityMenuListener.java
@@ -88,7 +88,7 @@ class ActivityMenuListener extends SelectionAdapter {
if (current != null) {
MenuItem item = new MenuItem(menu, SWT.PUSH);
- String label = ConfigurationChooser.getActivityLabel(current, true);;
+ String label = ConfigurationChooser.getActivityLabel(current, true);
item.setText( String.format("Open %1$s...", label));
Image image = sharedImages.getImage(ISharedImages.IMG_OBJS_CUNIT);
item.setImage(image);
@@ -154,7 +154,7 @@ class ActivityMenuListener extends SelectionAdapter {
}
item.addSelectionListener(new ActivityMenuListener(chooser,
- ACTION_OPEN_ACTIVITY, fqcn));
+ ACTION_SELECT_ACTIVITY, fqcn));
}
return current;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
index 2106f8d..8ca0c26 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/Configuration.java
@@ -17,44 +17,84 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.NightModeQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
-import com.android.ide.common.resources.configuration.ScreenDimensionQualifier;
-import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.resources.configuration.UiModeQualifier;
import com.android.ide.common.resources.configuration.VersionQualifier;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.Density;
import com.android.resources.NightMode;
-import com.android.resources.ScreenOrientation;
+import com.android.resources.ScreenSize;
import com.android.resources.UiMode;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.State;
import com.android.utils.Pair;
+import com.google.common.base.Objects;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
import java.util.List;
+import java.util.Map;
/**
* A {@linkplain Configuration} is a selection of device, orientation, theme,
* etc for use when rendering a layout.
*/
public class Configuration {
+ /** The {@link FolderConfiguration} in change flags or override flags */
+ public static final int CFG_FOLDER = 1 << 0;
+ /** The {@link Device} in change flags or override flags */
+ public static final int CFG_DEVICE = 1 << 1;
+ /** The {@link State} in change flags or override flags */
+ public static final int CFG_DEVICE_STATE = 1 << 2;
+ /** The theme in change flags or override flags */
+ public static final int CFG_THEME = 1 << 3;
+ /** The locale in change flags or override flags */
+ public static final int CFG_LOCALE = 1 << 4;
+ /** The rendering {@link IAndroidTarget} in change flags or override flags */
+ public static final int CFG_TARGET = 1 << 5;
+ /** The {@link NightMode} in change flags or override flags */
+ public static final int CFG_NIGHT_MODE = 1 << 6;
+ /** The {@link UiMode} in change flags or override flags */
+ public static final int CFG_UI_MODE = 1 << 7;
+ /** The {@link UiMode} in change flags or override flags */
+ public static final int CFG_ACTIVITY = 1 << 8;
+
+ /** References all attributes */
+ public static final int MASK_ALL = 0xFFFF;
+
+ /** Attributes which affect which best-layout-file selection */
+ public static final int MASK_FILE_ATTRS =
+ CFG_DEVICE|CFG_DEVICE_STATE|CFG_LOCALE|CFG_TARGET|CFG_NIGHT_MODE|CFG_UI_MODE;
+
+ /** Attributes which affect rendering appearance */
+ public static final int MASK_RENDERING = MASK_FILE_ATTRS|CFG_THEME;
+
/**
* Setting name for project-wide setting controlling rendering target and locale which
* is shared for all files
@@ -68,7 +108,7 @@ public class Configuration {
private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
@NonNull
- protected final ConfigurationChooser mConfigChooser;
+ protected ConfigurationChooser mConfigChooser;
/** The {@link FolderConfiguration} representing the state of the UI controls */
@NonNull
@@ -113,6 +153,9 @@ public class Configuration {
@NonNull
private NightMode mNightMode = NightMode.NOTNIGHT;
+ /** The display name */
+ private String mDisplayName;
+
/**
* Creates a new {@linkplain Configuration}
*
@@ -123,6 +166,30 @@ public class Configuration {
}
/**
+ * Sets the associated configuration chooser
+ *
+ * @param chooser the chooser
+ */
+ void setChooser(@NonNull ConfigurationChooser chooser) {
+ // TODO: We should get rid of the binding between configurations
+ // and configuration choosers. This is currently needed because
+ // the choosers contain vital data such as the set of available
+ // rendering targets, the set of available locales etc, which
+ // also doesn't belong inside the configuration but is needed by it.
+ mConfigChooser = chooser;
+ }
+
+ /**
+ * Gets the associated configuration chooser
+ *
+ * @return the chooser
+ */
+ @NonNull
+ ConfigurationChooser getChooser() {
+ return mConfigChooser;
+ }
+
+ /**
* Creates a new {@linkplain Configuration}
*
* @param chooser the associated chooser
@@ -134,6 +201,60 @@ public class Configuration {
}
/**
+ * Creates a configuration suitable for the given file
+ *
+ * @param base the base configuration to base the file configuration off of
+ * @param file the file to look up a configuration for
+ * @return a suitable configuration
+ */
+ @NonNull
+ public static Configuration create(
+ @NonNull Configuration base,
+ @NonNull IFile file) {
+ Configuration configuration = copy(base);
+ ConfigurationChooser chooser = base.getChooser();
+ ProjectResources resources = chooser.getResources();
+ ConfigurationMatcher matcher = new ConfigurationMatcher(chooser, configuration, file,
+ resources, false);
+
+ ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file);
+ configuration.mEditedConfig = new FolderConfiguration();
+ configuration.mEditedConfig.set(resFolder.getConfiguration());
+
+ matcher.adaptConfigSelection(true /*needBestMatch*/);
+ configuration.syncFolderConfig();
+
+ return configuration;
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} that is a copy from a different configuration
+ *
+ * @param original the original to copy from
+ * @return a new configuration copied from the original
+ */
+ @NonNull
+ public static Configuration copy(@NonNull Configuration original) {
+ Configuration copy = create(original.mConfigChooser);
+ copy.mFullConfig.set(original.mFullConfig);
+ if (original.mEditedConfig != null) {
+ copy.mEditedConfig = new FolderConfiguration();
+ copy.mEditedConfig.set(original.mEditedConfig);
+ }
+ copy.mTarget = original.getTarget();
+ copy.mTheme = original.getTheme();
+ copy.mDevice = original.getDevice();
+ copy.mState = original.getDeviceState();
+ copy.mActivity = original.getActivity();
+ copy.mLocale = original.getLocale();
+ copy.mUiMode = original.getUiMode();
+ copy.mNightMode = original.getNightMode();
+ copy.mDisplayName = original.getDisplayName();
+
+ return copy;
+ }
+
+ /**
* Returns the associated activity
*
* @return the activity
@@ -214,6 +335,16 @@ public class Configuration {
}
/**
+ * Returns the display name to show for this configuration
+ *
+ * @return the display name, or null if none has been assigned
+ */
+ @Nullable
+ public String getDisplayName() {
+ return mDisplayName;
+ }
+
+ /**
* Returns whether the configuration's theme is a project theme.
* <p/>
* The returned value is meaningless if {@link #getTheme()} returns
@@ -359,6 +490,15 @@ public class Configuration {
}
/**
+ * Sets the display name to be shown for this configuration.
+ *
+ * @param displayName the new display name
+ */
+ public void setDisplayName(@Nullable String displayName) {
+ mDisplayName = displayName;
+ }
+
+ /**
* Sets the night mode
*
* @param night the night mode
@@ -397,6 +537,7 @@ public class Configuration {
*/
public void setTheme(String theme) {
mTheme = theme;
+ checkThemePrefix();
}
/**
@@ -507,6 +648,67 @@ public class Configuration {
return sb.toString();
}
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String computePreferredTheme() {
+ IProject project = mConfigChooser.getProject();
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current state
+ ScreenSize screenSize = null;
+ Device device = getDevice();
+ if (device != null) {
+ List<State> states = device.getAllStates();
+ for (State state : states) {
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
+ if (folderConfig != null) {
+ ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(getTarget(), screenSize);
+
+ String preferred = defaultTheme;
+ if (getTheme() == null) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ String activity = getActivity();
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ setTheme(preferred);
+ }
+
+ return preferred;
+ }
+
+ private void checkThemePrefix() {
+ if (mTheme != null && !mTheme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (mTheme.isEmpty()) {
+ computePreferredTheme();
+ return;
+ }
+ ResourceRepository frameworkRes = mConfigChooser.getClient().getFrameworkResources();
+ if (frameworkRes != null
+ && frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + mTheme)) {
+ mTheme = ANDROID_STYLE_RESOURCE_PREFIX + mTheme;
+ } else {
+ mTheme = STYLE_RESOURCE_PREFIX + mTheme;
+ }
+ }
+ }
+
/**
* Initializes a string previously created with
* {@link #toPersistentString()}
@@ -555,6 +757,8 @@ public class Configuration {
} else if (mTheme.startsWith(MARKER_PROJECT)) {
mTheme = STYLE_RESOURCE_PREFIX
+ mTheme.substring(MARKER_PROJECT.length());
+ } else {
+ checkThemePrefix();
}
mUiMode = UiMode.getEnum(values[4]);
@@ -604,7 +808,7 @@ public class Configuration {
@Nullable
static Pair<Locale, IAndroidTarget> loadRenderState(ConfigurationChooser chooser) {
IProject project = chooser.getProject();
- if (!project.isAccessible()) {
+ if (project == null || !project.isAccessible()) {
return null;
}
@@ -629,24 +833,31 @@ public class Configuration {
}
locale = Locale.create(language, region);
- target = stringToTarget(chooser, values[1]);
-
- // See if we should "correct" the rendering target to a better version.
- // If you're using a pre-release version of the render target, and a
- // final release is available and installed, we should switch to that
- // one instead.
- if (target != null) {
- AndroidVersion version = target.getVersion();
- List<IAndroidTarget> targetList = chooser.getTargetList();
- if (version.getCodename() != null && targetList != null) {
- int targetApiLevel = version.getApiLevel() + 1;
- for (IAndroidTarget t : targetList) {
- if (t.getVersion().getApiLevel() == targetApiLevel
- && t.isPlatform()) {
- target = t;
- break;
+ if (AdtPrefs.getPrefs().isAutoPickRenderTarget()) {
+ target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
+ } else {
+ String targetString = values[1];
+ target = stringToTarget(chooser, targetString);
+ // See if we should "correct" the rendering target to a
+ // better version. If you're using a pre-release version
+ // of the render target, and a final release is
+ // available and installed, we should switch to that
+ // one instead.
+ if (target != null) {
+ AndroidVersion version = target.getVersion();
+ List<IAndroidTarget> targetList = chooser.getTargetList();
+ if (version.getCodename() != null && targetList != null) {
+ int targetApiLevel = version.getApiLevel() + 1;
+ for (IAndroidTarget t : targetList) {
+ if (t.getVersion().getApiLevel() == targetApiLevel
+ && t.isPlatform()) {
+ target = t;
+ break;
+ }
}
}
+ } else {
+ target = ConfigurationMatcher.findDefaultRenderTarget(chooser);
}
}
}
@@ -654,7 +865,7 @@ public class Configuration {
return Pair.of(locale, target);
}
- return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(project));
+ return Pair.of(Locale.ANY, ConfigurationMatcher.findDefaultRenderTarget(chooser));
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
@@ -668,9 +879,12 @@ public class Configuration {
*/
void saveRenderState() {
IProject project = mConfigChooser.getProject();
+ if (project == null) {
+ return;
+ }
try {
// Generate a persistent string from locale+target
- StringBuilder sb = new StringBuilder();
+ StringBuilder sb = new StringBuilder(32);
Locale locale = getLocale();
if (locale != null) {
// locale[0]/[1] can be null sometimes when starting Eclipse
@@ -700,7 +914,7 @@ public class Configuration {
* @return an id for the given target; never null
*/
@NonNull
- private static String targetToString(@NonNull IAndroidTarget target) {
+ public static String targetToString(@NonNull IAndroidTarget target) {
return target.getFullName().replace(SEP, ""); //$NON-NLS-1$
}
@@ -715,7 +929,7 @@ public class Configuration {
* @return an {@link IAndroidTarget} that matches the given id, or null
*/
@Nullable
- private static IAndroidTarget stringToTarget(
+ public static IAndroidTarget stringToTarget(
@NonNull ConfigurationChooser chooser,
@NonNull String id) {
List<IAndroidTarget> targetList = chooser.getTargetList();
@@ -731,6 +945,30 @@ public class Configuration {
}
/**
+ * Returns an {@link IAndroidTarget} that corresponds to the given id that was
+ * originally returned by {@link #targetToString}. May be null, if the platform is no
+ * longer available, or if the platform list has not yet been initialized.
+ *
+ * @param id the id that corresponds to the desired platform
+ * @return an {@link IAndroidTarget} that matches the given id, or null
+ */
+ @Nullable
+ public static IAndroidTarget stringToTarget(
+ @NonNull String id) {
+ Sdk currentSdk = Sdk.getCurrent();
+ if (currentSdk != null) {
+ IAndroidTarget[] targets = currentSdk.getTargets();
+ for (IAndroidTarget target : targets) {
+ if (id.equals(targetToString(target))) {
+ return target;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Returns the {@link State} by the given name for the given {@link Device}
*
* @param device the device
@@ -773,96 +1011,6 @@ public class Configuration {
}
/**
- * Returns the current device xdpi.
- *
- * @return the x dpi as a float
- */
- public float getXDpi() {
- Device device = getDevice();
- if (device != null) {
- State currState = getDeviceState();
- if (currState == null) {
- currState = device.getDefaultState();
- }
- float dpi = (float) currState.getHardware().getScreen().getXdpi();
- if (!Float.isNaN(dpi)) {
- return dpi;
- }
- }
-
- // get the pixel density as the density.
- return getDensity().getDpiValue();
- }
-
- /**
- * Returns the current device ydpi.
- *
- * @return the y dpi as a float
- */
- public float getYDpi() {
- Device device = getDevice();
- if (device != null) {
- State currState = getDeviceState();
- if (currState == null) {
- currState = device.getDefaultState();
- }
- float dpi = (float) currState.getHardware().getScreen().getYdpi();
- if (!Float.isNaN(dpi)) {
- return dpi;
- }
- }
-
- // get the pixel density as the density.
- return getDensity().getDpiValue();
- }
-
- /**
- * Returns the bounds of the screen
- *
- * @return the screen bounds
- */
- public Rect getScreenBounds() {
- return getScreenBounds(mFullConfig);
- }
-
- /**
- * Gets the orientation from the given configuration
- *
- * @param config the configuration to look up
- * @return the bounds
- */
- @NonNull
- public static Rect getScreenBounds(FolderConfiguration config) {
- // get the orientation from the given device config
- ScreenOrientationQualifier qual = config.getScreenOrientationQualifier();
- ScreenOrientation orientation = ScreenOrientation.PORTRAIT;
- if (qual != null) {
- orientation = qual.getValue();
- }
-
- // get the device screen dimension
- ScreenDimensionQualifier qual2 = config.getScreenDimensionQualifier();
- int s1, s2;
- if (qual2 != null) {
- s1 = qual2.getValue1();
- s2 = qual2.getValue2();
- } else {
- s1 = 480;
- s2 = 320;
- }
-
- switch (orientation) {
- default:
- case PORTRAIT:
- return new Rect(0, 0, s2, s1);
- case LANDSCAPE:
- return new Rect(0, 0, s1, s2);
- case SQUARE:
- return new Rect(0, 0, s1, s1);
- }
- }
-
- /**
* Get the next cyclical state after the given state
*
* @param from the state to start with
@@ -884,8 +1032,27 @@ public class Configuration {
return null;
}
+ /**
+ * Returns true if this configuration supports the given rendering
+ * capability
+ *
+ * @param capability the capability to check
+ * @return true if the capability is supported
+ */
+ public boolean supports(Capability capability) {
+ IAndroidTarget target = getTarget();
+ if (target != null) {
+ return RenderService.supports(target, capability);
+ }
+
+ return false;
+ }
+
@Override
public String toString() {
- return toPersistentString();
+ return Objects.toStringHelper(this.getClass())
+ .add("display", getDisplayName()) //$NON-NLS-1$
+ .add("persistent", toPersistentString()) //$NON-NLS-1$
+ .toString();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
index b512bcc..d4cc6df 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationChooser.java
@@ -19,24 +19,25 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.ATTR_CONTEXT;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.RES_QUALIFIER_SEP;
import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.TOOLS_URI;
import static com.android.ide.eclipse.adt.AdtUtils.isUiThread;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_ALL;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_DEVICE;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_DEVICE_CONFIG;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_FOLDER;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_LOCALE;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_RENDER_TARGET;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient.CHANGED_THEME;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_LOCALE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_THEME;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_ALL;
+import static com.google.common.base.Objects.equal;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.DeviceConfigHelper;
@@ -44,12 +45,17 @@ import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LanguageQualifier;
import com.android.ide.common.resources.configuration.RegionQualifier;
import com.android.ide.common.resources.configuration.ResourceQualifier;
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
import com.android.ide.common.sdk.LoadStatus;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlDelegate;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
@@ -58,12 +64,11 @@ import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
-import com.android.resources.ScreenSize;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
-import com.android.sdklib.devices.DeviceManager.DevicesChangeListener;
+import com.android.sdklib.devices.DeviceManager.DevicesChangedListener;
import com.android.sdklib.devices.State;
import com.android.utils.Pair;
import com.google.common.base.Objects;
@@ -72,7 +77,6 @@ import com.google.common.base.Strings;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
-import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
@@ -87,10 +91,12 @@ import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.IEditorPart;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
@@ -102,14 +108,7 @@ import java.util.SortedSet;
* {@link Configuration} by configuring various constraints.
*/
public class ConfigurationChooser extends Composite
- implements DevicesChangeListener, DisposeListener {
- /**
- * Settings name for file-specific configuration preferences, such as which theme or
- * device to render the current layout with
- */
- public final static QualifiedName NAME_CONFIG_STATE =
- new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
-
+ implements DevicesChangedListener, DisposeListener {
private static final String ICON_SQUARE = "square"; //$NON-NLS-1$
private static final String ICON_LANDSCAPE = "landscape"; //$NON-NLS-1$
private static final String ICON_PORTRAIT = "portrait"; //$NON-NLS-1$
@@ -205,8 +204,8 @@ public class ConfigurationChooser extends Composite
ToolBar toolBar = new ToolBar(this, SWT.WRAP | SWT.FLAT | SWT.RIGHT | SWT.HORIZONTAL);
toolBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
- mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN | SWT.BOLD);
- mConfigCombo.setImage(null);
+ mConfigCombo = new ToolItem(toolBar, SWT.DROP_DOWN );
+ mConfigCombo.setImage(icons.getIcon("android_file")); //$NON-NLS-1$
mConfigCombo.setToolTipText("Configuration to render this layout with in Eclipse");
@SuppressWarnings("unused")
@@ -296,21 +295,47 @@ public class ConfigurationChooser extends Composite
mOrientationCombo.addSelectionListener(listener);
addDisposeListener(this);
+
+ initDevices();
+ initTargets();
}
- IFile getEditedFile() {
+ /**
+ * Returns the edited file
+ *
+ * @return the file
+ */
+ @Nullable
+ public IFile getEditedFile() {
return mEditedFile;
}
- IProject getProject() {
- return mEditedFile.getProject();
+ /**
+ * Returns the project of the edited file
+ *
+ * @return the project
+ */
+ @Nullable
+ public IProject getProject() {
+ if (mEditedFile != null) {
+ return mEditedFile.getProject();
+ } else {
+ return null;
+ }
}
ConfigurationClient getClient() {
return mClient;
}
- ProjectResources getResources() {
+ /**
+ * Returns the project resources for the project being configured by this
+ * chooser
+ *
+ * @return the project resources
+ */
+ @Nullable
+ public ProjectResources getResources() {
return mResources;
}
@@ -328,7 +353,7 @@ public class ConfigurationChooser extends Composite
*
* @return the project target
*/
- IAndroidTarget getProjectTarget() {
+ public IAndroidTarget getProjectTarget() {
return mProjectTarget;
}
@@ -462,6 +487,7 @@ public class ConfigurationChooser extends Composite
*/
public void setFile(IFile file) {
mEditedFile = file;
+ ensureInitialized();
}
/**
@@ -478,7 +504,7 @@ public class ConfigurationChooser extends Composite
return;
}
- mEditedFile = file;
+ setFile(file);
IProject project = mEditedFile.getProject();
mResources = ResourceManager.getInstance().getProjectResources(project);
@@ -491,6 +517,7 @@ public class ConfigurationChooser extends Composite
try {
// only attempt to do anything if the SDK and targets are loaded.
LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
+
if (sdkStatus == LoadStatus.LOADED) {
setVisible(true);
@@ -509,6 +536,8 @@ public class ConfigurationChooser extends Composite
selectConfiguration(mConfiguration.getEditedConfig());
updateActivity();
}
+ } else if (sdkStatus == LoadStatus.FAILED) {
+ setVisible(true);
}
} finally {
mDisableUpdates--;
@@ -522,7 +551,7 @@ public class ConfigurationChooser extends Composite
* @see #replaceFile(IFile)
*/
public void changeFileOnNewConfig(IFile file) {
- mEditedFile = file;
+ setFile(file);
IProject project = mEditedFile.getProject();
mResources = ResourceManager.getInstance().getProjectResources(project);
@@ -561,8 +590,13 @@ public class ConfigurationChooser extends Composite
if (resFolder != null) {
mConfiguration.setEditedConfig(resFolder.getConfiguration());
} else {
- mConfiguration.setEditedConfig(FolderConfiguration.getConfig(
- parent.getName().split(RES_QUALIFIER_SEP)));
+ FolderConfiguration config = FolderConfiguration.getConfig(
+ parent.getName().split(RES_QUALIFIER_SEP));
+ if (config != null) {
+ mConfiguration.setEditedConfig(config);
+ } else {
+ mConfiguration.setEditedConfig(new FolderConfiguration());
+ }
}
onXmlModelLoaded();
@@ -577,15 +611,12 @@ public class ConfigurationChooser extends Composite
*/
public void setConfiguration(@NonNull Configuration configuration) {
if (mClient != null) {
- mClient.aboutToChange(CHANGED_ALL);
+ mClient.aboutToChange(MASK_ALL);
}
Configuration oldConfiguration = mConfiguration;
mConfiguration = configuration;
-
- if (mClient != null) {
- mClient.changed(CHANGED_ALL);
- }
+ mConfiguration.setChooser(this);
selectTheme(configuration.getTheme());
selectLocale(configuration.getLocale());
@@ -596,7 +627,13 @@ public class ConfigurationChooser extends Composite
// This may be a second refresh after triggered by theme above
if (mClient != null) {
- boolean accepted = mClient.changed(CHANGED_ALL);
+ LayoutCanvas canvas = mClient.getCanvas();
+ if (canvas != null) {
+ assert mConfiguration != oldConfiguration;
+ canvas.getPreviewManager().updateChooserConfig(oldConfiguration, mConfiguration);
+ }
+
+ boolean accepted = mClient.changed(MASK_ALL);
if (!accepted) {
configuration = oldConfiguration;
selectTheme(configuration.getTheme());
@@ -605,7 +642,22 @@ public class ConfigurationChooser extends Composite
selectDeviceState(configuration.getDeviceState());
selectTarget(configuration.getTarget());
selectActivity(configuration.getActivity());
+ if (canvas != null && mConfiguration != oldConfiguration) {
+ canvas.getPreviewManager().updateChooserConfig(mConfiguration,
+ oldConfiguration);
+ }
return;
+ } else {
+ int changed = 0;
+ if (!equal(oldConfiguration.getTheme(), mConfiguration.getTheme())) {
+ changed |= CFG_THEME;
+ }
+ if (!equal(oldConfiguration.getDevice(), mConfiguration.getDevice())) {
+ changed |= CFG_DEVICE | CFG_DEVICE_STATE;
+ }
+ if (changed != 0) {
+ syncToVariations(changed, mEditedFile, mConfiguration, false, true);
+ }
}
}
@@ -627,10 +679,9 @@ public class ConfigurationChooser extends Composite
mDisableUpdates++; // we do not want to trigger onXXXChange when setting
// new values in the widgets.
try {
- // this is going to be followed by a call to onTargetLoaded.
- // So we can only care about the layout devices in this case.
- initDevices();
- initTargets();
+ updateDevices();
+ updateTargets();
+ ensureInitialized();
} finally {
mDisableUpdates--;
}
@@ -668,8 +719,9 @@ public class ConfigurationChooser extends Composite
try {
// init the devices if needed (new SDK or first time going through here)
if (mSdkChanged) {
- initDevices();
- initTargets();
+ updateDevices();
+ updateTargets();
+ ensureInitialized();
mSdkChanged = false;
}
@@ -683,7 +735,8 @@ public class ConfigurationChooser extends Composite
LoadStatus targetStatus = LoadStatus.FAILED;
if (mProjectTarget != null) {
targetStatus = Sdk.getCurrent().checkAndLoadTargetData(mProjectTarget, null);
- initTargets();
+ updateTargets();
+ ensureInitialized();
}
if (targetStatus == LoadStatus.LOADED) {
@@ -697,24 +750,22 @@ public class ConfigurationChooser extends Composite
if (resFolder != null) {
mConfiguration.setEditedConfig(resFolder.getConfiguration());
} else {
- mConfiguration.setEditedConfig(FolderConfiguration.getConfig(
- parent.getName().split(RES_QUALIFIER_SEP)));
+ FolderConfiguration config = FolderConfiguration.getConfig(
+ parent.getName().split(RES_QUALIFIER_SEP));
+ if (config != null) {
+ mConfiguration.setEditedConfig(config);
+ } else {
+ mConfiguration.setEditedConfig(new FolderConfiguration());
+ }
}
}
targetData = Sdk.getCurrent().getTargetData(mProjectTarget);
// get the file stored state
- boolean loadedConfigData = false;
- String data = AdtPlugin.getFileProperty(mEditedFile, NAME_CONFIG_STATE);
- if (mInitialState != null) {
- data = mInitialState;
- mInitialState = null;
- }
-
- if (data != null) {
- loadedConfigData = mConfiguration.initialize(data);
- }
+ ensureInitialized();
+ boolean loadedConfigData = mConfiguration.getDevice() != null &&
+ mConfiguration.getDeviceState() != null;
// Load locale list. This must be run after we initialize the
// configuration above, since it attempts to sync the UI with
@@ -741,11 +792,11 @@ public class ConfigurationChooser extends Composite
matcher.findAndSetCompatibleConfig(false);
// Default to modern layout lib
- IProject p = mEditedFile.getProject();
- IAndroidTarget target = ConfigurationMatcher.findDefaultRenderTarget(p);
+ IAndroidTarget target = ConfigurationMatcher.findDefaultRenderTarget(this);
if (target != null) {
targetData = Sdk.getCurrent().getTargetData(target);
selectTarget(target);
+ mConfiguration.setTarget(target, true);
}
}
@@ -768,6 +819,8 @@ public class ConfigurationChooser extends Composite
// compute the final current config
mConfiguration.syncFolderConfig();
+ } else if (targetStatus == LoadStatus.FAILED) {
+ setVisible(true);
}
} finally {
mDisableUpdates--;
@@ -778,6 +831,19 @@ public class ConfigurationChooser extends Composite
}
/**
+ * This is a temporary workaround for a infrequently happening bug; apparently
+ * there are cases where the configuration chooser isn't shown
+ */
+ public void ensureVisible() {
+ if (!isVisible()) {
+ LoadStatus sdkStatus = AdtPlugin.getDefault().getSdkLoadStatus();
+ if (sdkStatus == LoadStatus.LOADED) {
+ onXmlModelLoaded();
+ }
+ }
+ }
+
+ /**
* An alternate layout for this layout has been created. This means that the
* current layout may no longer be a best fit. However, since we support multiple
* layouts being open at the same time, we need to adjust the current configuration
@@ -790,7 +856,7 @@ public class ConfigurationChooser extends Composite
matcher.adaptConfigSelection(true /*needBestMatch*/);
mConfiguration.syncFolderConfig();
if (mClient != null) {
- mClient.changed(CHANGED_ALL);
+ mClient.changed(MASK_ALL);
}
}
}
@@ -801,69 +867,93 @@ public class ConfigurationChooser extends Composite
private void initDevices() {
final Sdk sdk = Sdk.getCurrent();
if (sdk != null) {
- mDeviceList = sdk.getDevices();
DeviceManager manager = sdk.getDeviceManager();
// This method can be called more than once, so avoid duplicate entries
manager.unregisterListener(this);
manager.registerListener(this);
+ mDeviceList = manager.getDevices(DeviceManager.ALL_DEVICES);
} else {
mDeviceList = new ArrayList<Device>();
}
-
- // fill with the devices
- if (!mDeviceList.isEmpty()) {
- Device first = mDeviceList.get(0);
- selectDevice(first);
- List<State> states = first.getAllStates();
- selectDeviceState(states.get(0));
- } else {
- selectDevice(null);
- }
}
/**
* Loads the list of {@link IAndroidTarget} and inits the UI with it.
*/
- private void initTargets() {
+ private boolean initTargets() {
mTargetList.clear();
- IAndroidTarget renderingTarget = mConfiguration.getTarget();
-
Sdk currentSdk = Sdk.getCurrent();
if (currentSdk != null) {
IAndroidTarget[] targets = currentSdk.getTargets();
- IAndroidTarget match = null;
for (int i = 0 ; i < targets.length; i++) {
- // FIXME: add check based on project minSdkVersion
if (targets[i].hasRenderingLibrary()) {
mTargetList.add(targets[i]);
-
- if (renderingTarget != null) {
- // use equals because the rendering could be from a previous SDK, so
- // it may not be the same instance.
- if (renderingTarget.equals(targets[i])) {
- match = targets[i];
- }
- } else if (mProjectTarget == targets[i]) {
- match = targets[i];
- }
}
}
- if (match == null) {
- selectTarget(null);
+ return true;
+ }
- // the rendering target is the same as the project.
- renderingTarget = mProjectTarget;
- } else {
- selectTarget(match);
+ return false;
+ }
- // set the rendering target to the new object.
- renderingTarget = match;
+ /** Ensures that the configuration has been initialized */
+ public void ensureInitialized() {
+ if (mConfiguration.getDevice() == null && mEditedFile != null) {
+ String data = ConfigurationDescription.getDescription(mEditedFile);
+ if (mInitialState != null) {
+ data = mInitialState;
+ mInitialState = null;
+ }
+ if (data != null) {
+ mConfiguration.initialize(data);
+ mConfiguration.syncFolderConfig();
}
}
}
+ private void updateDevices() {
+ if (mDeviceList.size() == 0) {
+ initDevices();
+ }
+ }
+
+ private void updateTargets() {
+ if (mTargetList.size() == 0) {
+ if (!initTargets()) {
+ return;
+ }
+ }
+
+ IAndroidTarget renderingTarget = mConfiguration.getTarget();
+
+ IAndroidTarget match = null;
+ for (IAndroidTarget target : mTargetList) {
+ if (renderingTarget != null) {
+ // use equals because the rendering could be from a previous SDK, so
+ // it may not be the same instance.
+ if (renderingTarget.equals(target)) {
+ match = target;
+ }
+ } else if (mProjectTarget == target) {
+ match = target;
+ }
+
+ }
+
+ if (match == null) {
+ // the rendering target is the same as the project.
+ renderingTarget = mProjectTarget;
+ } else {
+ // set the rendering target to the new object.
+ renderingTarget = match;
+ }
+
+ mConfiguration.setTarget(renderingTarget, true);
+ selectTarget(renderingTarget);
+ }
+
/** Update the toolbar whenever a label has changed, to not only
* cause the layout in the current toolbar to update, but to possibly
* wrap the toolbars and update the layout of the surrounding area.
@@ -925,7 +1015,9 @@ public class ConfigurationChooser extends Composite
*/
public void saveConstraints() {
String description = mConfiguration.toPersistentString();
- AdtPlugin.setFileProperty(mEditedFile, NAME_CONFIG_STATE, description);
+ if (description != null && !description.isEmpty()) {
+ ConfigurationDescription.setDescription(mEditedFile, description);
+ }
}
// ---- Setting the current UI state ----
@@ -1043,6 +1135,11 @@ public class ConfigurationChooser extends Composite
}
private void selectConfiguration(FolderConfiguration fileConfig) {
+ /* For now, don't show any text in the configuration combo, use just an
+ icon. This has the advantage that the configuration contents don't
+ shift around, so you can for example click back and forth between
+ portrait and landscape without the icon moving under the mouse.
+ If this works well, remove this whole method post ADT 21.
assert isUiThread();
try {
String current = mEditedFile.getParent().getName();
@@ -1059,6 +1156,7 @@ public class ConfigurationChooser extends Composite
} finally {
mDisableUpdates--;
}
+ */
}
/**
@@ -1264,12 +1362,16 @@ public class ConfigurationChooser extends Composite
}
}
- // ---- Implements DevicesChangeListener ----
+ // ---- Implements DevicesChangedListener ----
@Override
- public void onDevicesChange() {
+ public void onDevicesChanged() {
final Sdk sdk = Sdk.getCurrent();
- mDeviceList = sdk.getDevices();
+ if (sdk != null) {
+ mDeviceList = sdk.getDeviceManager().getDevices(DeviceManager.ALL_DEVICES);
+ } else {
+ mDeviceList = new ArrayList<Device>();
+ }
}
// ---- Reacting to UI changes ----
@@ -1303,7 +1405,8 @@ public class ConfigurationChooser extends Composite
mConfiguration.syncFolderConfig();
// Notify
- boolean accepted = mClient.changed(CHANGED_DEVICE | CHANGED_DEVICE_CONFIG);
+ IFile file = mEditedFile;
+ boolean accepted = mClient.changed(CFG_DEVICE | CFG_DEVICE_STATE);
if (!accepted) {
mConfiguration.setDevice(prevDevice, true);
mConfiguration.setDeviceState(prevState, true);
@@ -1311,12 +1414,75 @@ public class ConfigurationChooser extends Composite
selectDevice(prevDevice);
selectDeviceState(prevState);
return;
+ } else {
+ syncToVariations(CFG_DEVICE | CFG_DEVICE_STATE, file, mConfiguration, false, true);
}
saveConstraints();
}
/**
+ * Synchronizes changes to the given attributes (indicated by the mask
+ * referencing the {@code CFG_} configuration attribute bit flags in
+ * {@link Configuration} to the layout variations of the given updated file.
+ *
+ * @param flags the attributes which were updated
+ * @param updatedFile the file which was updated
+ * @param base the base configuration to base the chooser off of
+ * @param includeSelf whether the updated file itself should be updated
+ * @param async whether the updates should be performed asynchronously
+ */
+ public void syncToVariations(
+ final int flags,
+ final @NonNull IFile updatedFile,
+ final @NonNull Configuration base,
+ final boolean includeSelf,
+ boolean async) {
+ if (async) {
+ getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ doSyncToVariations(flags, updatedFile, includeSelf, base);
+ }
+ });
+ } else {
+ doSyncToVariations(flags, updatedFile, includeSelf, base);
+ }
+ }
+
+ private void doSyncToVariations(int flags, IFile updatedFile, boolean includeSelf,
+ Configuration base) {
+ // Synchronize the given changes to other configurations as well
+ List<IFile> files = AdtUtils.getResourceVariations(updatedFile, includeSelf);
+ for (IFile file : files) {
+ Configuration configuration = Configuration.create(base, file);
+ configuration.setTheme(base.getTheme());
+ configuration.setActivity(base.getActivity());
+ Collection<IEditorPart> editors = AdtUtils.findEditorsFor(file, false);
+ boolean found = false;
+ for (IEditorPart editor : editors) {
+ if (editor instanceof CommonXmlEditor) {
+ CommonXmlDelegate delegate = ((CommonXmlEditor) editor).getDelegate();
+ if (delegate instanceof LayoutEditorDelegate) {
+ editor = ((LayoutEditorDelegate) delegate).getGraphicalEditor();
+ }
+ }
+ if (editor instanceof GraphicalEditorPart) {
+ ConfigurationChooser chooser =
+ ((GraphicalEditorPart) editor).getConfigurationChooser();
+ chooser.setConfiguration(configuration);
+ found = true;
+ }
+ }
+ if (!found) {
+ // Just update the file persistence
+ String description = configuration.toPersistentString();
+ ConfigurationDescription.setDescription(file, description);
+ }
+ }
+ }
+
+ /**
* Called when the device config selection changes.
*/
void onDeviceConfigChange() {
@@ -1331,7 +1497,7 @@ public class ConfigurationChooser extends Composite
mConfiguration.setDeviceState(state, false);
if (mClient != null) {
- boolean accepted = mClient.changed(CHANGED_DEVICE | CHANGED_DEVICE_CONFIG);
+ boolean accepted = mClient.changed(CFG_DEVICE | CFG_DEVICE_STATE);
if (!accepted) {
mConfiguration.setDeviceState(prev, false);
selectDeviceState(prev);
@@ -1360,7 +1526,7 @@ public class ConfigurationChooser extends Composite
mConfiguration.setLocale(locale, false);
if (mClient != null) {
- boolean accepted = mClient.changed(CHANGED_LOCALE);
+ boolean accepted = mClient.changed(CFG_LOCALE);
if (!accepted) {
mConfiguration.setLocale(prev, false);
selectLocale(prev);
@@ -1381,11 +1547,14 @@ public class ConfigurationChooser extends Composite
mConfiguration.setTheme((String) mThemeCombo.getData());
if (mClient != null) {
- boolean accepted = mClient.changed(CHANGED_THEME);
+ boolean accepted = mClient.changed(CFG_THEME);
if (!accepted) {
mConfiguration.setTheme(prev);
selectTheme(prev);
return;
+ } else {
+ syncToVariations(CFG_DEVICE|CFG_DEVICE_STATE, mEditedFile, mConfiguration,
+ false, true);
}
}
@@ -1397,7 +1566,7 @@ public class ConfigurationChooser extends Composite
return;
}
- if (mClient.changed(CHANGED_FOLDER)) {
+ if (mClient.changed(CFG_FOLDER)) {
saveConstraints();
}
}
@@ -1450,7 +1619,7 @@ public class ConfigurationChooser extends Composite
// tell the listener a new rendering target is being set. Need to do this before updating
// mRenderingTarget.
if (prevTarget != null) {
- changeFlags |= CHANGED_RENDER_TARGET;
+ changeFlags |= CFG_TARGET;
mClient.aboutToChange(changeFlags);
}
@@ -1465,12 +1634,12 @@ public class ConfigurationChooser extends Composite
// updateThemes may change the theme (based on theme availability in the new rendering
// target) so mark theme change if necessary
if (!Objects.equal(oldTheme, mConfiguration.getTheme())) {
- changeFlags |= CHANGED_THEME;
+ changeFlags |= CFG_THEME;
}
if (target != null) {
- changeFlags |= CHANGED_RENDER_TARGET;
- changeFlags |= CHANGED_FOLDER; // In case we added a -vNN qualifier
+ changeFlags |= CFG_TARGET;
+ changeFlags |= CFG_FOLDER; // In case we added a -vNN qualifier
}
// Store project-wide render-target setting
@@ -1518,7 +1687,7 @@ public class ConfigurationChooser extends Composite
if (locale != null) {
boolean localeChanged = setLocale(locale);
if (localeChanged) {
- changeFlags |= CHANGED_LOCALE;
+ changeFlags |= CFG_LOCALE;
}
} else {
locale = Locale.ANY;
@@ -1531,7 +1700,7 @@ public class ConfigurationChooser extends Composite
IAndroidTarget target = pair != null ? pair.getSecond() : configurationTarget;
if (target != null && configurationTarget != target) {
if (mClient != null && configurationTarget != null) {
- changeFlags |= CHANGED_RENDER_TARGET;
+ changeFlags |= CFG_TARGET;
mClient.aboutToChange(changeFlags);
}
@@ -1552,7 +1721,7 @@ public class ConfigurationChooser extends Composite
// Compute the new configuration; we want to do this both for locale changes
// and for render targets.
mConfiguration.syncFolderConfig();
- changeFlags |= CHANGED_FOLDER; // in case we added/remove a -v<NN> qualifier
+ changeFlags |= CFG_FOLDER; // in case we added/remove a -v<NN> qualifier
if (renderTargetChanged) {
// force a theme update to reflect the new rendering target.
@@ -1588,7 +1757,7 @@ public class ConfigurationChooser extends Composite
String theme = mConfiguration.getTheme();
if (theme == null || theme.isEmpty() || mClient.getIncludedWithin() != null) {
mConfiguration.setTheme(null);
- computePreferredTheme();
+ mConfiguration.computePreferredTheme();
}
assert mConfiguration.getTheme() != null;
}
@@ -1680,6 +1849,14 @@ public class ConfigurationChooser extends Composite
break;
}
}
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ // Arbitrary guess
+ if (theme.startsWith("Theme.")) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ } else {
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
}
// TODO: Handle the case where you have a theme persisted that isn't available??
@@ -1748,55 +1925,6 @@ public class ConfigurationChooser extends Composite
}
}
- /** Returns the preferred theme, or null */
- @Nullable
- String computePreferredTheme() {
- if (mClient == null) {
- return null;
- }
-
- IProject project = mEditedFile.getProject();
- ManifestInfo manifest = ManifestInfo.get(project);
-
- // Look up the screen size for the current state
- ScreenSize screenSize = null;
- Device device = mConfiguration.getDevice();
- if (device != null) {
- List<State> states = device.getAllStates();
- for (State state : states) {
- FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(state);
- if (folderConfig != null) {
- ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
- screenSize = qualifier.getValue();
- break;
- }
- }
- }
-
- // Look up the default/fallback theme to use for this project (which
- // depends on the screen size when no particular theme is specified
- // in the manifest)
- String defaultTheme = manifest.getDefaultTheme(mConfiguration.getTarget(), screenSize);
-
- String preferred = defaultTheme;
- if (mConfiguration.getTheme() == null) {
- // If we are rendering a layout in included context, pick the theme
- // from the outer layout instead
-
- String activity = mConfiguration.getActivity();
- if (activity != null) {
- Map<String, String> activityThemes = manifest.getActivityThemes();
- preferred = activityThemes.get(activity);
- }
- if (preferred == null) {
- preferred = defaultTheme;
- }
- mConfiguration.setTheme(preferred);
- }
-
- return preferred;
- }
-
@Nullable
private String getPreferredActivity(@NonNull IFile file) {
// Store/restore the activity context in the config state to help with
@@ -1942,4 +2070,22 @@ public class ConfigurationChooser extends Composite
return false;
}
+
+ /**
+ * Returns true if this configuration chooser represents the best match for
+ * the given file
+ *
+ * @param file the file to test
+ * @param config the config to test
+ * @return true if the given config is the best match for the given file
+ */
+ public boolean isBestMatchFor(IFile file, FolderConfiguration config) {
+ ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
+ ResourceType.LAYOUT, config);
+ if (match != null) {
+ return match.getFile().equals(mEditedFile);
+ }
+
+ return false;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java
index a7c26d4..3df2fed 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationClient.java
@@ -19,14 +19,10 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceRepository;
-import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
-import com.android.resources.NightMode;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
import com.android.resources.ResourceType;
-import com.android.resources.UiMode;
import com.android.sdklib.IAndroidTarget;
-import com.android.sdklib.devices.Device;
-import com.android.sdklib.devices.State;
import java.util.Map;
@@ -34,31 +30,13 @@ import java.util.Map;
* Interface implemented by clients who embed a {@link ConfigurationChooser}.
*/
public interface ConfigurationClient {
- /** The {@link FolderConfiguration} in the configuration has changed */
- public static final int CHANGED_FOLDER = 1 << 0;
- /** The {@link Device} in the configuration has changed */
- public static final int CHANGED_DEVICE = 1 << 1;
- /** The {@link State} in the configuration has changed */
- public static final int CHANGED_DEVICE_CONFIG = 1 << 2;
- /** The theme in the configuration has changed */
- public static final int CHANGED_THEME = 1 << 3;
- /** The locale in the configuration has changed */
- public static final int CHANGED_LOCALE = 1 << 4;
- /** The rendering {@link IAndroidTarget} in the configuration has changed */
- public static final int CHANGED_RENDER_TARGET = 1 << 5;
- /** The {@link NightMode} in the configuration has changed */
- public static final int CHANGED_NIGHT_MODE = 1 << 6;
- /** The {@link UiMode} in the configuration has changed */
- public static final int CHANGED_UI_MODE = 1 << 7;
-
- /** Everything has changed */
- public static final int CHANGED_ALL = 0xFFFF;
-
/**
* The configuration is about to be changed.
*
- * @param flags details about what changed; consult the {@code CHANGED_} flags
- * such as {@link #CHANGED_DEVICE}, {@link #CHANGED_LOCALE}, etc.
+ * @param flags details about what changed; consult the {@code CFG_} flags
+ * in {@link Configuration} such as
+ * {@link Configuration#CFG_DEVICE},
+ * {@link Configuration#CFG_LOCALE}, etc.
*/
void aboutToChange(int flags);
@@ -70,8 +48,9 @@ public interface ConfigurationClient {
* file to edit the new configuration -- and the current configuration
* should go back to editing the state prior to this change.
*
- * @param flags details about what changed; consult the {@code CHANGED_} flags
- * such as {@link #CHANGED_DEVICE}, {@link #CHANGED_LOCALE}, etc.
+ * @param flags details about what changed; consult the {@code CFG_} flags
+ * such as {@link Configuration#CFG_DEVICE},
+ * {@link Configuration#CFG_LOCALE}, etc.
* @return true if the change was accepted, false if it was rejected.
*/
boolean changed(int flags);
@@ -139,4 +118,12 @@ public interface ConfigurationClient {
* @param fqcn the fully qualified class name for the associated activity context
*/
void setActivity(@NonNull String fqcn);
+
+ /**
+ * Returns the associated layout canvas, if any
+ *
+ * @return the canvas, if any
+ */
+ @Nullable
+ LayoutCanvas getCanvas();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java
new file mode 100644
index 0000000..7141f94
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationDescription.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_THEME;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.RegionQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.NightMode;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ScreenSize;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+import com.google.common.base.Splitter;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.QualifiedName;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.List;
+import java.util.Map;
+
+/** A description of a configuration, used for persistence */
+public class ConfigurationDescription {
+ private static final String TAG_PREVIEWS = "previews"; //$NON-NLS-1$
+ private static final String TAG_PREVIEW = "preview"; //$NON-NLS-1$
+ private static final String ATTR_TARGET = "target"; //$NON-NLS-1$
+ private static final String ATTR_CONFIG = "config"; //$NON-NLS-1$
+ private static final String ATTR_LOCALE = "locale"; //$NON-NLS-1$
+ private static final String ATTR_ACTIVITY = "activity"; //$NON-NLS-1$
+ private static final String ATTR_DEVICE = "device"; //$NON-NLS-1$
+ private static final String ATTR_STATE = "devicestate"; //$NON-NLS-1$
+ private static final String ATTR_UIMODE = "ui"; //$NON-NLS-1$
+ private static final String ATTR_NIGHTMODE = "night"; //$NON-NLS-1$
+ private final static String SEP_LOCALE = "-"; //$NON-NLS-1$
+
+ /**
+ * Settings name for file-specific configuration preferences, such as which theme or
+ * device to render the current layout with
+ */
+ public final static QualifiedName NAME_CONFIG_STATE =
+ new QualifiedName(AdtPlugin.PLUGIN_ID, "state");//$NON-NLS-1$
+
+ /** The project corresponding to this configuration's description */
+ public final IProject project;
+
+ /** The display name */
+ public String displayName;
+
+ /** The theme */
+ public String theme;
+
+ /** The target */
+ public IAndroidTarget target;
+
+ /** The display name */
+ public FolderConfiguration folder;
+
+ /** The locale */
+ public Locale locale = Locale.ANY;
+
+ /** The device */
+ public Device device;
+
+ /** The device state */
+ public State state;
+
+ /** The activity */
+ public String activity;
+
+ /** UI mode */
+ @NonNull
+ public UiMode uiMode = UiMode.NORMAL;
+
+ /** Night mode */
+ @NonNull
+ public NightMode nightMode = NightMode.NOTNIGHT;
+
+ private ConfigurationDescription(@Nullable IProject project) {
+ this.project = project;
+ }
+
+ /**
+ * Returns the persistent configuration description from the given file
+ *
+ * @param file the file to look up a description from
+ * @return the description or null if never written
+ */
+ @Nullable
+ public static String getDescription(@NonNull IFile file) {
+ return AdtPlugin.getFileProperty(file, NAME_CONFIG_STATE);
+ }
+
+ /**
+ * Sets the persistent configuration description data for the given file
+ *
+ * @param file the file to associate the description with
+ * @param description the description
+ */
+ public static void setDescription(@NonNull IFile file, @NonNull String description) {
+ AdtPlugin.setFileProperty(file, NAME_CONFIG_STATE, description);
+ }
+
+ /**
+ * Creates a description from a given configuration
+ *
+ * @param project the project for this configuration's description
+ * @param configuration the configuration to describe
+ * @return a new configuration
+ */
+ public static ConfigurationDescription fromConfiguration(
+ @Nullable IProject project,
+ @NonNull Configuration configuration) {
+ ConfigurationDescription description = new ConfigurationDescription(project);
+ description.displayName = configuration.getDisplayName();
+ description.theme = configuration.getTheme();
+ description.target = configuration.getTarget();
+ description.folder = new FolderConfiguration();
+ description.folder.set(configuration.getFullConfig());
+ description.locale = configuration.getLocale();
+ description.device = configuration.getDevice();
+ description.state = configuration.getDeviceState();
+ description.activity = configuration.getActivity();
+ return description;
+ }
+
+ /**
+ * Initializes a string previously created with
+ * {@link #toXml(Document)}
+ *
+ * @param project the project for this configuration's description
+ * @param element the element to read back from
+ * @param deviceList list of available devices
+ * @return true if the configuration was initialized
+ */
+ @Nullable
+ public static ConfigurationDescription fromXml(
+ @Nullable IProject project,
+ @NonNull Element element,
+ @NonNull List<Device> deviceList) {
+ ConfigurationDescription description = new ConfigurationDescription(project);
+
+ if (!TAG_PREVIEW.equals(element.getTagName())) {
+ return null;
+ }
+
+ String displayName = element.getAttribute(ATTR_NAME);
+ if (!displayName.isEmpty()) {
+ description.displayName = displayName;
+ }
+
+ String config = element.getAttribute(ATTR_CONFIG);
+ Iterable<String> segments = Splitter.on('-').split(config);
+ description.folder = FolderConfiguration.getConfig(segments);
+
+ String theme = element.getAttribute(ATTR_THEME);
+ if (!theme.isEmpty()) {
+ description.theme = theme;
+ }
+
+ String targetId = element.getAttribute(ATTR_TARGET);
+ if (!targetId.isEmpty()) {
+ IAndroidTarget target = Configuration.stringToTarget(targetId);
+ description.target = target;
+ }
+
+ String localeString = element.getAttribute(ATTR_LOCALE);
+ if (!localeString.isEmpty()) {
+ // Load locale. Note that this can get overwritten by the
+ // project-wide settings read below.
+ LanguageQualifier language = Locale.ANY_LANGUAGE;
+ RegionQualifier region = Locale.ANY_REGION;
+ String locales[] = localeString.split(SEP_LOCALE);
+ if (locales[0].length() > 0) {
+ language = new LanguageQualifier(locales[0]);
+ }
+ if (locales.length > 1 && locales[1].length() > 0) {
+ region = new RegionQualifier(locales[1]);
+ }
+ description.locale = Locale.create(language, region);
+ }
+
+ String activity = element.getAttribute(ATTR_ACTIVITY);
+ if (activity.isEmpty()) {
+ activity = null;
+ }
+
+ String deviceString = element.getAttribute(ATTR_DEVICE);
+ if (!deviceString.isEmpty()) {
+ for (Device d : deviceList) {
+ if (d.getName().equals(deviceString)) {
+ description.device = d;
+ String stateName = element.getAttribute(ATTR_STATE);
+ if (stateName.isEmpty() || stateName.equals("null")) {
+ description.state = Configuration.getState(d, stateName);
+ } else if (d.getAllStates().size() > 0) {
+ description.state = d.getAllStates().get(0);
+ }
+ break;
+ }
+ }
+ }
+
+ String uiModeString = element.getAttribute(ATTR_UIMODE);
+ if (!uiModeString.isEmpty()) {
+ description.uiMode = UiMode.getEnum(uiModeString);
+ if (description.uiMode == null) {
+ description.uiMode = UiMode.NORMAL;
+ }
+ }
+
+ String nightModeString = element.getAttribute(ATTR_NIGHTMODE);
+ if (!nightModeString.isEmpty()) {
+ description.nightMode = NightMode.getEnum(nightModeString);
+ if (description.nightMode == null) {
+ description.nightMode = NightMode.NOTNIGHT;
+ }
+ }
+
+
+ // Should I really be storing the FULL configuration? Might be trouble if
+ // you bring a different device
+
+ return description;
+ }
+
+ /**
+ * Write this description into the given document as a new element.
+ *
+ * @param document the document to add the description to
+ * @return the newly inserted element
+ */
+ @NonNull
+ public Element toXml(Document document) {
+ Element element = document.createElement(TAG_PREVIEW);
+
+ element.setAttribute(ATTR_NAME, displayName);
+ FolderConfiguration fullConfig = folder;
+ String folderName = fullConfig.getFolderName(ResourceFolderType.LAYOUT);
+ element.setAttribute(ATTR_CONFIG, folderName);
+ if (theme != null) {
+ element.setAttribute(ATTR_THEME, theme);
+ }
+ if (target != null) {
+ element.setAttribute(ATTR_TARGET, Configuration.targetToString(target));
+ }
+
+ if (locale != null && (locale.hasLanguage() || locale.hasRegion())) {
+ String value;
+ if (locale.hasRegion()) {
+ value = locale.language.getValue() + SEP_LOCALE + locale.region.getValue();
+ } else {
+ value = locale.language.getValue();
+ }
+ element.setAttribute(ATTR_LOCALE, value);
+ }
+
+ if (device != null) {
+ element.setAttribute(ATTR_DEVICE, device.getName());
+ if (state != null) {
+ element.setAttribute(ATTR_STATE, state.getName());
+ }
+ }
+
+ if (activity != null) {
+ element.setAttribute(ATTR_ACTIVITY, activity);
+ }
+
+ if (uiMode != null && uiMode != UiMode.NORMAL) {
+ element.setAttribute(ATTR_UIMODE, uiMode.getResourceValue());
+ }
+
+ if (nightMode != null && nightMode != NightMode.NOTNIGHT) {
+ element.setAttribute(ATTR_NIGHTMODE, nightMode.getResourceValue());
+ }
+
+ Element parent = document.getDocumentElement();
+ if (parent == null) {
+ parent = document.createElement(TAG_PREVIEWS);
+ document.appendChild(parent);
+ }
+ parent.appendChild(element);
+
+ return element;
+ }
+
+ /** Returns the preferred theme, or null */
+ @Nullable
+ String computePreferredTheme() {
+ if (project == null) {
+ return "Theme";
+ }
+ ManifestInfo manifest = ManifestInfo.get(project);
+
+ // Look up the screen size for the current state
+ ScreenSize screenSize = null;
+ if (device != null) {
+ List<State> states = device.getAllStates();
+ for (State s : states) {
+ FolderConfiguration folderConfig = DeviceConfigHelper.getFolderConfig(s);
+ if (folderConfig != null) {
+ ScreenSizeQualifier qualifier = folderConfig.getScreenSizeQualifier();
+ screenSize = qualifier.getValue();
+ break;
+ }
+ }
+ }
+
+ // Look up the default/fallback theme to use for this project (which
+ // depends on the screen size when no particular theme is specified
+ // in the manifest)
+ String defaultTheme = manifest.getDefaultTheme(target, screenSize);
+
+ String preferred = defaultTheme;
+ if (theme == null) {
+ // If we are rendering a layout in included context, pick the theme
+ // from the outer layout instead
+
+ if (activity != null) {
+ Map<String, String> activityThemes = manifest.getActivityThemes();
+ preferred = activityThemes.get(activity);
+ }
+ if (preferred == null) {
+ preferred = defaultTheme;
+ }
+ theme = preferred;
+ }
+
+ return preferred;
+ }
+
+ private void checkThemePrefix() {
+ if (theme != null && !theme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (theme.isEmpty()) {
+ computePreferredTheme();
+ return;
+ }
+
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ AndroidTargetData data = sdk.getTargetData(target);
+
+ if (data != null) {
+ ResourceRepository resources = data.getFrameworkResources();
+ if (resources != null
+ && resources.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ return;
+ }
+ }
+ }
+ }
+
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
index dc64b36..5dfcdb8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMatcher.java
@@ -38,7 +38,7 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
import com.android.resources.Density;
import com.android.resources.NightMode;
-import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.resources.ScreenSize;
import com.android.resources.UiMode;
@@ -59,12 +59,37 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-/** Produces matches for configurations */
+/**
+ * Produces matches for configurations
+ * <p>
+ * See algorithm described here:
+ * http://developer.android.com/guide/topics/resources/providing-resources.html
+ */
public class ConfigurationMatcher {
+ private static final boolean PREFER_RECENT_RENDER_TARGETS = true;
+
private final ConfigurationChooser mConfigChooser;
+ private final Configuration mConfiguration;
+ private final IFile mEditedFile;
+ private final ProjectResources mResources;
+ private final boolean mUpdateUi;
ConfigurationMatcher(ConfigurationChooser chooser) {
+ this(chooser, chooser.getConfiguration(), chooser.getEditedFile(),
+ chooser.getResources(), true);
+ }
+
+ ConfigurationMatcher(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration,
+ @Nullable IFile editedFile,
+ @Nullable ProjectResources resources,
+ boolean updateUi) {
mConfigChooser = chooser;
+ mConfiguration = configuration;
+ mEditedFile = editedFile;
+ mResources = resources;
+ mUpdateUi = updateUi;
}
// ---- Finding matching configurations ----
@@ -118,14 +143,12 @@ public class ConfigurationMatcher {
* @return true if the current edited file is the best match in the project for the
* given config.
*/
- boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
- ProjectResources resources = mConfigChooser.getResources();
- IFile editedFile = mConfigChooser.getEditedFile();
- ResourceFile match = resources.getMatchingFile(editedFile.getName(),
- ResourceFolderType.LAYOUT, config);
+ public boolean isCurrentFileBestMatchFor(FolderConfiguration config) {
+ ResourceFile match = mResources.getMatchingFile(mEditedFile.getName(),
+ ResourceType.LAYOUT, config);
if (match != null) {
- return match.getFile().equals(editedFile);
+ return match.getFile().equals(mEditedFile);
} else {
// if we stop here that means the current file is not even a match!
AdtPlugin.log(IStatus.ERROR, "Current file is not a match for the given config.");
@@ -149,9 +172,8 @@ public class ConfigurationMatcher {
// check the device config (ie sans locale)
boolean needConfigChange = true; // if still true, we need to find another config.
boolean currentConfigIsCompatible = false;
- Configuration configuration = mConfigChooser.getConfiguration();
- State selectedState = configuration.getDeviceState();
- FolderConfiguration editedConfig = configuration.getEditedConfig();
+ State selectedState = mConfiguration.getDeviceState();
+ FolderConfiguration editedConfig = mConfiguration.getEditedConfig();
if (selectedState != null) {
FolderConfiguration currentConfig = DeviceConfigHelper.getFolderConfig(selectedState);
if (currentConfig != null && editedConfig.isMatchFor(currentConfig)) {
@@ -172,7 +194,7 @@ public class ConfigurationMatcher {
// first look in the current device.
State matchState = null;
int localeIndex = -1;
- Device device = configuration.getDevice();
+ Device device = mConfiguration.getDevice();
if (device != null) {
mainloop: for (State state : device.getAllStates()) {
testConfig.set(DeviceConfigHelper.getFolderConfig(state));
@@ -196,12 +218,14 @@ public class ConfigurationMatcher {
}
if (matchState != null) {
- configuration.setDeviceState(matchState, true);
+ mConfiguration.setDeviceState(matchState, true);
Locale locale = localeList.get(localeIndex);
- configuration.setLocale(locale, true);
- mConfigChooser.selectDeviceState(matchState);
- mConfigChooser.selectLocale(locale);
- configuration.syncFolderConfig();
+ mConfiguration.setLocale(locale, true);
+ if (mUpdateUi) {
+ mConfigChooser.selectDeviceState(matchState);
+ mConfigChooser.selectLocale(locale);
+ }
+ mConfiguration.syncFolderConfig();
} else {
// no match in current device with any state/locale
// attempt to find another device that can display this
@@ -225,9 +249,8 @@ public class ConfigurationMatcher {
void findAndSetCompatibleConfig(boolean favorCurrentConfig) {
List<Locale> localeList = mConfigChooser.getLocaleList();
List<Device> deviceList = mConfigChooser.getDeviceList();
- Configuration configuration = mConfigChooser.getConfiguration();
- FolderConfiguration editedConfig = configuration.getEditedConfig();
- FolderConfiguration currentConfig = configuration.getFullConfig();
+ FolderConfiguration editedConfig = mConfiguration.getEditedConfig();
+ FolderConfiguration currentConfig = mConfiguration.getFullConfig();
// list of compatible device/state/locale
List<ConfigMatch> anyMatches = new ArrayList<ConfigMatch>();
@@ -316,7 +339,7 @@ public class ConfigurationMatcher {
}
// just display the warning
- AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
String.format(
"'%1$s' is not a best match for any device/locale combination.",
editedConfig.toDisplayString()),
@@ -326,21 +349,23 @@ public class ConfigurationMatcher {
} else if (anyMatches.size() > 0) {
// select the best device anyway.
ConfigMatch match = selectConfigMatch(anyMatches);
- configuration.setDevice(match.device, true);
- configuration.setDeviceState(match.state, true);
- configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
- configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
- configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex),
+ mConfiguration.setDevice(match.device, true);
+ mConfiguration.setDeviceState(match.state, true);
+ mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex),
true);
- mConfigChooser.selectDevice(configuration.getDevice());
- mConfigChooser.selectDeviceState(configuration.getDeviceState());
- mConfigChooser.selectLocale(configuration.getLocale());
+ if (mUpdateUi) {
+ mConfigChooser.selectDevice(mConfiguration.getDevice());
+ mConfigChooser.selectDeviceState(mConfiguration.getDeviceState());
+ mConfigChooser.selectLocale(mConfiguration.getLocale());
+ }
- configuration.syncFolderConfig();
+ mConfiguration.syncFolderConfig();
// TODO: display a better warning!
- AdtPlugin.printErrorToConsole(mConfigChooser.getProject(),
+ AdtPlugin.printErrorToConsole(mEditedFile.getProject(),
String.format(
"'%1$s' is not a best match for any device/locale combination.",
editedConfig.toDisplayString()),
@@ -357,17 +382,19 @@ public class ConfigurationMatcher {
}
} else {
ConfigMatch match = selectConfigMatch(bestMatches);
- configuration.setDevice(match.device, true);
- configuration.setDeviceState(match.state, true);
- configuration.setLocale(localeList.get(match.bundle.localeIndex), true);
- configuration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
- configuration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true);
-
- configuration.syncFolderConfig();
-
- mConfigChooser.selectDevice(configuration.getDevice());
- mConfigChooser.selectDeviceState(configuration.getDeviceState());
- mConfigChooser.selectLocale(configuration.getLocale());
+ mConfiguration.setDevice(match.device, true);
+ mConfiguration.setDeviceState(match.state, true);
+ mConfiguration.setLocale(localeList.get(match.bundle.localeIndex), true);
+ mConfiguration.setUiMode(UiMode.getByIndex(match.bundle.dockModeIndex), true);
+ mConfiguration.setNightMode(NightMode.getByIndex(match.bundle.nightModeIndex), true);
+
+ mConfiguration.syncFolderConfig();
+
+ if (mUpdateUi) {
+ mConfigChooser.selectDevice(mConfiguration.getDevice());
+ mConfigChooser.selectDeviceState(mConfiguration.getDeviceState());
+ mConfigChooser.selectLocale(mConfiguration.getLocale());
+ }
}
}
@@ -455,15 +482,24 @@ public class ConfigurationMatcher {
private ConfigMatch selectConfigMatch(List<ConfigMatch> matches) {
// API 11-13: look for a x-large device
- int apiLevel = mConfigChooser.getProjectTarget().getVersion().getApiLevel();
- if (apiLevel >= 11 && apiLevel < 14) {
- // TODO: Maybe check the compatible-screen tag in the manifest to figure out
- // what kind of device should be used for display.
- Collections.sort(matches, new TabletConfigComparator());
- } else {
+ Comparator<ConfigMatch> comparator = null;
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ IAndroidTarget projectTarget = sdk.getTarget(mEditedFile.getProject());
+ if (projectTarget != null) {
+ int apiLevel = projectTarget.getVersion().getApiLevel();
+ if (apiLevel >= 11 && apiLevel < 14) {
+ // TODO: Maybe check the compatible-screen tag in the manifest to figure out
+ // what kind of device should be used for display.
+ comparator = new TabletConfigComparator();
+ }
+ }
+ }
+ if (comparator == null) {
// lets look for a high density device
- Collections.sort(matches, new PhoneConfigComparator());
+ comparator = new PhoneConfigComparator();
}
+ Collections.sort(matches, comparator);
// Look at the currently active editor to see if it's a layout editor, and if so,
// look up its configuration and if the configuration is in our match list,
@@ -473,7 +509,7 @@ public class ConfigurationMatcher {
LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
if (delegate != null
// (Only do this when the two files are in the same project)
- && delegate.getEditor().getProject() == mConfigChooser.getProject()) {
+ && delegate.getEditor().getProject() == mEditedFile.getProject()) {
FolderConfiguration configuration = delegate.getGraphicalEditor().getConfiguration();
if (configuration != null) {
for (ConfigMatch match : matches) {
@@ -490,7 +526,16 @@ public class ConfigurationMatcher {
/** Return the default render target to use, or null if no strong preference */
@Nullable
- static IAndroidTarget findDefaultRenderTarget(@NonNull IProject project) {
+ static IAndroidTarget findDefaultRenderTarget(ConfigurationChooser chooser) {
+ if (PREFER_RECENT_RENDER_TARGETS) {
+ // Use the most recent target
+ List<IAndroidTarget> targetList = chooser.getTargetList();
+ if (!targetList.isEmpty()) {
+ return targetList.get(targetList.size() - 1);
+ }
+ }
+
+ IProject project = chooser.getProject();
// Default to layoutlib version 5
Sdk current = Sdk.getCurrent();
if (current != null) {
@@ -636,9 +681,13 @@ public class ConfigurationMatcher {
}
// From the resources, look for a matching file
- String name = chooser.getEditedFile().getName();
+ IFile editedFile = chooser.getEditedFile();
+ if (editedFile == null) {
+ return null;
+ }
+ String name = editedFile.getName();
FolderConfiguration config = chooser.getConfiguration().getFullConfig();
- ResourceFile match = resources.getMatchingFile(name, ResourceFolderType.LAYOUT, config);
+ ResourceFile match = resources.getMatchingFile(name, ResourceType.LAYOUT, config);
if (match != null) {
// In Eclipse, the match's file is always an instance of IFileWrapper
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
index 30f7dc2..a791c63 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationMenuListener.java
@@ -16,21 +16,33 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
-import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.CUSTOM;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.DEFAULT;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.INCLUDES;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.LOCALES;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.NONE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.SCREENS;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.VARIATIONS;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
@@ -39,9 +51,9 @@ import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolItem;
+import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -52,18 +64,24 @@ class ConfigurationMenuListener extends SelectionAdapter {
private static final String ICON_NEW_CONFIG = "newConfig"; //$NON-NLS-1$
private static final int ACTION_SELECT_CONFIG = 1;
private static final int ACTION_CREATE_CONFIG_FILE = 2;
+ private static final int ACTION_ADD = 3;
+ private static final int ACTION_DELETE_ALL = 4;
+ private static final int ACTION_PREVIEW_MODE = 5;
private final ConfigurationChooser mConfigChooser;
private final int mAction;
private final IFile mResource;
+ private final RenderPreviewMode mMode;
ConfigurationMenuListener(
@NonNull ConfigurationChooser configChooser,
int action,
- @Nullable IFile resource) {
+ @Nullable IFile resource,
+ @Nullable RenderPreviewMode mode) {
mConfigChooser = configChooser;
mAction = action;
mResource = resource;
+ mMode = mode;
}
@Override
@@ -75,77 +93,165 @@ class ConfigurationMenuListener extends SelectionAdapter {
} catch (PartInitException ex) {
AdtPlugin.log(ex, null);
}
- break;
+ return;
}
case ACTION_CREATE_CONFIG_FILE: {
ConfigurationClient client = mConfigChooser.getClient();
if (client != null) {
client.createConfigFile();
}
+ return;
+ }
+ }
+
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
+ IFile editedFile = mConfigChooser.getEditedFile();
+
+ if (delegate == null || editedFile == null) {
+ return;
+ }
+ // (Only do this when the two files are in the same project)
+ IProject project = delegate.getEditor().getProject();
+ if (project == null ||
+ !project.equals(editedFile.getProject())) {
+ return;
+ }
+ LayoutCanvas canvas = delegate.getGraphicalEditor().getCanvasControl();
+ RenderPreviewManager previewManager = canvas.getPreviewManager();
+
+ switch (mAction) {
+ case ACTION_ADD: {
+ previewManager.addAsThumbnail();
+ break;
+ }
+ case ACTION_PREVIEW_MODE: {
+ previewManager.selectMode(mMode);
+ break;
+ }
+ case ACTION_DELETE_ALL: {
+ previewManager.deleteManualPreviews();
break;
}
default: assert false : mAction;
}
+ canvas.setFitScale(true /*onlyZoomOut*/, false /*allowZoomIn*/);
+ canvas.redraw();
}
static void show(ConfigurationChooser chooser, ToolItem combo) {
Menu menu = new Menu(chooser.getShell(), SWT.POP_UP);
+ RenderPreviewMode mode = AdtPrefs.getPrefs().getRenderPreviewMode();
- // Compute the set of layout files defining this layout resource
- IFile file = chooser.getEditedFile();
- String name = file.getName();
- IContainer resFolder = file.getParent().getParent();
- List<IFile> variations = new ArrayList<IFile>();
- try {
- for (IResource resource : resFolder.members()) {
- if (resource.getName().startsWith(FD_RES_LAYOUT)
- && resource instanceof IContainer) {
- IContainer layoutFolder = (IContainer) resource;
- IResource variation = layoutFolder.findMember(name);
- if (variation instanceof IFile) {
- variations.add((IFile) variation);
- }
+ // Configuration Previews
+ create(menu, "Add As Thumbnail...",
+ new ConfigurationMenuListener(chooser, ACTION_ADD, null, null),
+ SWT.PUSH, false);
+ if (mode == RenderPreviewMode.CUSTOM) {
+ MenuItem item = create(menu, "Delete All Thumbnails",
+ new ConfigurationMenuListener(chooser, ACTION_DELETE_ALL, null, null),
+ SWT.PUSH, false);
+ IEditorPart activeEditor = AdtUtils.getActiveEditor();
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(activeEditor);
+ if (delegate != null) {
+ LayoutCanvas canvas = delegate.getGraphicalEditor().getCanvasControl();
+ RenderPreviewManager previewManager = canvas.getPreviewManager();
+ if (!previewManager.hasManualPreviews()) {
+ item.setEnabled(false);
}
}
- } catch (CoreException e1) {
- AdtPlugin.log(e1, null);
}
- ResourceManager manager = ResourceManager.getInstance();
- for (final IFile resource : variations) {
- MenuItem item = new MenuItem(menu, SWT.CHECK);
+ @SuppressWarnings("unused")
+ MenuItem configSeparator = new MenuItem(menu, SWT.SEPARATOR);
- IFolder parent = (IFolder) resource.getParent();
- ResourceFolder parentResource = manager.getResourceFolder(parent);
- FolderConfiguration configuration = parentResource.getConfiguration();
- String title = configuration.toDisplayString();
- item.setText(title);
+ create(menu, "Preview Representative Sample",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ DEFAULT), SWT.RADIO, mode == DEFAULT);
+ create(menu, "Preview All Screen Sizes",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ SCREENS), SWT.RADIO, mode == SCREENS);
- boolean selected = file.equals(resource);
- if (selected) {
- item.setSelection(true);
- item.setEnabled(false);
- }
+ MenuItem localeItem = create(menu, "Preview All Locales",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ LOCALES), SWT.RADIO, mode == LOCALES);
+ if (chooser.getLocaleList().size() <= 1) {
+ localeItem.setEnabled(false);
+ }
- item.addSelectionListener(new ConfigurationMenuListener(chooser,
- ACTION_SELECT_CONFIG, resource));
+ boolean canPreviewIncluded = false;
+ IProject project = chooser.getProject();
+ if (project != null) {
+ IncludeFinder finder = IncludeFinder.get(project);
+ final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile());
+ canPreviewIncluded = includedBy != null && !includedBy.isEmpty();
+ }
+ //if (!graphicalEditor.renderingSupports(Capability.EMBEDDED_LAYOUT)) {
+ // canPreviewIncluded = false;
+ //}
+ MenuItem includedItem = create(menu, "Preview Included",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ INCLUDES), SWT.RADIO, mode == INCLUDES);
+ if (!canPreviewIncluded) {
+ includedItem.setEnabled(false);
+ }
+
+ IFile file = chooser.getEditedFile();
+ List<IFile> variations = AdtUtils.getResourceVariations(file, true);
+ MenuItem variationsItem = create(menu, "Preview Layout Versions",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ VARIATIONS), SWT.RADIO, mode == VARIATIONS);
+ if (variations.size() <= 1) {
+ variationsItem.setEnabled(false);
+ }
+
+ create(menu, "Manual Previews",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ CUSTOM), SWT.RADIO, mode == CUSTOM);
+ create(menu, "None",
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null,
+ NONE), SWT.RADIO, mode == NONE);
+
+ if (variations.size() > 1) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ ResourceManager manager = ResourceManager.getInstance();
+ for (final IFile resource : variations) {
+ IFolder parent = (IFolder) resource.getParent();
+ ResourceFolder parentResource = manager.getResourceFolder(parent);
+ FolderConfiguration configuration = parentResource.getConfiguration();
+ String title = configuration.toDisplayString();
+
+ MenuItem item = create(menu, title,
+ new ConfigurationMenuListener(chooser, ACTION_SELECT_CONFIG,
+ resource, null),
+ SWT.CHECK, false);
+
+ if (file != null) {
+ boolean selected = file.equals(resource);
+ if (selected) {
+ item.setSelection(true);
+ item.setEnabled(false);
+ }
+ }
+ }
}
Configuration configuration = chooser.getConfiguration();
- if (!configuration.getEditedConfig().equals(configuration.getFullConfig())) {
+ if (configuration.getEditedConfig() != null &&
+ !configuration.getEditedConfig().equals(configuration.getFullConfig())) {
if (variations.size() > 0) {
@SuppressWarnings("unused")
MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
}
// Add action for creating a new configuration
- MenuItem item = new MenuItem(menu, SWT.PUSH);
- item.setText("Create New...");
+ MenuItem item = create(menu, "Create New...",
+ new ConfigurationMenuListener(chooser, ACTION_CREATE_CONFIG_FILE,
+ null, null),
+ SWT.PUSH, false);
item.setImage(IconFactory.getInstance().getIcon(ICON_NEW_CONFIG));
- //item.setToolTipText("Duplicate: Create new configuration for this layout");
-
- item.addSelectionListener(
- new ConfigurationMenuListener(chooser, ACTION_CREATE_CONFIG_FILE, null));
}
Rectangle bounds = combo.getBounds();
@@ -154,4 +260,31 @@ class ConfigurationMenuListener extends SelectionAdapter {
menu.setLocation(location.x, location.y);
menu.setVisible(true);
}
+
+ @NonNull
+ public static MenuItem create(@NonNull Menu menu, String title,
+ ConfigurationMenuListener listener, int style, boolean selected) {
+ MenuItem item = new MenuItem(menu, style);
+ item.setText(title);
+ item.addSelectionListener(listener);
+ if (selected) {
+ item.setSelection(true);
+ }
+ return item;
+ }
+
+ @NonNull
+ static MenuItem addTogglePreviewModeAction(
+ @NonNull Menu menu,
+ @NonNull String title,
+ @NonNull ConfigurationChooser chooser,
+ @NonNull RenderPreviewMode mode) {
+ boolean selected = AdtPrefs.getPrefs().getRenderPreviewMode() == mode;
+ if (selected) {
+ mode = RenderPreviewMode.NONE;
+ }
+ return create(menu, title,
+ new ConfigurationMenuListener(chooser, ACTION_PREVIEW_MODE, null, mode),
+ SWT.CHECK, selected);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java
index 32f8e9d..4489b52 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/DeviceMenuListener.java
@@ -16,11 +16,18 @@
package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+import static com.android.ide.common.rendering.HardwareConfigHelper.MANUFACTURER_GENERIC;
+import static com.android.ide.common.rendering.HardwareConfigHelper.getGenericLabel;
+import static com.android.ide.common.rendering.HardwareConfigHelper.getNexusLabel;
+import static com.android.ide.common.rendering.HardwareConfigHelper.isGeneric;
+import static com.android.ide.common.rendering.HardwareConfigHelper.isNexus;
+import static com.android.ide.common.rendering.HardwareConfigHelper.sortNexusList;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.devices.Device;
-import com.android.sdklib.devices.Screen;
import com.android.sdklib.internal.avd.AvdInfo;
import com.android.sdklib.internal.avd.AvdManager;
@@ -35,23 +42,15 @@ import org.eclipse.swt.widgets.ToolItem;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* The {@linkplain DeviceMenuListener} class is responsible for generating the device
* menu in the {@link ConfigurationChooser}.
*/
class DeviceMenuListener extends SelectionAdapter {
- private static final String NEXUS = "Nexus"; //$NON-NLS-1$
- private static final String GENERIC = "Generic"; //$NON-NLS-1$
- private static Pattern PATTERN = Pattern.compile(
- "(\\d+\\.?\\d*)in (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$
-
private final ConfigurationChooser mConfigChooser;
private final Device mDevice;
@@ -147,7 +146,7 @@ class DeviceMenuListener extends SelectionAdapter {
for (List<Device> devices : manufacturers.values()) {
for (Device device : devices) {
if (isNexus(device)) {
- if (device.getManufacturer().equals(GENERIC)) {
+ if (device.getManufacturer().equals(MANUFACTURER_GENERIC)) {
generic.add(device);
} else {
nexus.add(device);
@@ -183,94 +182,17 @@ class DeviceMenuListener extends SelectionAdapter {
}
}
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ ConfigurationMenuListener.addTogglePreviewModeAction(menu,
+ "Preview All Screens", chooser, RenderPreviewMode.SCREENS);
+
+
Rectangle bounds = combo.getBounds();
Point location = new Point(bounds.x, bounds.y + bounds.height);
location = combo.getParent().toDisplay(location);
menu.setLocation(location.x, location.y);
menu.setVisible(true);
}
-
- private static String getNexusLabel(Device d) {
- String name = d.getName();
- Screen screen = d.getDefaultHardware().getScreen();
- float length = (float) screen.getDiagonalLength();
- return String.format(java.util.Locale.US, "%1$s (%3$s\", %2$s)",
- name, getResolutionString(d), Float.toString(length));
- }
-
- private static String getGenericLabel(Device d) {
- // * Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA)
- // * Use the same precision for all devices (all but one specify decimals)
- // * Add some leading space such that the dot ends up roughly in the
- // same space
- // * Add in screen resolution and density
- String name = d.getName();
- if (name.equals("3.7 FWVGA slider")) {
- // Fix metadata: this one entry doesn't have "in" like the rest of them
- name = "3.7in FWVGA slider";
- }
-
- Matcher matcher = PATTERN.matcher(name);
- if (matcher.matches()) {
- String size = matcher.group(1);
- String n = matcher.group(2);
- int dot = size.indexOf('.');
- if (dot == -1) {
- size = size + ".0";
- dot = size.length() - 2;
- }
- for (int i = 0; i < 2 - dot; i++) {
- size = ' ' + size;
- }
- name = size + "\" " + n;
- }
-
- return String.format(java.util.Locale.US, "%1$s (%2$s)", name,
- getResolutionString(d));
- }
-
- @Nullable
- private static String getResolutionString(Device device) {
- Screen screen = device.getDefaultHardware().getScreen();
- return String.format(java.util.Locale.US,
- "%1$d \u00D7 %2$d: %3$s", // U+00D7: Unicode multiplication sign
- screen.getXDimension(),
- screen.getYDimension(),
- screen.getPixelDensity().getResourceValue());
- }
-
- private static boolean isGeneric(Device device) {
- return device.getManufacturer().equals(GENERIC);
- }
-
- private static boolean isNexus(Device device) {
- return device.getName().contains(NEXUS);
- }
-
- private static void sortNexusList(List<Device> list) {
- Collections.sort(list, new Comparator<Device>() {
- @Override
- public int compare(Device device1, Device device2) {
- // Descending order of age
- return nexusRank(device2) - nexusRank(device1);
- }
- private int nexusRank(Device device) {
- String name = device.getName();
- if (name.endsWith(" One")) { //$NON-NLS-1$
- return 1;
- }
- if (name.endsWith(" S")) { //$NON-NLS-1$
- return 2;
- }
- if (name.startsWith("Galaxy")) { //$NON-NLS-1$
- return 3;
- }
- if (name.endsWith(" 7")) { //$NON-NLS-1$
- return 4;
- }
-
- return 5;
- }
- });
- }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleManager.java
index 0d30011..43c90d9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleManager.java
@@ -487,7 +487,7 @@ public class LocaleManager {
// "gn": Guaraní -> Paraguay
sLanguageToCountry.put("gn", "PY"); //$NON-NLS-1$ //$NON-NLS-2$
- sLanguageNames.put("gn", "Guaraní"); //$NON-NLS-1$
+ sLanguageNames.put("gn", "Guaran\u00ed" /*Guaraní*/); //$NON-NLS-1$
// "gu": Gujarati -> India
sLanguageToCountry.put("gu", "IN"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -708,7 +708,7 @@ public class LocaleManager {
// "nb": Norwegian -> Norway
sLanguageToCountry.put("nb", "NO"); //$NON-NLS-1$ //$NON-NLS-2$
- sLanguageNames.put("nb", "Norwegian Bokmål"); //$NON-NLS-1$
+ sLanguageNames.put("nb", "Norwegian Bokm\u00e5l" /*Norwegian Bokmål*/); //$NON-NLS-1$
// "nd": North Ndebele -> Zimbabwe
sLanguageToCountry.put("nd", "ZW"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -976,7 +976,7 @@ public class LocaleManager {
// "yo": Yorùbá -> Nigeria, Togo, Benin
sLanguageToCountry.put("yo", "NG"); //$NON-NLS-1$ //$NON-NLS-2$
- sLanguageNames.put("yo", "Yorùbá"); //$NON-NLS-1$
+ sLanguageNames.put("yo", "Yor\u00f9b\u00e1" /*Yorùbá*/); //$NON-NLS-1$
// "za": Zhuang -> China
sLanguageToCountry.put("za", "CN"); //$NON-NLS-1$ //$NON-NLS-2$
@@ -1005,7 +1005,7 @@ public class LocaleManager {
sRegionNames.put("AT", "Austria"); //$NON-NLS-1$
sRegionNames.put("AU", "Australia"); //$NON-NLS-1$
sRegionNames.put("AW", "Aruba"); //$NON-NLS-1$
- sRegionNames.put("AX", "Ã…land Islands"); //$NON-NLS-1$
+ sRegionNames.put("AX", "\u00c5land Islands" /*Ã…land Islands*/); //$NON-NLS-1$
sRegionNames.put("AZ", "Azerbaijan"); //$NON-NLS-1$
sRegionNames.put("BA", "Bosnia and Herzegovina"); //$NON-NLS-1$
sRegionNames.put("BB", "Barbados"); //$NON-NLS-1$
@@ -1016,7 +1016,7 @@ public class LocaleManager {
sRegionNames.put("BH", "Bahrain"); //$NON-NLS-1$
sRegionNames.put("BI", "Burundi"); //$NON-NLS-1$
sRegionNames.put("BJ", "Benin"); //$NON-NLS-1$
- sRegionNames.put("BL", "Saint Barthélemy"); //$NON-NLS-1$
+ sRegionNames.put("BL", "Saint Barth\u00e9lemy" /*Saint Barthélemy*/); //$NON-NLS-1$
sRegionNames.put("BM", "Bermuda"); //$NON-NLS-1$
sRegionNames.put("BN", "Brunei Darussalam"); //$NON-NLS-1$
sRegionNames.put("BO", "Bolivia, Plurinational State of"); //$NON-NLS-1$
@@ -1034,7 +1034,7 @@ public class LocaleManager {
sRegionNames.put("CF", "Central African Republic"); //$NON-NLS-1$
sRegionNames.put("CG", "Congo"); //$NON-NLS-1$
sRegionNames.put("CH", "Switzerland"); //$NON-NLS-1$
- sRegionNames.put("CI", "Côte d'Ivoire"); //$NON-NLS-1$
+ sRegionNames.put("CI", "C\u00f4te d'Ivoire" /*Côte d'Ivoire*/); //$NON-NLS-1$
sRegionNames.put("CK", "Cook Islands"); //$NON-NLS-1$
sRegionNames.put("CL", "Chile"); //$NON-NLS-1$
sRegionNames.put("CM", "Cameroon"); //$NON-NLS-1$
@@ -1043,7 +1043,7 @@ public class LocaleManager {
sRegionNames.put("CR", "Costa Rica"); //$NON-NLS-1$
sRegionNames.put("CU", "Cuba"); //$NON-NLS-1$
sRegionNames.put("CV", "Cape Verde"); //$NON-NLS-1$
- sRegionNames.put("CW", "Curaçao"); //$NON-NLS-1$
+ sRegionNames.put("CW", "Cura\u00e7ao" /*Curaçao*/); //$NON-NLS-1$
sRegionNames.put("CX", "Christmas Island"); //$NON-NLS-1$
sRegionNames.put("CY", "Cyprus"); //$NON-NLS-1$
sRegionNames.put("CZ", "Czech Republic"); //$NON-NLS-1$
@@ -1178,7 +1178,7 @@ public class LocaleManager {
sRegionNames.put("PW", "Palau"); //$NON-NLS-1$
sRegionNames.put("PY", "Paraguay"); //$NON-NLS-1$
sRegionNames.put("QA", "Qatar"); //$NON-NLS-1$
- sRegionNames.put("RE", "Réunion"); //$NON-NLS-1$
+ sRegionNames.put("RE", "R\u00e9union" /*Réunion*/); //$NON-NLS-1$
sRegionNames.put("RO", "Romania"); //$NON-NLS-1$
sRegionNames.put("RS", "Serbia"); //$NON-NLS-1$
sRegionNames.put("RU", "Russian Federation"); //$NON-NLS-1$
@@ -1257,7 +1257,7 @@ public class LocaleManager {
// in sLanguageToCountry, since they are either extinct or constructed or
// only in literary use:
sLanguageNames.put("pi", "Pali"); //$NON-NLS-1$
- sLanguageNames.put("vo", "Volapük"); //$NON-NLS-1$
+ sLanguageNames.put("vo", "Volap\u00fck" /*Volapük*/); //$NON-NLS-1$
sLanguageNames.put("eo", "Esperanto"); //$NON-NLS-1$
sLanguageNames.put("la", "Latin"); //$NON-NLS-1$
sLanguageNames.put("ia", "Interlingua"); //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java
index e85f21d..2bc5417 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/LocaleMenuListener.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.AddTranslationDialog;
import org.eclipse.core.resources.IProject;
@@ -97,6 +98,14 @@ class LocaleMenuListener extends SelectionAdapter {
item.addSelectionListener(listener);
}
+ if (locales.size() > 1) {
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ ConfigurationMenuListener.addTogglePreviewModeAction(menu,
+ "Preview All Locales", chooser, RenderPreviewMode.LOCALES);
+ }
+
@SuppressWarnings("unused")
MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java
new file mode 100644
index 0000000..50778e2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/NestedConfiguration.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.resources.NightMode;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.State;
+import com.google.common.base.Objects;
+
+/**
+ * An {@linkplain NestedConfiguration} is a {@link Configuration} which inherits
+ * all of its values from a different configuration, except for one or more
+ * attributes where it overrides a custom value.
+ * <p>
+ * Unlike a {@link VaryingConfiguration}, a {@linkplain NestedConfiguration}
+ * will always return the same overridden value, regardless of the inherited
+ * value.
+ * <p>
+ * For example, an {@linkplain NestedConfiguration} may fix the locale to always
+ * be "en", but otherwise inherit everything else.
+ */
+public class NestedConfiguration extends Configuration {
+ /** The configuration we are inheriting non-overridden values from */
+ protected Configuration mParent;
+
+ /** Bitmask of attributes to be overridden in this configuration */
+ private int mOverride;
+
+ /**
+ * Constructs a new {@linkplain NestedConfiguration}.
+ * Construct via
+ *
+ * @param chooser the associated chooser
+ * @param configuration the configuration to inherit from
+ */
+ protected NestedConfiguration(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration) {
+ super(chooser);
+ mParent = configuration;
+
+ mFullConfig.set(mParent.mFullConfig);
+ if (mParent.getEditedConfig() != null) {
+ mEditedConfig = new FolderConfiguration();
+ mEditedConfig.set(mParent.mEditedConfig);
+ }
+ }
+
+ /**
+ * Returns the override flags for this configuration. Corresponds to
+ * the {@code CFG_} flags in {@link ConfigurationClient}.
+ *
+ * @return the bitmask
+ */
+ public int getOverrideFlags() {
+ return mOverride;
+ }
+
+ /**
+ * Creates a new {@linkplain NestedConfiguration} that has the same overriding
+ * attributes as the given other {@linkplain NestedConfiguration}, and gets
+ * its values from the given {@linkplain Configuration}.
+ *
+ * @param other the configuration to copy overrides from
+ * @param values the configuration to copy values from
+ * @param parent the parent to tie the configuration to for inheriting values
+ * @return a new configuration
+ */
+ @NonNull
+ public static NestedConfiguration create(
+ @NonNull NestedConfiguration other,
+ @NonNull Configuration values,
+ @NonNull Configuration parent) {
+ NestedConfiguration configuration =
+ new NestedConfiguration(other.mConfigChooser, parent);
+ initFrom(configuration, other, values, true /*sync*/);
+ return configuration;
+ }
+
+ /**
+ * Initializes a new {@linkplain NestedConfiguration} with the overriding
+ * attributes as the given other {@linkplain NestedConfiguration}, and gets
+ * its values from the given {@linkplain Configuration}.
+ *
+ * @param configuration the configuration to initialize
+ * @param other the configuration to copy overrides from
+ * @param values the configuration to copy values from
+ * @param sync if true, sync the folder configuration from
+ */
+ protected static void initFrom(NestedConfiguration configuration,
+ NestedConfiguration other, Configuration values, boolean sync) {
+ configuration.mOverride = other.mOverride;
+ configuration.setDisplayName(values.getDisplayName());
+ configuration.setActivity(values.getActivity());
+
+ if (configuration.isOverridingLocale()) {
+ configuration.setLocale(values.getLocale(), true);
+ }
+ if (configuration.isOverridingTarget()) {
+ configuration.setTarget(values.getTarget(), true);
+ }
+ if (configuration.isOverridingDevice()) {
+ configuration.setDevice(values.getDevice(), true);
+ }
+ if (configuration.isOverridingDeviceState()) {
+ configuration.setDeviceState(values.getDeviceState(), true);
+ }
+ if (configuration.isOverridingNightMode()) {
+ configuration.setNightMode(values.getNightMode(), true);
+ }
+ if (configuration.isOverridingUiMode()) {
+ configuration.setUiMode(values.getUiMode(), true);
+ }
+ if (sync) {
+ configuration.syncFolderConfig();
+ }
+ }
+
+ /**
+ * Sets the parent configuration that this configuration is inheriting from.
+ *
+ * @param parent the parent configuration
+ */
+ public void setParent(@NonNull Configuration parent) {
+ mParent = parent;
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} which inherits values from the
+ * given parent {@linkplain Configuration}, possibly overriding some as
+ * well.
+ *
+ * @param chooser the associated chooser
+ * @param parent the configuration to inherit values from
+ * @return a new configuration
+ */
+ @NonNull
+ public static NestedConfiguration create(@NonNull ConfigurationChooser chooser,
+ @NonNull Configuration parent) {
+ return new NestedConfiguration(chooser, parent);
+ }
+
+ @Override
+ @Nullable
+ public String getTheme() {
+ // Never overridden: this is a static attribute of a layout, not something which
+ // varies by configuration or at runtime
+ return mParent.getTheme();
+ }
+
+ @Override
+ public void setTheme(String theme) {
+ // Never overridden
+ mParent.setTheme(theme);
+ }
+
+ /**
+ * Sets whether the locale should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideLocale(boolean override) {
+ mOverride |= CFG_LOCALE;
+ }
+
+ /**
+ * Returns true if the locale is overridden
+ *
+ * @return true if the locale is overridden
+ */
+ public final boolean isOverridingLocale() {
+ return (mOverride & CFG_LOCALE) != 0;
+ }
+
+ @Override
+ @NonNull
+ public Locale getLocale() {
+ if (isOverridingLocale()) {
+ return super.getLocale();
+ } else {
+ return mParent.getLocale();
+ }
+ }
+
+ @Override
+ public void setLocale(@NonNull Locale locale, boolean skipSync) {
+ if (isOverridingLocale()) {
+ super.setLocale(locale, skipSync);
+ } else {
+ mParent.setLocale(locale, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the rendering target should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideTarget(boolean override) {
+ mOverride |= CFG_TARGET;
+ }
+
+ /**
+ * Returns true if the target is overridden
+ *
+ * @return true if the target is overridden
+ */
+ public final boolean isOverridingTarget() {
+ return (mOverride & CFG_TARGET) != 0;
+ }
+
+ @Override
+ @Nullable
+ public IAndroidTarget getTarget() {
+ if (isOverridingTarget()) {
+ return super.getTarget();
+ } else {
+ return mParent.getTarget();
+ }
+ }
+
+ @Override
+ public void setTarget(IAndroidTarget target, boolean skipSync) {
+ if (isOverridingTarget()) {
+ super.setTarget(target, skipSync);
+ } else {
+ mParent.setTarget(target, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the device should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideDevice(boolean override) {
+ mOverride |= CFG_DEVICE;
+ }
+
+ /**
+ * Returns true if the device is overridden
+ *
+ * @return true if the device is overridden
+ */
+ public final boolean isOverridingDevice() {
+ return (mOverride & CFG_DEVICE) != 0;
+ }
+
+ @Override
+ @Nullable
+ public Device getDevice() {
+ if (isOverridingDevice()) {
+ return super.getDevice();
+ } else {
+ return mParent.getDevice();
+ }
+ }
+
+ @Override
+ public void setDevice(Device device, boolean skipSync) {
+ if (isOverridingDevice()) {
+ super.setDevice(device, skipSync);
+ } else {
+ mParent.setDevice(device, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the device state should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideDeviceState(boolean override) {
+ mOverride |= CFG_DEVICE_STATE;
+ }
+
+ /**
+ * Returns true if the device state is overridden
+ *
+ * @return true if the device state is overridden
+ */
+ public final boolean isOverridingDeviceState() {
+ return (mOverride & CFG_DEVICE_STATE) != 0;
+ }
+
+ @Override
+ @Nullable
+ public State getDeviceState() {
+ if (isOverridingDeviceState()) {
+ return super.getDeviceState();
+ } else {
+ State state = mParent.getDeviceState();
+ if (isOverridingDevice()) {
+ // If the device differs, I need to look up a suitable equivalent state
+ // on our device
+ if (state != null) {
+ Device device = super.getDevice();
+ if (device != null) {
+ return device.getState(state.getName());
+ }
+ }
+ }
+
+ return state;
+ }
+ }
+
+ @Override
+ public void setDeviceState(State state, boolean skipSync) {
+ if (isOverridingDeviceState()) {
+ super.setDeviceState(state, skipSync);
+ } else {
+ if (isOverridingDevice()) {
+ Device device = super.getDevice();
+ if (device != null) {
+ State equivalentState = device.getState(state.getName());
+ if (equivalentState != null) {
+ state = equivalentState;
+ }
+ }
+ }
+ mParent.setDeviceState(state, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the night mode should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideNightMode(boolean override) {
+ mOverride |= CFG_NIGHT_MODE;
+ }
+
+ /**
+ * Returns true if the night mode is overridden
+ *
+ * @return true if the night mode is overridden
+ */
+ public final boolean isOverridingNightMode() {
+ return (mOverride & CFG_NIGHT_MODE) != 0;
+ }
+
+ @Override
+ @NonNull
+ public NightMode getNightMode() {
+ if (isOverridingNightMode()) {
+ return super.getNightMode();
+ } else {
+ return mParent.getNightMode();
+ }
+ }
+
+ @Override
+ public void setNightMode(@NonNull NightMode night, boolean skipSync) {
+ if (isOverridingNightMode()) {
+ super.setNightMode(night, skipSync);
+ } else {
+ mParent.setNightMode(night, skipSync);
+ }
+ }
+
+ /**
+ * Sets whether the UI mode should be overridden by this configuration
+ *
+ * @param override if true, override the inherited value
+ */
+ public void setOverrideUiMode(boolean override) {
+ mOverride |= CFG_UI_MODE;
+ }
+
+ /**
+ * Returns true if the UI mode is overridden
+ *
+ * @return true if the UI mode is overridden
+ */
+ public final boolean isOverridingUiMode() {
+ return (mOverride & CFG_UI_MODE) != 0;
+ }
+
+ @Override
+ @NonNull
+ public UiMode getUiMode() {
+ if (isOverridingUiMode()) {
+ return super.getUiMode();
+ } else {
+ return mParent.getUiMode();
+ }
+ }
+
+ @Override
+ public void setUiMode(@NonNull UiMode uiMode, boolean skipSync) {
+ if (isOverridingUiMode()) {
+ super.setUiMode(uiMode, skipSync);
+ } else {
+ mParent.setUiMode(uiMode, skipSync);
+ }
+ }
+
+ /**
+ * Returns the configuration this {@linkplain NestedConfiguration} is
+ * inheriting from
+ *
+ * @return the configuration this configuration is inheriting from
+ */
+ @NonNull
+ public Configuration getParent() {
+ return mParent;
+ }
+
+ @Override
+ @Nullable
+ public String getActivity() {
+ return mParent.getActivity();
+ }
+
+ @Override
+ public void setActivity(String activity) {
+ super.setActivity(activity);
+ }
+
+ /**
+ * Returns a computed display name (ignoring the value stored by
+ * {@link #setDisplayName(String)}) by looking at the override flags
+ * and picking a suitable name.
+ *
+ * @return a suitable display name
+ */
+ @Nullable
+ public String computeDisplayName() {
+ return computeDisplayName(mOverride, this);
+ }
+
+ /**
+ * Computes a display name for the given configuration, using the given
+ * override flags (which correspond to the {@code CFG_} constants in
+ * {@link ConfigurationClient}
+ *
+ * @param flags the override bitmask
+ * @param configuration the configuration to fetch values from
+ * @return a suitable display name
+ */
+ @Nullable
+ public static String computeDisplayName(int flags, @NonNull Configuration configuration) {
+ if ((flags & CFG_LOCALE) != 0) {
+ return ConfigurationChooser.getLocaleLabel(configuration.mConfigChooser,
+ configuration.getLocale(), false);
+ }
+
+ if ((flags & CFG_TARGET) != 0) {
+ return ConfigurationChooser.getRenderingTargetLabel(configuration.getTarget(), false);
+ }
+
+ if ((flags & CFG_DEVICE) != 0) {
+ return ConfigurationChooser.getDeviceLabel(configuration.getDevice(), true);
+ }
+
+ if ((flags & CFG_DEVICE_STATE) != 0) {
+ State deviceState = configuration.getDeviceState();
+ if (deviceState != null) {
+ return deviceState.getName();
+ }
+ }
+
+ if ((flags & CFG_NIGHT_MODE) != 0) {
+ return configuration.getNightMode().getLongDisplayValue();
+ }
+
+ if ((flags & CFG_UI_MODE) != 0) {
+ configuration.getUiMode().getLongDisplayValue();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toStringHelper(this.getClass())
+ .add("parent", mParent.getDisplayName()) //$NON-NLS-1$
+ .add("display", getDisplayName()) //$NON-NLS-1$
+ .add("overrideLocale", isOverridingLocale()) //$NON-NLS-1$
+ .add("overrideTarget", isOverridingTarget()) //$NON-NLS-1$
+ .add("overrideDevice", isOverridingDevice()) //$NON-NLS-1$
+ .add("overrideDeviceState", isOverridingDeviceState()) //$NON-NLS-1$
+ .add("persistent", toPersistentString()) //$NON-NLS-1$
+ .toString();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java
index cae6596..71905f7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/TargetMenuListener.java
@@ -18,6 +18,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import org.eclipse.swt.SWT;
@@ -30,6 +32,7 @@ import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ToolItem;
import java.util.List;
+import java.util.RandomAccess;
/**
* The {@linkplain TargetMenuListener} class is responsible for
@@ -38,17 +41,36 @@ import java.util.List;
class TargetMenuListener extends SelectionAdapter {
private final ConfigurationChooser mConfigChooser;
private final IAndroidTarget mTarget;
+ private final boolean mPickBest;
TargetMenuListener(
@NonNull ConfigurationChooser configChooser,
- @Nullable IAndroidTarget target) {
+ @Nullable IAndroidTarget target,
+ boolean pickBest) {
mConfigChooser = configChooser;
mTarget = target;
+ mPickBest = pickBest;
}
@Override
public void widgetSelected(SelectionEvent e) {
- mConfigChooser.selectTarget(mTarget);
+ IAndroidTarget target = mTarget;
+ AdtPrefs prefs = AdtPrefs.getPrefs();
+ if (mPickBest) {
+ boolean autoPick = prefs.isAutoPickRenderTarget();
+ autoPick = !autoPick;
+ prefs.setAutoPickRenderTarget(autoPick);
+ if (autoPick) {
+ target = ConfigurationMatcher.findDefaultRenderTarget(mConfigChooser);
+ } else {
+ // Turn it off, but keep current target until another one is chosen
+ return;
+ }
+ } else {
+ // Manually picked some other target: turn off auto-pick
+ prefs.setAutoPickRenderTarget(false);
+ }
+ mConfigChooser.selectTarget(target);
mConfigChooser.onRenderingTargetChange();
}
@@ -57,8 +79,32 @@ class TargetMenuListener extends SelectionAdapter {
Configuration configuration = chooser.getConfiguration();
IAndroidTarget current = configuration.getTarget();
List<IAndroidTarget> targets = chooser.getTargetList();
+ boolean haveRecent = false;
+
+ MenuItem menuItem = new MenuItem(menu, SWT.CHECK);
+ menuItem.setText("Automatically Pick Best");
+ menuItem.addSelectionListener(new TargetMenuListener(chooser, null, true));
+ if (AdtPrefs.getPrefs().isAutoPickRenderTarget()) {
+ menuItem.setSelection(true);
+ }
+
+ @SuppressWarnings("unused")
+ MenuItem separator = new MenuItem(menu, SWT.SEPARATOR);
+
+ // Process in reverse order: most important targets first
+ assert targets instanceof RandomAccess;
+ for (int i = targets.size() - 1; i >= 0; i--) {
+ IAndroidTarget target = targets.get(i);
+
+ AndroidVersion version = target.getVersion();
+ if (version.getApiLevel() >= 7) {
+ haveRecent = true;
+ } else if (haveRecent) {
+ // Don't show ancient rendering targets; they're pretty broken
+ // (unless of course all you have are ancient targets)
+ break;
+ }
- for (final IAndroidTarget target : targets) {
String title = ConfigurationChooser.getRenderingTargetLabel(target, false);
MenuItem item = new MenuItem(menu, SWT.CHECK);
item.setText(title);
@@ -68,7 +114,7 @@ class TargetMenuListener extends SelectionAdapter {
item.setSelection(true);
}
- item.addSelectionListener(new TargetMenuListener(chooser, target));
+ item.addSelectionListener(new TargetMenuListener(chooser, target, false));
}
Rectangle bounds = combo.getBounds();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
index 239f396..0f6c9eb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -106,7 +106,7 @@ class ThemeMenuAction extends SubmenuAction {
manager.add(new Separator());
}
- String preferred = configChooser.computePreferredTheme();
+ String preferred = configuration.computePreferredTheme();
if (preferred != null && !preferred.equals(currentTheme)) {
manager.add(new SelectThemeAction(configChooser,
ResourceHelper.styleToTheme(preferred),
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java
new file mode 100644
index 0000000..c6e6407
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/VaryingConfiguration.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.configuration;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.resources.Density;
+import com.android.resources.NightMode;
+import com.android.resources.UiMode;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Hardware;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+
+import java.util.List;
+
+/**
+ * An {@linkplain VaryingConfiguration} is a {@link Configuration} which
+ * inherits all of its values from a different configuration, except for one or
+ * more attributes where it overrides a custom value, and the overridden value
+ * will always <b>differ</b> from the inherited value!
+ * <p>
+ * For example, a {@linkplain VaryingConfiguration} may state that it
+ * overrides the locale, and if the inherited locale is "en", then the returned
+ * locale from the {@linkplain VaryingConfiguration} may be for example "nb",
+ * but never "en".
+ * <p>
+ * The configuration will attempt to make its changed inherited value to be as
+ * different as possible from the inherited value. Thus, a configuration which
+ * overrides the device will probably return a phone-sized screen if the
+ * inherited device is a tablet, or vice versa.
+ */
+public class VaryingConfiguration extends NestedConfiguration {
+ /** Variation version; see {@link #setVariation(int)} */
+ private int mVariation;
+
+ /** Variation version count; see {@link #setVariationCount(int)} */
+ private int mVariationCount;
+
+ /** Bitmask of attributes to be varied/alternated from the parent */
+ private int mAlternate;
+
+ /**
+ * Constructs a new {@linkplain VaryingConfiguration}.
+ * Construct via
+ *
+ * @param chooser the associated chooser
+ * @param configuration the configuration to inherit from
+ */
+ private VaryingConfiguration(
+ @NonNull ConfigurationChooser chooser,
+ @NonNull Configuration configuration) {
+ super(chooser, configuration);
+ }
+
+ /**
+ * Creates a new {@linkplain Configuration} which inherits values from the
+ * given parent {@linkplain Configuration}, possibly overriding some as
+ * well.
+ *
+ * @param chooser the associated chooser
+ * @param parent the configuration to inherit values from
+ * @return a new configuration
+ */
+ @NonNull
+ public static VaryingConfiguration create(@NonNull ConfigurationChooser chooser,
+ @NonNull Configuration parent) {
+ return new VaryingConfiguration(chooser, parent);
+ }
+
+ /**
+ * Creates a new {@linkplain VaryingConfiguration} that has the same overriding
+ * attributes as the given other {@linkplain VaryingConfiguration}.
+ *
+ * @param other the configuration to copy overrides from
+ * @param parent the parent to tie the configuration to for inheriting values
+ * @return a new configuration
+ */
+ @NonNull
+ public static VaryingConfiguration create(
+ @NonNull VaryingConfiguration other,
+ @NonNull Configuration parent) {
+ VaryingConfiguration configuration =
+ new VaryingConfiguration(other.mConfigChooser, parent);
+ initFrom(configuration, other, other, false);
+ configuration.mAlternate = other.mAlternate;
+ configuration.mVariation = other.mVariation;
+ configuration.mVariationCount = other.mVariationCount;
+ configuration.syncFolderConfig();
+
+ return configuration;
+ }
+
+ /**
+ * Returns the alternate flags for this configuration. Corresponds to
+ * the {@code CFG_} flags in {@link ConfigurationClient}.
+ *
+ * @return the bitmask
+ */
+ public int getAlternateFlags() {
+ return mAlternate;
+ }
+
+ @Override
+ public void syncFolderConfig() {
+ super.syncFolderConfig();
+ updateDisplayName();
+ }
+
+ /**
+ * Sets the variation version for this
+ * {@linkplain VaryingConfiguration}. There might be multiple
+ * {@linkplain VaryingConfiguration} instances inheriting from a
+ * {@link Configuration}. The variation version allows them to choose
+ * different complementing values, so they don't all flip to the same other
+ * (out of multiple choices) value. The {@link #setVariationCount(int)}
+ * value can be used to determine how to partition the buckets of values.
+ * Also updates the variation count if necessary.
+ *
+ * @param variation variation version
+ */
+ public void setVariation(int variation) {
+ mVariation = variation;
+ mVariationCount = Math.max(mVariationCount, variation + 1);
+ }
+
+ /**
+ * Sets the number of {@link VaryingConfiguration} variations mapped
+ * to the same parent configuration as this one. See
+ * {@link #setVariation(int)} for details.
+ *
+ * @param count the total number of variation versions
+ */
+ public void setVariationCount(int count) {
+ mVariationCount = count;
+ }
+
+ /**
+ * Updates the display name in this configuration based on the values and override settings
+ */
+ public void updateDisplayName() {
+ setDisplayName(computeDisplayName());
+ }
+
+ @Override
+ @NonNull
+ public Locale getLocale() {
+ if (isOverridingLocale()) {
+ return super.getLocale();
+ }
+ Locale locale = mParent.getLocale();
+ if (isAlternatingLocale() && locale != null) {
+ List<Locale> locales = mConfigChooser.getLocaleList();
+ for (Locale l : locales) {
+ // TODO: Try to be smarter about which one we pick; for example, try
+ // to pick a language that is substantially different from the inherited
+ // language, such as either with the strings of the largest or shortest
+ // length, or perhaps based on some geography or population metrics
+ if (!l.equals(locale)) {
+ locale = l;
+ break;
+ }
+ }
+ }
+
+ return locale;
+ }
+
+ @Override
+ @Nullable
+ public IAndroidTarget getTarget() {
+ if (isOverridingTarget()) {
+ return super.getTarget();
+ }
+ IAndroidTarget target = mParent.getTarget();
+ if (isAlternatingTarget() && target != null) {
+ List<IAndroidTarget> targets = mConfigChooser.getTargetList();
+ if (!targets.isEmpty()) {
+ // Pick a different target: if you're showing the most recent render target,
+ // then pick the lowest supported target, and vice versa
+ IAndroidTarget mostRecent = targets.get(targets.size() - 1);
+ if (target.equals(mostRecent)) {
+ // Find oldest supported
+ ManifestInfo info = ManifestInfo.get(mConfigChooser.getProject());
+ int minSdkVersion = info.getMinSdkVersion();
+ for (IAndroidTarget t : targets) {
+ if (t.getVersion().getApiLevel() >= minSdkVersion) {
+ target = t;
+ break;
+ }
+ }
+ } else {
+ target = mostRecent;
+ }
+ }
+ }
+
+ return target;
+ }
+
+ // Cached values, key=parent's device, cached value=device
+ private Device mPrevParentDevice;
+ private Device mPrevDevice;
+
+ @Override
+ @Nullable
+ public Device getDevice() {
+ if (isOverridingDevice()) {
+ return super.getDevice();
+ }
+ Device device = mParent.getDevice();
+ if (isAlternatingDevice() && device != null) {
+ if (device == mPrevParentDevice) {
+ return mPrevDevice;
+ }
+
+ mPrevParentDevice = device;
+
+ // Pick a different device
+ List<Device> devices = mConfigChooser.getDeviceList();
+
+ // Divide up the available devices into {@link #mVariationCount} + 1 buckets
+ // (the + 1 is for the bucket now taken up by the inherited value).
+ // Then assign buckets to each {@link #mVariation} version, and pick one
+ // from the bucket assigned to this current configuration's variation version.
+
+ // I could just divide up the device list count, but that would treat a lot of
+ // very similar phones as having the same kind of variety as the 7" and 10"
+ // tablets which are sitting right next to each other in the device list.
+ // Instead, do this by screen size.
+
+
+ double smallest = 100;
+ double biggest = 1;
+ for (Device d : devices) {
+ double size = getScreenSize(d);
+ if (size < 0) {
+ continue; // no data
+ }
+ if (size >= biggest) {
+ biggest = size;
+ }
+ if (size <= smallest) {
+ smallest = size;
+ }
+ }
+
+ int bucketCount = mVariationCount + 1;
+ double inchesPerBucket = (biggest - smallest) / bucketCount;
+
+ double overriddenSize = getScreenSize(device);
+ int overriddenBucket = (int) ((overriddenSize - smallest) / inchesPerBucket);
+ int bucket = (mVariation < overriddenBucket) ? mVariation : mVariation + 1;
+ double from = inchesPerBucket * bucket + smallest;
+ double to = from + inchesPerBucket;
+ if (biggest - to < 0.1) {
+ to = biggest + 0.1;
+ }
+
+ boolean canScaleNinePatch = supports(Capability.FIXED_SCALABLE_NINE_PATCH);
+ for (Device d : devices) {
+ double size = getScreenSize(d);
+ if (size >= from && size < to) {
+ if (!canScaleNinePatch) {
+ Density density = getDensity(d);
+ if (density == Density.TV || density == Density.LOW) {
+ continue;
+ }
+ }
+
+ device = d;
+ break;
+ }
+ }
+
+ mPrevDevice = device;
+ }
+
+ return device;
+ }
+
+ /**
+ * Returns the density of the given device
+ *
+ * @param device the device to check
+ * @return the density or null
+ */
+ @Nullable
+ private static Density getDensity(@NonNull Device device) {
+ Hardware hardware = device.getDefaultHardware();
+ if (hardware != null) {
+ Screen screen = hardware.getScreen();
+ if (screen != null) {
+ return screen.getPixelDensity();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the diagonal length of the given device
+ *
+ * @param device the device to check
+ * @return the diagonal length or -1
+ */
+ private static double getScreenSize(@NonNull Device device) {
+ Hardware hardware = device.getDefaultHardware();
+ if (hardware != null) {
+ Screen screen = hardware.getScreen();
+ if (screen != null) {
+ return screen.getDiagonalLength();
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ @Nullable
+ public State getDeviceState() {
+ if (isOverridingDeviceState()) {
+ return super.getDeviceState();
+ }
+ State state = mParent.getDeviceState();
+ if (isAlternatingDeviceState() && state != null) {
+ State alternate = getNextDeviceState(state);
+
+ return alternate;
+ } else {
+ if ((isAlternatingDevice() || isOverridingDevice()) && state != null) {
+ // If the device differs, I need to look up a suitable equivalent state
+ // on our device
+ Device device = getDevice();
+ if (device != null) {
+ return device.getState(state.getName());
+ }
+ }
+
+ return state;
+ }
+ }
+
+ @Override
+ @NonNull
+ public NightMode getNightMode() {
+ if (isOverridingNightMode()) {
+ return super.getNightMode();
+ }
+ NightMode nightMode = mParent.getNightMode();
+ if (isAlternatingNightMode() && nightMode != null) {
+ nightMode = nightMode == NightMode.NIGHT ? NightMode.NOTNIGHT : NightMode.NIGHT;
+ return nightMode;
+ } else {
+ return nightMode;
+ }
+ }
+
+ @Override
+ @NonNull
+ public UiMode getUiMode() {
+ if (isOverridingUiMode()) {
+ return super.getUiMode();
+ }
+ UiMode uiMode = mParent.getUiMode();
+ if (isAlternatingUiMode() && uiMode != null) {
+ // TODO: Use manifest's supports screen to decide which are most relevant
+ // (as well as which available configuration qualifiers are present in the
+ // layout)
+ UiMode[] values = UiMode.values();
+ uiMode = values[(uiMode.ordinal() + 1) % values.length];
+ return uiMode;
+ } else {
+ return uiMode;
+ }
+ }
+
+ @Override
+ @Nullable
+ public String computeDisplayName() {
+ return computeDisplayName(getOverrideFlags() | mAlternate, this);
+ }
+
+ /**
+ * Sets whether the locale should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateLocale(boolean alternate) {
+ mAlternate |= CFG_LOCALE;
+ }
+
+ /**
+ * Returns true if the locale is alternated
+ *
+ * @return true if the locale is alternated
+ */
+ public final boolean isAlternatingLocale() {
+ return (mAlternate & CFG_LOCALE) != 0;
+ }
+
+ /**
+ * Sets whether the rendering target should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateTarget(boolean alternate) {
+ mAlternate |= CFG_TARGET;
+ }
+
+ /**
+ * Returns true if the target is alternated
+ *
+ * @return true if the target is alternated
+ */
+ public final boolean isAlternatingTarget() {
+ return (mAlternate & CFG_TARGET) != 0;
+ }
+
+ /**
+ * Sets whether the device should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateDevice(boolean alternate) {
+ mAlternate |= CFG_DEVICE;
+ }
+
+ /**
+ * Returns true if the device is alternated
+ *
+ * @return true if the device is alternated
+ */
+ public final boolean isAlternatingDevice() {
+ return (mAlternate & CFG_DEVICE) != 0;
+ }
+
+ /**
+ * Sets whether the device state should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateDeviceState(boolean alternate) {
+ mAlternate |= CFG_DEVICE_STATE;
+ }
+
+ /**
+ * Returns true if the device state is alternated
+ *
+ * @return true if the device state is alternated
+ */
+ public final boolean isAlternatingDeviceState() {
+ return (mAlternate & CFG_DEVICE_STATE) != 0;
+ }
+
+ /**
+ * Sets whether the night mode should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateNightMode(boolean alternate) {
+ mAlternate |= CFG_NIGHT_MODE;
+ }
+
+ /**
+ * Returns true if the night mode is alternated
+ *
+ * @return true if the night mode is alternated
+ */
+ public final boolean isAlternatingNightMode() {
+ return (mAlternate & CFG_NIGHT_MODE) != 0;
+ }
+
+ /**
+ * Sets whether the UI mode should be alternated by this configuration
+ *
+ * @param alternate if true, alternate the inherited value
+ */
+ public void setAlternateUiMode(boolean alternate) {
+ mAlternate |= CFG_UI_MODE;
+ }
+
+ /**
+ * Returns true if the UI mode is alternated
+ *
+ * @return true if the UI mode is alternated
+ */
+ public final boolean isAlternatingUiMode() {
+ return (mAlternate & CFG_UI_MODE) != 0;
+ }
+
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
index 15dc356..7b2fe84 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/LayoutDescriptors.java
@@ -21,11 +21,13 @@ import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_TAG;
+import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.FQCN_GESTURE_OVERLAY_VIEW;
import static com.android.SdkConstants.REQUEST_FOCUS;
import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;
import static com.android.SdkConstants.VIEW_MERGE;
+import static com.android.SdkConstants.VIEW_TAG;
import com.android.SdkConstants;
import com.android.ide.common.api.IAttributeInfo.Format;
@@ -168,6 +170,14 @@ public final class LayoutDescriptors implements IDescriptorProvider {
newDescriptors.addAll(newLayouts);
newDescriptors.addAll(newViews);
+ ViewElementDescriptor viewTag = createViewTag(frameLayoutAttrs);
+ newViews.add(viewTag);
+ newDescriptors.add(viewTag);
+
+ ViewElementDescriptor requestFocus = createRequestFocus();
+ newViews.add(requestFocus);
+ newDescriptors.add(requestFocus);
+
// Link all layouts to everything else here.. recursively
for (ViewElementDescriptor layoutDesc : newLayouts) {
layoutDesc.setChildren(newDescriptors);
@@ -184,10 +194,6 @@ public final class LayoutDescriptors implements IDescriptorProvider {
fixSuperClasses(infoDescMap);
- ViewElementDescriptor requestFocus = createRequestFocus();
- newViews.add(requestFocus);
- newDescriptors.add(requestFocus);
-
// The <merge> tag can only be a root tag, so it is added at the end.
// It gets everything else as children but it is not made a child itself.
ViewElementDescriptor mergeTag = createMerge(frameLayoutAttrs);
@@ -305,7 +311,7 @@ public final class LayoutDescriptors implements IDescriptorProvider {
}
/**
- * Creates a new <include> descriptor and adds it to the list of view descriptors.
+ * Creates a new {@code <include>} descriptor and adds it to the list of view descriptors.
*
* @param knownViews A list of view descriptors being populated. Also used to find the
* View descriptor and extract its layout attributes.
@@ -316,6 +322,18 @@ public final class LayoutDescriptors implements IDescriptorProvider {
// Create the include custom attributes
ArrayList<AttributeDescriptor> attributes = new ArrayList<AttributeDescriptor>();
+ // Find View and inherit all its layout attributes
+ AttributeDescriptor[] viewLayoutAttribs;
+ AttributeDescriptor[] viewAttributes = null;
+ ViewElementDescriptor viewDesc = findDescriptorByClass(SdkConstants.CLASS_VIEW);
+ if (viewDesc != null) {
+ viewAttributes = viewDesc.getAttributes();
+ attributes = new ArrayList<AttributeDescriptor>(viewAttributes.length + 1);
+ viewLayoutAttribs = viewDesc.getLayoutAttributes();
+ } else {
+ viewLayoutAttribs = new AttributeDescriptor[0];
+ }
+
// Note that the "layout" attribute does NOT have the Android namespace
DescriptorsUtils.appendAttribute(attributes,
null, //elementXmlName
@@ -326,18 +344,11 @@ public final class LayoutDescriptors implements IDescriptorProvider {
true, //required
null); //overrides
- DescriptorsUtils.appendAttribute(attributes,
- null, //elementXmlName
- ANDROID_URI, //nsUri
- new AttributeInfo(
- "id", //$NON-NLS-1$
- Format.REFERENCE_SET ),
- true, //required
- null); //overrides
-
- // Find View and inherit all its layout attributes
- AttributeDescriptor[] viewLayoutAttribs = findViewLayoutAttributes(
- SdkConstants.CLASS_VIEW);
+ if (viewAttributes != null) {
+ for (AttributeDescriptor descriptor : viewAttributes) {
+ attributes.add(descriptor);
+ }
+ }
// Create the include descriptor
ViewElementDescriptor desc = new ViewElementDescriptor(xmlName,
@@ -451,6 +462,37 @@ public final class LayoutDescriptors implements IDescriptorProvider {
}
/**
+ * Creates and returns a new {@code <view>} descriptor.
+ * @param viewLayoutAttribs The layout attributes to use for the new descriptor
+ * @param styleMap The style map provided by the SDK
+ */
+ private ViewElementDescriptor createViewTag(AttributeDescriptor[] viewLayoutAttribs) {
+ String xmlName = VIEW_TAG;
+
+ TextAttributeDescriptor classAttribute = new ClassAttributeDescriptor(
+ CLASS_VIEW,
+ ATTR_CLASS, null /* namespace */,
+ new AttributeInfo(ATTR_CLASS, Format.STRING_SET),
+ true /*mandatory*/)
+ .setTooltip("Supply the name of the view class to instantiate");
+
+ // Create the include descriptor
+ ViewElementDescriptor desc = new ViewElementDescriptor(xmlName,
+ xmlName, // ui_name
+ xmlName, // "class name"; the GLE only treats this as an element tag
+ "A view tag whose class attribute names the class to be instantiated", // tooltip
+ null, // sdk_url
+ new AttributeDescriptor[] { // attributes
+ classAttribute
+ },
+ viewLayoutAttribs, // layout attributes
+ null, // children
+ false /* mandatory */);
+
+ return desc;
+ }
+
+ /**
* Creates and returns a new {@code <requestFocus>} descriptor.
*/
private ViewElementDescriptor createRequestFocus() {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
index 466720a..7999524 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/ViewElementDescriptor.java
@@ -19,6 +19,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
import static com.android.SdkConstants.ANDROID_VIEW_PKG;
import static com.android.SdkConstants.ANDROID_WEBKIT_PKG;
import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
+import static com.android.SdkConstants.VIEW;
+import static com.android.SdkConstants.VIEW_TAG;
import com.android.ide.common.resources.platform.AttributeInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -188,6 +190,10 @@ public class ViewElementDescriptor extends ElementDescriptor {
// "android.gesture.GestureOverlayView" in their XML, we need to look up
// only by basename
name = name.substring(name.lastIndexOf('.') + 1);
+ } else if (VIEW_TAG.equals(name)) {
+ // Can't have both view.png and View.png; issues on case sensitive vs
+ // case insensitive file systems
+ name = VIEW;
}
Image icon = factory.getIcon(name);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java
new file mode 100644
index 0000000..9fc2e09
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/BinPacker.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.Rect;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+/**
+ * This class implements 2D bin packing: packing rectangles into a given area as
+ * tightly as "possible" (bin packing in general is NP hard, so this class uses
+ * heuristics).
+ * <p>
+ * The algorithm implemented is to keep a set of (possibly overlapping)
+ * available areas for placement. For each newly inserted rectangle, we first
+ * pick which available space to occupy, and we then subdivide the
+ * current rectangle into all the possible remaining unoccupied sub-rectangles.
+ * We also remove any other space rectangles which are no longer eligible if
+ * they are intersecting the newly placed rectangle.
+ * <p>
+ * This algorithm is not very fast, so should not be used for a large number of
+ * rectangles.
+ */
+class BinPacker {
+ /**
+ * When enabled, the successive passes are dumped as PNG images showing the
+ * various available and occupied rectangles)
+ */
+ private static final boolean DEBUG = false;
+
+ private final List<Rect> mSpace = new ArrayList<Rect>();
+ private final int mMinHeight;
+ private final int mMinWidth;
+
+ /**
+ * Creates a new {@linkplain BinPacker}. To use it, first add one or more
+ * initial available space rectangles with {@link #addSpace(Rect)}, and then
+ * place the rectangles with {@link #occupy(int, int)}. The returned
+ * {@link Rect} from {@link #occupy(int, int)} gives the coordinates of the
+ * positioned rectangle.
+ *
+ * @param minWidth the smallest width of any rectangle placed into this bin
+ * @param minHeight the smallest height of any rectangle placed into this bin
+ */
+ BinPacker(int minWidth, int minHeight) {
+ mMinWidth = minWidth;
+ mMinHeight = minHeight;
+
+ if (DEBUG) {
+ mAllocated = new ArrayList<Rect>();
+ sLayoutId++;
+ sRectId = 1;
+ }
+ }
+
+ /** Adds more available space */
+ void addSpace(Rect rect) {
+ if (rect.w >= mMinWidth && rect.h >= mMinHeight) {
+ mSpace.add(rect);
+ }
+ }
+
+ /** Attempts to place a rectangle of the given dimensions, if possible */
+ @Nullable
+ Rect occupy(int width, int height) {
+ int index = findBest(width, height);
+ if (index == -1) {
+ return null;
+ }
+
+ return split(index, width, height);
+ }
+
+ /**
+ * Finds the best available space rectangle to position a new rectangle of
+ * the given size in.
+ */
+ private int findBest(int width, int height) {
+ if (mSpace.isEmpty()) {
+ return -1;
+ }
+
+ // Try to pack as far up as possible first
+ int bestIndex = -1;
+ boolean multipleAtSameY = false;
+ int minY = Integer.MAX_VALUE;
+ for (int i = 0, n = mSpace.size(); i < n; i++) {
+ Rect rect = mSpace.get(i);
+ if (rect.y <= minY) {
+ if (rect.w >= width && rect.h >= height) {
+ if (rect.y < minY) {
+ minY = rect.y;
+ multipleAtSameY = false;
+ bestIndex = i;
+ } else if (minY == rect.y) {
+ multipleAtSameY = true;
+ }
+ }
+ }
+ }
+
+ if (!multipleAtSameY) {
+ return bestIndex;
+ }
+
+ bestIndex = -1;
+
+ // Pick a rectangle. This currently tries to find the rectangle whose shortest
+ // side most closely matches the placed rectangle's size.
+ // Attempt to find the best short side fit
+ int bestShortDistance = Integer.MAX_VALUE;
+ int bestLongDistance = Integer.MAX_VALUE;
+
+ for (int i = 0, n = mSpace.size(); i < n; i++) {
+ Rect rect = mSpace.get(i);
+ if (rect.y != minY) { // Only comparing elements at same y
+ continue;
+ }
+ if (rect.w >= width && rect.h >= height) {
+ if (width < height) {
+ int distance = rect.w - width;
+ if (distance < bestShortDistance ||
+ distance == bestShortDistance &&
+ (rect.h - height) < bestLongDistance) {
+ bestShortDistance = distance;
+ bestLongDistance = rect.h - height;
+ bestIndex = i;
+ }
+ } else {
+ int distance = rect.w - width;
+ if (distance < bestShortDistance ||
+ distance == bestShortDistance &&
+ (rect.h - height) < bestLongDistance) {
+ bestShortDistance = distance;
+ bestLongDistance = rect.h - height;
+ bestIndex = i;
+ }
+ }
+ }
+ }
+
+ return bestIndex;
+ }
+
+ /**
+ * Removes the rectangle at the given index. Since the rectangles are in an
+ * {@link ArrayList}, removing a rectangle in the normal way is slow (it
+ * would involve shifting all elements), but since we don't care about
+ * order, this always swaps the to-be-deleted element to the last position
+ * in the array first, <b>then</b> it deletes it (which should be
+ * immediate).
+ *
+ * @param index the index in the {@link #mSpace} list to remove a rectangle
+ * from
+ */
+ private void removeRect(int index) {
+ assert !mSpace.isEmpty();
+ int lastIndex = mSpace.size() - 1;
+ if (index != lastIndex) {
+ // Swap before remove to make deletion faster since we don't
+ // care about order
+ Rect temp = mSpace.get(index);
+ mSpace.set(index, mSpace.get(lastIndex));
+ mSpace.set(lastIndex, temp);
+ }
+
+ mSpace.remove(lastIndex);
+ }
+
+ /**
+ * Splits the rectangle at the given rectangle index such that it can contain
+ * a rectangle of the given width and height. */
+ private Rect split(int index, int width, int height) {
+ Rect rect = mSpace.get(index);
+ assert rect.w >= width && rect.h >= height : rect;
+
+ Rect r = new Rect(rect);
+ r.w = width;
+ r.h = height;
+
+ // Remove all rectangles that intersect my rectangle
+ for (int i = 0; i < mSpace.size(); i++) {
+ Rect other = mSpace.get(i);
+ if (other.intersects(r)) {
+ removeRect(i);
+ i--;
+ }
+ }
+
+
+ // Split along vertical line x = rect.x + width:
+ // (rect.x,rect.y)
+ // +-------------+-------------------------+
+ // | | |
+ // | | |
+ // | | height |
+ // | | |
+ // | | |
+ // +-------------+ B | rect.h
+ // | width |
+ // | | |
+ // | A |
+ // | | |
+ // | |
+ // +---------------------------------------+
+ // rect.w
+ int remainingHeight = rect.h - height;
+ int remainingWidth = rect.w - width;
+ if (remainingHeight >= mMinHeight) {
+ mSpace.add(new Rect(rect.x, rect.y + height, width, remainingHeight));
+ }
+ if (remainingWidth >= mMinWidth) {
+ mSpace.add(new Rect(rect.x + width, rect.y, remainingWidth, rect.h));
+ }
+
+ // Split along horizontal line y = rect.y + height:
+ // +-------------+-------------------------+
+ // | | |
+ // | | height |
+ // | | A |
+ // | | |
+ // | | | rect.h
+ // +-------------+ - - - - - - - - - - - - |
+ // | width |
+ // | |
+ // | B |
+ // | |
+ // | |
+ // +---------------------------------------+
+ // rect.w
+ if (remainingHeight >= mMinHeight) {
+ mSpace.add(new Rect(rect.x, rect.y + height, rect.w, remainingHeight));
+ }
+ if (remainingWidth >= mMinWidth) {
+ mSpace.add(new Rect(rect.x + width, rect.y, remainingWidth, height));
+ }
+
+ // Remove redundant rectangles. This is not very efficient.
+ for (int i = 0; i < mSpace.size() - 1; i++) {
+ for (int j = i + 1; j < mSpace.size(); j++) {
+ Rect iRect = mSpace.get(i);
+ Rect jRect = mSpace.get(j);
+ if (jRect.contains(iRect)) {
+ removeRect(i);
+ i--;
+ break;
+ }
+ if (iRect.contains(jRect)) {
+ removeRect(j);
+ j--;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ mAllocated.add(r);
+ dumpImage();
+ }
+
+ return r;
+ }
+
+ // DEBUGGING CODE: Enable with DEBUG
+
+ private List<Rect> mAllocated;
+ private static int sLayoutId;
+ private static int sRectId;
+ private void dumpImage() {
+ if (DEBUG) {
+ int width = 100;
+ int height = 100;
+ for (Rect rect : mSpace) {
+ width = Math.max(width, rect.w);
+ height = Math.max(height, rect.h);
+ }
+ width += 10;
+ height += 10;
+
+ BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = image.createGraphics();
+ g.setColor(Color.BLACK);
+ g.fillRect(0, 0, image.getWidth(), image.getHeight());
+
+ Color[] colors = new Color[] {
+ Color.blue, Color.cyan,
+ Color.green, Color.magenta, Color.orange,
+ Color.pink, Color.red, Color.white, Color.yellow, Color.darkGray,
+ Color.lightGray, Color.gray,
+ };
+
+ char allocated = 'A';
+ for (Rect rect : mAllocated) {
+ Color color = new Color(0x9FFFFFFF, true);
+ g.setColor(color);
+ g.setBackground(color);
+ g.fillRect(rect.x, rect.y, rect.w, rect.h);
+ g.setColor(Color.WHITE);
+ g.drawRect(rect.x, rect.y, rect.w, rect.h);
+ g.drawString("" + (allocated++),
+ rect.x + rect.w / 2, rect.y + rect.h / 2);
+ }
+
+ int colorIndex = 0;
+ for (Rect rect : mSpace) {
+ Color color = colors[colorIndex];
+ colorIndex = (colorIndex + 1) % colors.length;
+
+ color = new Color(color.getRed(), color.getGreen(), color.getBlue(), 128);
+ g.setColor(color);
+
+ g.fillRect(rect.x, rect.y, rect.w, rect.h);
+ g.setColor(Color.WHITE);
+ g.drawString(Integer.toString(colorIndex),
+ rect.x + rect.w / 2, rect.y + rect.h / 2);
+ }
+
+
+ g.dispose();
+
+ File file = new File("/tmp/layout" + sLayoutId + "_pass" + sRectId + ".png");
+ try {
+ ImageIO.write(image, "PNG", file);
+ System.out.println("Wrote diagnostics image " + file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ sRectId++;
+ }
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
index 5650772..ad5bd52 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
@@ -40,6 +40,9 @@ public class CanvasTransform {
/** Canvas image size (original, before zoom), in pixels. */
private int mImgSize;
+ /** Full size being scrolled (after zoom), in pixels */
+ private int mFullSize;;
+
/** Client size, in pixels. */
private int mClientSize;
@@ -83,6 +86,11 @@ public class CanvasTransform {
}
}
+ /** Recomputes the scrollbar and view port settings */
+ public void refresh() {
+ resizeScrollbar();
+ }
+
/**
* Returns current scaling factor.
*
@@ -110,14 +118,17 @@ public class CanvasTransform {
return (int) (mImgSize * mScale);
}
- /** Changes the size of the canvas image and the client size. Recomputes scrollbars. */
- public void setSize(int imgSize, int clientSize) {
+ /**
+ * Changes the size of the canvas image and the client size. Recomputes
+ * scrollbars.
+ *
+ * @param imgSize the size of the image being scaled
+ * @param fullSize the size of the full view area being scrolled
+ * @param clientSize the size of the view port
+ */
+ public void setSize(int imgSize, int fullSize, int clientSize) {
mImgSize = imgSize;
- setClientSize(clientSize);
- }
-
- /** Changes the size of the client size. Recomputes scrollbars. */
- public void setClientSize(int clientSize) {
+ mFullSize = fullSize;
mClientSize = clientSize;
mScrollbar.setPageIncrement(clientSize);
resizeScrollbar();
@@ -125,7 +136,7 @@ public class CanvasTransform {
private void resizeScrollbar() {
// scaled image size
- int sx = (int) (mImgSize * mScale);
+ int sx = (int) (mScale * mFullSize);
// Adjust margin such that for zoomed out views
// we don't waste space (unless the viewport is
@@ -150,6 +161,11 @@ public class CanvasTransform {
mMargin = DEFAULT_MARGIN;
}
+ if (mCanvas.getPreviewManager().hasPreviews()) {
+ // Make more room for the previews
+ mMargin = 2;
+ }
+
// actual client area is always reduced by the margins
int cx = mClientSize - 2 * mMargin;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java
index ae2737b..2634569 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ClipboardSupport.java
@@ -304,7 +304,7 @@ public class ClipboardSupport {
* In case of success, the new element will have some default attributes set (xmlns:android,
* layout_width and height). The edit is wrapped in a proper undo.
* <p/>
- * Implementation is similar to {@link #createDocumentRoot(String)} except we also
+ * Implementation is similar to {@link #createDocumentRoot} except we also
* copy all the attributes and inner elements recursively.
*/
private void pasteInEmptyDocument(final IDragElement pastedElement) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java
index f0698e6..44cd081 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CreateNewConfigJob.java
@@ -22,6 +22,7 @@ import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.resources.ResourceFolderType;
+import com.google.common.base.Charsets;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
@@ -35,8 +36,8 @@ import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PartInitException;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
/** Job which creates a new layout file for a given configuration */
class CreateNewConfigJob extends Job {
@@ -64,19 +65,17 @@ class CreateNewConfigJob extends Job {
IFolder res = (IFolder) mFromFile.getParent().getParent();
IFolder newParentFolder = res.getFolder(folderName);
- if (newParentFolder.exists()) {
- // this should not happen since aapt would have complained
- // before, but if one disables the automatic build, this could
- // happen.
- String message = String.format("File 'res/%1$s' already exists!",
- folderName);
- return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
- }
-
AdtUtils.ensureExists(newParentFolder);
final IFile file = newParentFolder.getFile(mFromFile.getName());
+ if (file.exists()) {
+ String message = String.format("File 'res/%1$s/%2$s' already exists!",
+ folderName, mFromFile.getName());
+ return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
+ }
- InputStream input = mFromFile.getContents();
+ // Read current document contents instead of from file: mFromFile.getContents()
+ String text = mEditor.getEditorDelegate().getEditor().getStructuredDocument().get();
+ ByteArrayInputStream input = new ByteArrayInputStream(text.getBytes(Charsets.UTF_8));
file.create(input, false, monitor);
input.close();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
index 1625195..145036b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
@@ -15,19 +15,17 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.TOOLS_URI;
-
-
import static org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML.ContentTypeID_XML;
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.utils.Pair;
@@ -61,6 +59,7 @@ import java.util.Set;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
/**
* Various utility methods for manipulating DOM nodes.
@@ -186,6 +185,31 @@ public class DomUtilities {
}
/**
+ * Returns the DOM document for the given editor
+ *
+ * @param editor the XML editor
+ * @return the document, or null if not found or not parsed properly (no
+ * errors are generated/thrown)
+ */
+ @Nullable
+ public static Document getDocument(@NonNull AndroidXmlEditor editor) {
+ IStructuredModel model = editor.getModelForRead();
+ try {
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ return domModel.getDocument();
+ }
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
* Returns the XML DOM node corresponding to the given offset of the given
* document.
*
@@ -828,6 +852,33 @@ public class DomUtilities {
}
/**
+ * Creates an empty non-Eclipse XML document.
+ * This is used when you need to use XML operations not supported by
+ * the Eclipse XML model (such as serialization).
+ * <p>
+ * The new document will not validate, will ignore comments, and will
+ * support namespace.
+ *
+ * @return the new document
+ */
+ @Nullable
+ public static Document createEmptyPlainDocument() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ factory.setIgnoringComments(true);
+ DocumentBuilder builder;
+ try {
+ builder = factory.newDocumentBuilder();
+ return builder.newDocument();
+ } catch (ParserConfigurationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ /**
* Parses the given XML string as a DOM document, using the JDK parser.
* The parser does not validate, and is namespace aware.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java
index 6829c40..ac3328d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ExportScreenshotAction.java
@@ -42,7 +42,7 @@ class ExportScreenshotAction extends Action {
@Override
public void run() {
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
ImageOverlay imageOverlay = mCanvas.getImageOverlay();
BufferedImage image = imageOverlay.getAwtImage();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java
index 0dbd152..f7085fc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/FragmentMenu.java
@@ -16,28 +16,24 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMetadata.KEY_FRAGMENT_LAYOUT;
-
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
import com.android.resources.ResourceType;
import com.android.utils.Pair;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
@@ -46,10 +42,8 @@ import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.Separator;
-import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Menu;
-import org.eclipse.swt.widgets.Shell;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -292,33 +286,12 @@ public class FragmentMenu extends SubmenuAction {
@Override
public void run() {
LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
- IProject project = delegate.getEditor().getProject();
- // get the resource repository for this project and the system resources.
- ResourceRepository projectRepository = ResourceManager.getInstance()
- .getProjectResources(project);
- Shell shell = mCanvas.getShell();
-
- AndroidTargetData data = delegate.getEditor().getTargetData();
- ResourceRepository systemRepository = data.getFrameworkResources();
-
- ResourceChooser dlg = new ResourceChooser(project,
- ResourceType.LAYOUT, projectRepository,
- systemRepository, shell);
-
- IInputValidator validator =
- CyclicDependencyValidator.create(delegate.getEditor().getInputFile());
-
- if (validator != null) {
- // Ensure wide enough to accommodate validator error message
- dlg.setSize(85, 10);
- dlg.setInputValidator(validator);
- }
-
- String layout = getSelectedLayout();
- if (layout != null) {
- dlg.setCurrentResource(layout);
- }
-
+ IFile file = delegate.getEditor().getInputFile();
+ GraphicalEditorPart editor = delegate.getGraphicalEditor();
+ ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT)
+ .setInputValidator(CyclicDependencyValidator.create(file))
+ .setInitialSize(85, 10)
+ .setCurrentResource(getSelectedLayout());
int result = dlg.open();
if (result == ResourceChooser.CLEAR_RETURN_CODE) {
setNewLayout(null);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
index 468d159..98bc25e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java
@@ -39,6 +39,7 @@ import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
+import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.events.TypedEvent;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Device;
@@ -433,7 +434,8 @@ public class GestureManager {
* Helper class which implements the {@link MouseMoveListener},
* {@link MouseListener} and {@link KeyListener} interfaces.
*/
- private class Listener implements MouseMoveListener, MouseListener, KeyListener {
+ private class Listener implements MouseMoveListener, MouseListener, MouseTrackListener,
+ KeyListener {
// --- MouseMoveListener ---
@@ -443,15 +445,16 @@ public class GestureManager {
mLastMouseY = e.y;
mLastStateMask = e.stateMask;
+ ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
if (mCurrentGesture != null) {
- ControlPoint controlPoint = ControlPoint.create(mCanvas, e);
updateMouse(controlPoint, e);
mCanvas.redraw();
}
} else {
- updateCursor(ControlPoint.create(mCanvas, e));
+ updateCursor(controlPoint);
mCanvas.hover(e);
+ mCanvas.getPreviewManager().moved(controlPoint);
}
}
@@ -460,7 +463,13 @@ public class GestureManager {
@Override
public void mouseUp(MouseEvent e) {
ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+
if (mCurrentGesture == null) {
+ // If clicking on a configuration preview, just process it there
+ if (mCanvas.getPreviewManager().click(mousePos)) {
+ return;
+ }
+
// Just a click, select
Pair<SelectionItem, SelectionHandle> handlePair =
mCanvas.getSelectionManager().findHandle(mousePos);
@@ -507,6 +516,24 @@ public class GestureManager {
}
}
+ // --- MouseTrackListener ---
+
+ @Override
+ public void mouseEnter(MouseEvent e) {
+ ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+ mCanvas.getPreviewManager().enter(mousePos);
+ }
+
+ @Override
+ public void mouseExit(MouseEvent e) {
+ ControlPoint mousePos = ControlPoint.create(mCanvas, e);
+ mCanvas.getPreviewManager().exit(mousePos);
+ }
+
+ @Override
+ public void mouseHover(MouseEvent e) {
+ }
+
// --- KeyListener ---
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index 726824f..051c568 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -30,8 +30,10 @@ import static com.android.SdkConstants.STRING_PREFIX;
import static com.android.SdkConstants.VALUE_FILL_PARENT;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
-import static com.android.ide.eclipse.adt.AdtUtils.isUiThread;
-import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser.NAME_CONFIG_STATE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_FOLDER;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_TARGET;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
@@ -41,7 +43,6 @@ import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_OPEN;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.ide.common.api.Rect;
import com.android.ide.common.layout.BaseLayoutRule;
import com.android.ide.common.rendering.LayoutLibrary;
import com.android.ide.common.rendering.StaticRenderSession;
@@ -71,6 +72,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationMatcher;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LayoutCreatorDialog;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
@@ -643,7 +645,7 @@ public class GraphicalEditorPart extends EditorPart
// ---- Implements ConfigurationClient ----
@Override
public void aboutToChange(int flags) {
- if ((flags & CHANGED_RENDER_TARGET) != 0) {
+ if ((flags & CFG_TARGET) != 0) {
IAndroidTarget oldTarget = mConfigChooser.getConfiguration().getTarget();
preRenderingTargetChangeCleanUp(oldTarget);
}
@@ -671,8 +673,12 @@ public class GraphicalEditorPart extends EditorPart
if (mEditorDelegate.getEditor().isCreatingPages()) {
recomputeLayout();
} else {
+ boolean affectsFileSelection = (flags & Configuration.MASK_FILE_ATTRS) != 0;
+ IFile best = null;
// get the resources of the file's project.
- IFile best = ConfigurationMatcher.getBestFileMatch(mConfigChooser);
+ if (affectsFileSelection) {
+ best = ConfigurationMatcher.getBestFileMatch(mConfigChooser);
+ }
if (best != null) {
if (!best.equals(mEditedFile)) {
try {
@@ -682,12 +688,12 @@ public class GraphicalEditorPart extends EditorPart
boolean reuseEditor = AdtPrefs.getPrefs().isSharedLayoutEditor();
if (!reuseEditor) {
- String data = AdtPlugin.getFileProperty(best, NAME_CONFIG_STATE);
+ String data = ConfigurationDescription.getDescription(best);
if (data == null) {
// Not previously opened: duplicate the current state as
// much as possible
data = mConfigChooser.getConfiguration().toPersistentString();
- AdtPlugin.setFileProperty(best, NAME_CONFIG_STATE, data);
+ ConfigurationDescription.setDescription(best, data);
}
}
@@ -710,7 +716,7 @@ public class GraphicalEditorPart extends EditorPart
// Even though the layout doesn't change, the config changed, and referenced
// resources need to be updated.
recomputeLayout();
- } else {
+ } else if (affectsFileSelection) {
// display the error.
Configuration configuration = mConfigChooser.getConfiguration();
FolderConfiguration currentConfig = configuration.getFullConfig();
@@ -728,10 +734,15 @@ public class GraphicalEditorPart extends EditorPart
currentConfig.toDisplayString(),
currentConfig.getFolderName(ResourceFolderType.LAYOUT),
mEditedFile.getName());
+ } else {
+ // Something else changed, such as the theme - just recompute existing
+ // layout
+ mConfigChooser.saveConstraints();
+ recomputeLayout();
}
}
- if ((flags & CHANGED_RENDER_TARGET) != 0) {
+ if ((flags & CFG_TARGET) != 0) {
Configuration configuration = mConfigChooser.getConfiguration();
IAndroidTarget target = configuration.getTarget();
Sdk current = Sdk.getCurrent();
@@ -741,17 +752,19 @@ public class GraphicalEditorPart extends EditorPart
}
}
- if ((flags & (CHANGED_DEVICE | CHANGED_DEVICE_CONFIG)) != 0) {
+ if ((flags & (CFG_DEVICE | CFG_DEVICE_STATE)) != 0) {
// When the device changes, zoom the view to fit, but only up to 100% (e.g. zoom
// out to fit the content, or zoom back in if we were zoomed out more from the
// previous view, but only up to 100% such that we never blow up pixels
if (mActionBar.isZoomingAllowed()) {
- getCanvasControl().setFitScale(true);
+ getCanvasControl().setFitScale(true, true /*allowZoomIn*/);
}
}
reloadPalette();
+ getCanvasControl().getPreviewManager().configurationChanged(flags);
+
return true;
}
@@ -872,6 +885,12 @@ public class GraphicalEditorPart extends EditorPart
return mIncludedWithin;
}
+ @Override
+ @Nullable
+ public LayoutCanvas getCanvas() {
+ return getCanvasControl();
+ }
+
/**
* Listens to target changed in the current project, to trigger a new layout rendering.
*/
@@ -906,7 +925,7 @@ public class GraphicalEditorPart extends EditorPart
IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject());
if (target != null) {
mConfigChooser.onSdkLoaded(target);
- changed(CHANGED_FOLDER | CHANGED_RENDER_TARGET);
+ changed(CFG_FOLDER | CFG_TARGET);
}
}
}
@@ -1039,6 +1058,8 @@ public class GraphicalEditorPart extends EditorPart
if (mNeedsRecompute) {
recomputeLayout();
}
+
+ mCanvasViewer.getCanvas().syncPreviewMode();
}
}
@@ -1165,7 +1186,7 @@ public class GraphicalEditorPart extends EditorPart
AndroidTargetData targetData = mConfigChooser.onXmlModelLoaded();
updateCapabilities(targetData);
- changed(CHANGED_FOLDER | CHANGED_RENDER_TARGET);
+ changed(CFG_FOLDER | CFG_TARGET);
}
/** Updates the capabilities for the given target data (which may be null) */
@@ -1253,6 +1274,7 @@ public class GraphicalEditorPart extends EditorPart
}
UiDocumentNode model = getModel();
+ LayoutCanvas canvas = mCanvasViewer.getCanvas();
if (!ensureModelValid(model)) {
// Although we display an error, we still treat an empty document as a
// successful layout result so that we can drop new elements in it.
@@ -1260,7 +1282,7 @@ public class GraphicalEditorPart extends EditorPart
// For that purpose, create a special LayoutScene that has no image,
// no root view yet indicates success and then update the canvas with it.
- mCanvasViewer.getCanvas().setSession(
+ canvas.setSession(
new StaticRenderSession(
Result.Status.SUCCESS.createResult(),
null /*rootViewInfo*/, null /*image*/),
@@ -1278,6 +1300,8 @@ public class GraphicalEditorPart extends EditorPart
IProject project = mEditedFile.getProject();
renderWithBridge(project, model, layoutLib);
+
+ canvas.getPreviewManager().renderPreviews();
}
} finally {
// no matter the result, we are done doing the recompute based on the latest
@@ -1310,15 +1334,6 @@ public class GraphicalEditorPart extends EditorPart
}
/**
- * Returns the current bounds of the Android device screen, in canvas control pixels.
- *
- * @return the bounds of the screen, never null
- */
- public Rect getScreenBounds() {
- return mConfigChooser.getConfiguration().getScreenBounds();
- }
-
- /**
* Returns the scale to multiply pixels in the layout coordinate space with to obtain
* the corresponding dip (device independent pixel)
*
@@ -1435,7 +1450,7 @@ public class GraphicalEditorPart extends EditorPart
}
} else if (displayError) { // target == null
- displayError("The project target is not set.");
+ displayError("The project target is not set. Right click project, choose Properties | Android.");
}
} else if (displayError) { // currentSdk == null
displayError("Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.",
@@ -1462,11 +1477,6 @@ public class GraphicalEditorPart extends EditorPart
return null;
}
- if (mConfigChooser.isDisposed()) {
- return null;
- }
- assert isUiThread();
-
// attempt to get a target from the configuration selector.
IAndroidTarget renderingTarget = mConfigChooser.getConfiguration().getTarget();
if (renderingTarget != null) {
@@ -1501,6 +1511,10 @@ public class GraphicalEditorPart extends EditorPart
private boolean ensureModelValid(UiDocumentNode model) {
// check there is actually a model (maybe the file is empty).
if (model.getUiChildren().size() == 0) {
+ if (mEditorDelegate.getEditor().isCreatingPages()) {
+ displayError("Loading editor");
+ return false;
+ }
displayError(
"No XML content. Please add a root view or layout to your document.");
return false;
@@ -1513,7 +1527,6 @@ public class GraphicalEditorPart extends EditorPart
LayoutLibrary layoutLib) {
LayoutCanvas canvas = getCanvasControl();
Set<UiElementNode> explodeNodes = canvas.getNodesToExplode();
- Rect rect = getScreenBounds();
RenderLogger logger = new RenderLogger(mEditedFile.getName());
RenderingMode renderingMode = RenderingMode.NORMAL;
// FIXME set the rendering mode using ViewRule or something.
@@ -1525,7 +1538,6 @@ public class GraphicalEditorPart extends EditorPart
RenderSession session = RenderService.create(this)
.setModel(model)
- .setSize(rect.w, rect.h)
.setLog(logger)
.setRenderingMode(renderingMode)
.setIncludedWithin(mIncludedWithin)
@@ -1565,7 +1577,7 @@ public class GraphicalEditorPart extends EditorPart
} else if (missingClasses.size() > 0 || brokenClasses.size() > 0) {
displayFailingClasses(missingClasses, brokenClasses, false);
displayUserStackTrace(logger, true);
- } else {
+ } else if (session != null) {
// Nope, no missing or broken classes. Clear success, congrats!
hideError();
@@ -1583,6 +1595,8 @@ public class GraphicalEditorPart extends EditorPart
job.setSystem(true);
job.schedule(3000); // 3 seconds
}
+
+ mConfigChooser.ensureInitialized();
}
model.refreshUi();
@@ -2043,6 +2057,21 @@ public class GraphicalEditorPart extends EditorPart
"or fix the theme style references.\n\n");
}
+ List<Throwable> trace = logger.getFirstTrace();
+ if (trace != null
+ && trace.toString().contains(
+ "java.lang.IndexOutOfBoundsException: Index: 2, Size: 2") //$NON-NLS-1$
+ && mConfigChooser.getConfiguration().getDensity() == Density.TV) {
+ addBoldText(mErrorLabel,
+ "It looks like you are using a render target where the layout library " +
+ "does not support the tvdpi density.\n\n");
+ addText(mErrorLabel, "Please try either updating to " +
+ "the latest available version (using the SDK manager), or if no updated " +
+ "version is available for this specific version of Android, try using " +
+ "a more recent render target version.\n\n");
+
+ }
+
if (hasAaptErrors && logger.seenTagPrefix(LayoutLog.TAG_RESOURCES_PREFIX)) {
// Text will automatically be wrapped by the error widget so no reason
// to insert linebreaks in this error message:
@@ -2337,7 +2366,7 @@ public class GraphicalEditorPart extends EditorPart
@SuppressWarnings("restriction")
String id = BuildPathsPropertyPage.PROP_ID;
PreferencesUtil.createPropertyDialogOn(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
getProject(), id, null, null).open();
break;
case LINK_OPEN_CLASS:
@@ -2798,7 +2827,7 @@ public class GraphicalEditorPart extends EditorPart
// Auto zoom the surface if you open or close flyout windows such as the palette
// or the property/outline views
if (newState == STATE_OPEN || newState == STATE_COLLAPSED && oldState == STATE_OPEN) {
- getCanvasControl().setFitScale(true /*onlyZoomOut*/);
+ getCanvasControl().setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/);
}
sDockingStateVersion++;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
index c55d0d8..b5bc9aa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageUtils.java
@@ -21,6 +21,12 @@ import static com.android.SdkConstants.DOT_GIF;
import static com.android.SdkConstants.DOT_JPG;
import static com.android.SdkConstants.DOT_PNG;
import static com.android.utils.SdkUtils.endsWithIgnoreCase;
+import static java.awt.RenderingHints.KEY_ANTIALIASING;
+import static java.awt.RenderingHints.KEY_INTERPOLATION;
+import static java.awt.RenderingHints.KEY_RENDERING;
+import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
+import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
+import static java.awt.RenderingHints.VALUE_RENDER_QUALITY;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -34,7 +40,6 @@ import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
-import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.IOException;
@@ -471,6 +476,7 @@ public class ImageUtils {
* Draws a drop shadow for the given rectangle into the given context. It
* will not draw anything if the rectangle is smaller than a minimum
* determined by the assets used to draw the shadow graphics.
+ * The size of the shadow is {@link #SHADOW_SIZE}.
*
* @param image the image to draw the shadow into
* @param x the left coordinate of the left hand side of the rectangle
@@ -489,12 +495,40 @@ public class ImageUtils {
}
/**
+ * Draws a small drop shadow for the given rectangle into the given context. It
+ * will not draw anything if the rectangle is smaller than a minimum
+ * determined by the assets used to draw the shadow graphics.
+ * The size of the shadow is {@link #SMALL_SHADOW_SIZE}.
+ *
+ * @param image the image to draw the shadow into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static final void drawSmallRectangleShadow(BufferedImage image,
+ int x, int y, int width, int height) {
+ Graphics gc = image.getGraphics();
+ try {
+ drawSmallRectangleShadow(gc, x, y, width, height);
+ } finally {
+ gc.dispose();
+ }
+ }
+
+ /**
* The width and height of the drop shadow painted by
* {@link #drawRectangleShadow(Graphics, int, int, int, int)}
*/
public static final int SHADOW_SIZE = 20; // DO NOT EDIT. This corresponds to bitmap graphics
/**
+ * The width and height of the drop shadow painted by
+ * {@link #drawSmallRectangleShadow(Graphics, int, int, int, int)}
+ */
+ public static final int SMALL_SHADOW_SIZE = 10; // DO NOT EDIT. Corresponds to bitmap graphics
+
+ /**
* Draws a drop shadow for the given rectangle into the given context. It
* will not draw anything if the rectangle is smaller than a minimum
* determined by the assets used to draw the shadow graphics.
@@ -569,8 +603,82 @@ public class ImageUtils {
null);
}
+ /**
+ * Draws a small drop shadow for the given rectangle into the given context. It
+ * will not draw anything if the rectangle is smaller than a minimum
+ * determined by the assets used to draw the shadow graphics.
+ * <p>
+ *
+ * @param gc the graphics context to draw into
+ * @param x the left coordinate of the left hand side of the rectangle
+ * @param y the top coordinate of the top of the rectangle
+ * @param width the width of the rectangle
+ * @param height the height of the rectangle
+ */
+ public static final void drawSmallRectangleShadow(Graphics gc,
+ int x, int y, int width, int height) {
+ if (sShadow2BottomLeft == null) {
+ // Shadow graphics. This was generated by creating a drop shadow in
+ // Gimp, using the parameters x offset=5, y offset=%, blur radius=5,
+ // color=black, and opacity=51. These values attempt to make a shadow
+ // that is legible both for dark and light themes, on top of the
+ // canvas background (rgb(150,150,150). Darker shadows would tend to
+ // blend into the foreground for a dark holo screen, and lighter shadows
+ // would be hard to spot on the canvas background. If you make adjustments,
+ // make sure to check the shadow with both dark and light themes.
+ //
+ // After making the graphics, I cut out the top right, bottom left
+ // and bottom right corners as 20x20 images, and these are reproduced by
+ // painting them in the corresponding places in the target graphics context.
+ // I then grabbed a single horizontal gradient line from the middle of the
+ // right edge,and a single vertical gradient line from the bottom. These
+ // are then painted scaled/stretched in the target to fill the gaps between
+ // the three corner images.
+ //
+ // Filenames: bl=bottom left, b=bottom, br=bottom right, r=right, tr=top right
+ sShadow2BottomLeft = readImage("shadow2-bl.png"); //$NON-NLS-1$
+ sShadow2Bottom = readImage("shadow2-b.png"); //$NON-NLS-1$
+ sShadow2BottomRight = readImage("shadow2-br.png"); //$NON-NLS-1$
+ sShadow2Right = readImage("shadow2-r.png"); //$NON-NLS-1$
+ sShadow2TopRight = readImage("shadow2-tr.png"); //$NON-NLS-1$
+ assert sShadow2BottomLeft != null;
+ assert sShadow2TopRight != null;
+ assert sShadow2BottomRight.getWidth() == SMALL_SHADOW_SIZE;
+ assert sShadow2BottomRight.getHeight() == SMALL_SHADOW_SIZE;
+ }
+
+ int blWidth = sShadow2BottomLeft.getWidth();
+ int trHeight = sShadow2TopRight.getHeight();
+ if (width < blWidth) {
+ return;
+ }
+ if (height < trHeight) {
+ return;
+ }
+
+ gc.drawImage(sShadow2BottomLeft, x, y + height, null);
+ gc.drawImage(sShadow2BottomRight, x + width, y + height, null);
+ gc.drawImage(sShadow2TopRight, x + width, y, null);
+ gc.drawImage(sShadow2Bottom,
+ x + sShadow2BottomLeft.getWidth(), y + height,
+ x + width, y + height + sShadow2Bottom.getHeight(),
+ 0, 0, sShadow2Bottom.getWidth(), sShadow2Bottom.getHeight(),
+ null);
+ gc.drawImage(sShadow2Right,
+ x + width, y + sShadow2TopRight.getHeight(),
+ x + width + sShadow2Right.getWidth(), y + height,
+ 0, 0, sShadow2Right.getWidth(), sShadow2Right.getHeight(),
+ null);
+ }
+
+ /**
+ * Reads the given image from the plugin folder
+ *
+ * @param name the name of the image (including file extension)
+ * @return the corresponding image, or null if something goes wrong
+ */
@Nullable
- private static BufferedImage readImage(@NonNull String name) {
+ public static BufferedImage readImage(@NonNull String name) {
InputStream stream = ImageUtils.class.getResourceAsStream("/icons/" + name); //$NON-NLS-1$
if (stream != null) {
try {
@@ -589,12 +697,19 @@ public class ImageUtils {
return null;
}
+ // Normal drop shadow
private static BufferedImage sShadowBottomLeft;
private static BufferedImage sShadowBottom;
private static BufferedImage sShadowBottomRight;
private static BufferedImage sShadowRight;
private static BufferedImage sShadowTopRight;
+ // Small drop shadow
+ private static BufferedImage sShadow2BottomLeft;
+ private static BufferedImage sShadow2Bottom;
+ private static BufferedImage sShadow2BottomRight;
+ private static BufferedImage sShadow2Right;
+ private static BufferedImage sShadow2TopRight;
/**
* Returns a bounding rectangle for the given list of rectangles. If the list is
@@ -731,20 +846,102 @@ public class ImageUtils {
if (imageType == BufferedImage.TYPE_CUSTOM) {
imageType = BufferedImage.TYPE_INT_ARGB;
}
- BufferedImage scaled = new BufferedImage(
- destWidth + rightMargin, destHeight + bottomMargin, imageType);
- Graphics2D g2 = scaled.createGraphics();
- g2.setComposite(AlphaComposite.Src);
- g2.setColor(new Color(0, true));
- g2.fillRect(0, 0, destWidth + rightMargin, destHeight + bottomMargin);
- g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
- RenderingHints.VALUE_INTERPOLATION_BILINEAR);
- g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
- g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight, null);
- g2.dispose();
+ if (xScale > 0.5 && yScale > 0.5) {
+ BufferedImage scaled =
+ new BufferedImage(destWidth + rightMargin, destHeight + bottomMargin, imageType);
+ Graphics2D g2 = scaled.createGraphics();
+ g2.setComposite(AlphaComposite.Src);
+ g2.setColor(new Color(0, true));
+ g2.fillRect(0, 0, destWidth + rightMargin, destHeight + bottomMargin);
+ g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0, destWidth, destHeight, 0, 0, sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+ return scaled;
+ } else {
+ // When creating a thumbnail, using the above code doesn't work very well;
+ // you get some visible artifacts, especially for text. Instead use the
+ // technique of repeatedly scaling the image into half; this will cause
+ // proper averaging of neighboring pixels, and will typically (for the kinds
+ // of screen sizes used by this utility method in the layout editor) take
+ // about 3-4 iterations to get the result since we are logarithmically reducing
+ // the size. Besides, each successive pass in operating on much fewer pixels
+ // (a reduction of 4 in each pass).
+ //
+ // However, we may not be resizing to a size that can be reached exactly by
+ // successively diving in half. Therefore, once we're within a factor of 2 of
+ // the final size, we can do a resize to the exact target size.
+ // However, we can get even better results if we perform this final resize
+ // up front. Let's say we're going from width 1000 to a destination width of 85.
+ // The first approach would cause a resize from 1000 to 500 to 250 to 125, and
+ // then a resize from 125 to 85. That last resize can distort/blur a lot.
+ // Instead, we can start with the destination width, 85, and double it
+ // successfully until we're close to the initial size: 85, then 170,
+ // then 340, and finally 680. (The next one, 1360, is larger than 1000).
+ // So, now we *start* the thumbnail operation by resizing from width 1000 to
+ // width 680, which will preserve a lot of visual details such as text.
+ // Then we can successively resize the image in half, 680 to 340 to 170 to 85.
+ // We end up with the expected final size, but we've been doing an exact
+ // divide-in-half resizing operation at the end so there is less distortion.
+
+
+ int iterations = 0; // Number of halving operations to perform after the initial resize
+ int nearestWidth = destWidth; // Width closest to source width that = 2^x, x is integer
+ int nearestHeight = destHeight;
+ while (nearestWidth < sourceWidth / 2) {
+ nearestWidth *= 2;
+ nearestHeight *= 2;
+ iterations++;
+ }
- return scaled;
+ // If we're supposed to add in margins, we need to do it in the initial resizing
+ // operation if we don't have any subsequent resizing operations.
+ if (iterations == 0) {
+ nearestWidth += rightMargin;
+ nearestHeight += bottomMargin;
+ }
+
+ BufferedImage scaled = new BufferedImage(nearestWidth, nearestHeight, imageType);
+ Graphics2D g2 = scaled.createGraphics();
+ g2.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0, nearestWidth, nearestHeight,
+ 0, 0, sourceWidth, sourceHeight, null);
+ g2.dispose();
+
+ sourceWidth = nearestWidth;
+ sourceHeight = nearestHeight;
+ source = scaled;
+
+ for (int iteration = iterations - 1; iteration >= 0; iteration--) {
+ int halfWidth = sourceWidth / 2;
+ int halfHeight = sourceHeight / 2;
+ if (iteration == 0) { // Last iteration: Add margins in final image
+ scaled = new BufferedImage(halfWidth + rightMargin, halfHeight + bottomMargin,
+ imageType);
+ } else {
+ scaled = new BufferedImage(halfWidth, halfHeight, imageType);
+ }
+ g2 = scaled.createGraphics();
+ g2.setRenderingHint(KEY_INTERPOLATION,VALUE_INTERPOLATION_BILINEAR);
+ g2.setRenderingHint(KEY_RENDERING, VALUE_RENDER_QUALITY);
+ g2.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ g2.drawImage(source, 0, 0,
+ halfWidth, halfHeight, 0, 0,
+ sourceWidth, sourceHeight,
+ null);
+ g2.dispose();
+
+ sourceWidth = halfWidth;
+ sourceHeight = halfHeight;
+ source = scaled;
+ iterations--;
+ }
+ return scaled;
+ }
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
index 0b8f784..7bab914 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/IncludeFinder.java
@@ -20,6 +20,8 @@ import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.EXT_XML;
import static com.android.SdkConstants.FD_RESOURCES;
import static com.android.SdkConstants.FD_RES_LAYOUT;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
import static com.android.SdkConstants.VIEW_INCLUDE;
import static com.android.ide.eclipse.adt.AdtConstants.WS_LAYOUTS;
import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
@@ -29,6 +31,8 @@ import static org.eclipse.core.resources.IResourceDelta.CHANGED;
import static org.eclipse.core.resources.IResourceDelta.CONTENT;
import static org.eclipse.core.resources.IResourceDelta.REMOVED;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
@@ -56,6 +60,7 @@ import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
@@ -117,8 +122,9 @@ public class IncludeFinder {
* Returns the {@link IncludeFinder} for the given project
*
* @param project the project the finder is associated with
- * @return an {@IncludeFinder} for the given project, never null
+ * @return an {@link IncludeFinder} for the given project, never null
*/
+ @NonNull
public static IncludeFinder get(IProject project) {
IncludeFinder finder = null;
try {
@@ -157,6 +163,7 @@ public class IncludeFinder {
* @param included the file that is included
* @return the files that are including the given file, or null or empty
*/
+ @Nullable
public List<Reference> getIncludedBy(IResource included) {
ensureInitialized();
String mapKey = getMapKey(included);
@@ -503,8 +510,10 @@ public class IncludeFinder {
* empty if the file does not include any include tags; it does this by only parsing
* if it detects the string &lt;include in the file.
*/
- private List<String> findIncludes(String xml) {
- int index = xml.indexOf("<include"); //$NON-NLS-1$
+ @VisibleForTesting
+ @NonNull
+ static List<String> findIncludes(@NonNull String xml) {
+ int index = xml.indexOf(ATTR_LAYOUT);
if (index != -1) {
return findIncludesInXml(xml);
}
@@ -518,7 +527,9 @@ public class IncludeFinder {
* @param xml layout XML content to be parsed for includes
* @return a list of included urls, or null
*/
- private List<String> findIncludesInXml(String xml) {
+ @VisibleForTesting
+ @NonNull
+ static List<String> findIncludesInXml(@NonNull String xml) {
Document document = DomUtilities.parseDocument(xml, false /*logParserErrors*/);
if (document != null) {
return findIncludesInDocument(document);
@@ -528,27 +539,52 @@ public class IncludeFinder {
}
/** Searches the given DOM document and returns the list of includes, if any */
- private List<String> findIncludesInDocument(Document document) {
- NodeList includes = document.getElementsByTagName(VIEW_INCLUDE);
- if (includes.getLength() > 0) {
- List<String> urls = new ArrayList<String>();
- for (int i = 0; i < includes.getLength(); i++) {
- Element element = (Element) includes.item(i);
- String url = element.getAttribute(ATTR_LAYOUT);
+ @NonNull
+ private static List<String> findIncludesInDocument(@NonNull Document document) {
+ List<String> includes = findIncludesInDocument(document, null);
+ if (includes == null) {
+ includes = Collections.emptyList();
+ }
+ return includes;
+ }
+
+ @Nullable
+ private static List<String> findIncludesInDocument(@NonNull Node node,
+ @Nullable List<String> urls) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ String tag = node.getNodeName();
+ boolean isInclude = tag.equals(VIEW_INCLUDE);
+ boolean isFragment = tag.equals(VIEW_FRAGMENT);
+ if (isInclude || isFragment) {
+ Element element = (Element) node;
+ String url;
+ if (isInclude) {
+ url = element.getAttribute(ATTR_LAYOUT);
+ } else {
+ url = element.getAttributeNS(TOOLS_URI, ATTR_LAYOUT);
+ }
if (url.length() > 0) {
String resourceName = urlToLocalResource(url);
if (resourceName != null) {
+ if (urls == null) {
+ urls = new ArrayList<String>();
+ }
urls.add(resourceName);
}
}
+
}
+ }
- return urls;
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ urls = findIncludesInDocument(children.item(i), urls);
}
- return Collections.emptyList();
+ return urls;
}
+
/**
* Returns the layout URL to a local resource name (provided the URL is a local
* resource, not something in @android etc.) Returns null otherwise.
@@ -628,6 +664,7 @@ public class IncludeFinder {
ResourceManager.getInstance().addListener(sListener);
}
+ /** Stop listening on project resources */
public static void stop() {
assert sListener != null;
ResourceManager.getInstance().addListener(sListener);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 4368db4..1b1bd23 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -33,6 +33,8 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Screen;
import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
import com.google.common.base.Strings;
@@ -691,7 +693,7 @@ public class LayoutActionBar extends Composite {
* Reset the canvas scale to best fit (so content is as large as possible without scrollbars)
*/
void rescaleToFit(boolean onlyZoomOut) {
- mEditor.getCanvasControl().setFitScale(onlyZoomOut);
+ mEditor.getCanvasControl().setFitScale(onlyZoomOut, true /*allowZoomIn*/);
}
boolean rescaleToReal(boolean real) {
@@ -708,7 +710,9 @@ public class LayoutActionBar extends Composite {
// compute average dpi of X and Y
ConfigurationChooser chooser = mEditor.getConfigurationChooser();
Configuration config = chooser.getConfiguration();
- float dpi = (config.getXDpi() + config.getYDpi()) / 2.f;
+ Device device = config.getDevice();
+ Screen screen = device.getDefaultHardware().getScreen();
+ double dpi = (screen.getXdpi() + screen.getYdpi()) / 2.;
// get the monitor dpi
float monitor = AdtPrefs.getPrefs().getMonitorDensity();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 86878ac..814b82c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -18,6 +18,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.IDragElement.IDragAttribute;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Margins;
import com.android.ide.common.api.Point;
@@ -27,6 +29,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeFactory;
@@ -81,6 +84,7 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
@@ -119,7 +123,7 @@ public class LayoutCanvas extends Canvas {
private static final boolean DEBUG = false;
- /* package */ static final String PREFIX_CANVAS_ACTION = "canvas_action_";
+ static final String PREFIX_CANVAS_ACTION = "canvas_action_"; //$NON-NLS-1$
/** The layout editor that uses this layout canvas. */
private final LayoutEditorDelegate mEditorDelegate;
@@ -147,13 +151,13 @@ public class LayoutCanvas extends Canvas {
private DropTarget mDropTarget;
/** Factory that can create {@link INode} proxies. */
- private final NodeFactory mNodeFactory = new NodeFactory(this);
+ private final @NonNull NodeFactory mNodeFactory = new NodeFactory(this);
/** Vertical scaling & scrollbar information. */
- private CanvasTransform mVScale;
+ private final CanvasTransform mVScale;
/** Horizontal scaling & scrollbar information. */
- private CanvasTransform mHScale;
+ private final CanvasTransform mHScale;
/** Drag source associated with this canvas. */
private DragSource mDragSource;
@@ -218,6 +222,9 @@ public class LayoutCanvas extends Canvas {
/** The overlay which paints masks hiding everything but included content. */
private IncludeOverlay mIncludeOverlay;
+ /** Configuration previews shown next to the layout */
+ private final RenderPreviewManager mPreviewManager;
+
/**
* Gesture Manager responsible for identifying mouse, keyboard and drag and
* drop events.
@@ -239,6 +246,14 @@ public class LayoutCanvas extends Canvas {
private Color mBackgroundColor;
+ /**
+ * Creates a new {@link LayoutCanvas} widget
+ *
+ * @param editorDelegate the associated editor delegate
+ * @param rulesEngine the rules engine
+ * @param parent parent SWT widget
+ * @param style the SWT style
+ */
public LayoutCanvas(LayoutEditorDelegate editorDelegate,
RulesEngine rulesEngine,
Composite parent,
@@ -253,6 +268,7 @@ public class LayoutCanvas extends Canvas {
mClipboardSupport = new ClipboardSupport(this, parent);
mHScale = new CanvasTransform(this, getHorizontalBar());
mVScale = new CanvasTransform(this, getVerticalBar());
+ mPreviewManager = new RenderPreviewManager(this);
// Unit test suite passes a null here; TODO: Replace with mocking
IFile file = editorDelegate != null ? editorDelegate.getEditor().getInputFile() : null;
@@ -314,9 +330,7 @@ public class LayoutCanvas extends Canvas {
}
}
- Rectangle clientArea = getClientArea();
- mHScale.setClientSize(clientArea.width);
- mVScale.setClientSize(clientArea.height);
+ updateScrollBars();
// Update the zoom level in the canvas when you toggle the zoom
if (coordinator != null) {
@@ -355,6 +369,51 @@ public class LayoutCanvas extends Canvas {
mLintTooltipManager.register();
}
+ void updateScrollBars() {
+ Rectangle clientArea = getClientArea();
+ Image image = mImageOverlay.getImage();
+ if (image != null) {
+ ImageData imageData = image.getImageData();
+ int clientWidth = clientArea.width;
+ int clientHeight = clientArea.height;
+
+ int imageWidth = imageData.width;
+ int imageHeight = imageData.height;
+
+ int fullWidth = imageWidth;
+ int fullHeight = imageHeight;
+
+ if (mPreviewManager.hasPreviews()) {
+ fullHeight = Math.max(fullHeight,
+ (int) (mPreviewManager.getHeight() / mHScale.getScale()));
+ }
+
+ if (clientWidth == 0) {
+ clientWidth = imageWidth;
+ Shell shell = getShell();
+ if (shell != null) {
+ org.eclipse.swt.graphics.Point size = shell.getSize();
+ if (size.x > 0) {
+ clientWidth = size.x * 70 / 100;
+ }
+ }
+ }
+ if (clientHeight == 0) {
+ clientHeight = imageHeight;
+ Shell shell = getShell();
+ if (shell != null) {
+ org.eclipse.swt.graphics.Point size = shell.getSize();
+ if (size.y > 0) {
+ clientWidth = size.y * 80 / 100;
+ }
+ }
+ }
+
+ mHScale.setSize(imageWidth, fullWidth, clientWidth);
+ mVScale.setSize(imageHeight, fullHeight, clientHeight);
+ }
+ }
+
private Runnable mZoomCheck = new Runnable() {
private Boolean mWasZoomed;
@@ -372,10 +431,9 @@ public class LayoutCanvas extends Canvas {
Boolean zoomed = coordinator.isEditorMaximized();
if (mWasZoomed != zoomed) {
if (mWasZoomed != null) {
- LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor()
- .getLayoutActionBar();
+ LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar();
if (actionBar.isZoomingAllowed()) {
- setFitScale(true /*onlyZoomOut*/);
+ setFitScale(true /*onlyZoomOut*/, true /*allowZoomIn*/);
}
}
mWasZoomed = zoomed;
@@ -415,18 +473,26 @@ public class LayoutCanvas extends Canvas {
} else {
// Zooming actions
char c = e.character;
- LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor().getLayoutActionBar();
+ LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar();
if (c == '1' && actionBar.isZoomingAllowed()) {
setScale(1, true);
} else if (c == '0' && actionBar.isZoomingAllowed()) {
- setFitScale(true);
+ setFitScale(true, true /*allowZoomIn*/);
} else if (e.keyCode == '0' && (e.stateMask & SWT.MOD2) != 0
&& actionBar.isZoomingAllowed()) {
- setFitScale(false);
- } else if (c == '+' && actionBar.isZoomingAllowed()) {
- actionBar.rescale(1);
+ setFitScale(false, true /*allowZoomIn*/);
+ } else if ((c == '+' || c == '=') && actionBar.isZoomingAllowed()) {
+ if ((e.stateMask & SWT.MOD1) != 0) {
+ mPreviewManager.zoomIn();
+ } else {
+ actionBar.rescale(1);
+ }
} else if (c == '-' && actionBar.isZoomingAllowed()) {
- actionBar.rescale(-1);
+ if ((e.stateMask & SWT.MOD1) != 0) {
+ mPreviewManager.zoomOut();
+ } else {
+ actionBar.rescale(-1);
+ }
}
}
}
@@ -507,24 +573,38 @@ public class LayoutCanvas extends Canvas {
mBackgroundColor = null;
}
+ mPreviewManager.disposePreviews();
mViewHierarchy.dispose();
}
+ /**
+ * Returns the configuration preview manager for this canvas
+ *
+ * @return the configuration preview manager for this canvas
+ */
+ @NonNull
+ public RenderPreviewManager getPreviewManager() {
+ return mPreviewManager;
+ }
+
/** Returns the Rules Engine, associated with the current project. */
- /* package */ RulesEngine getRulesEngine() {
+ RulesEngine getRulesEngine() {
return mRulesEngine;
}
/** Sets the Rules Engine, associated with the current project. */
- /* package */ void setRulesEngine(RulesEngine rulesEngine) {
+ void setRulesEngine(RulesEngine rulesEngine) {
mRulesEngine = rulesEngine;
}
/**
* Returns the factory to use to convert from {@link CanvasViewInfo} or from
* {@link UiViewElementNode} to {@link INode} proxies.
+ *
+ * @return the node factory
*/
- /* package */ NodeFactory getNodeFactory() {
+ @NonNull
+ public NodeFactory getNodeFactory() {
return mNodeFactory;
}
@@ -533,12 +613,14 @@ public class LayoutCanvas extends Canvas {
*
* @return The GCWrapper used to paint view rules
*/
- /* package */ GCWrapper getGcWrapper() {
+ GCWrapper getGcWrapper() {
return mGCWrapper;
}
/**
* Returns the {@link LayoutEditorDelegate} associated with this canvas.
+ *
+ * @return the delegate
*/
public LayoutEditorDelegate getEditorDelegate() {
return mEditorDelegate;
@@ -589,7 +671,7 @@ public class LayoutCanvas extends Canvas {
* @return A {@link CanvasTransform} for mapping between layout and control
* coordinates in the horizontal dimension.
*/
- /* package */ CanvasTransform getHorizontalTransform() {
+ CanvasTransform getHorizontalTransform() {
return mHScale;
}
@@ -600,7 +682,7 @@ public class LayoutCanvas extends Canvas {
* @return A {@link CanvasTransform} for mapping between layout and control
* coordinates in the vertical dimension.
*/
- /* package */ CanvasTransform getVerticalTransform() {
+ CanvasTransform getVerticalTransform() {
return mVScale;
}
@@ -648,6 +730,11 @@ public class LayoutCanvas extends Canvas {
return mSelectAllAction;
}
+ /** Returns the associated {@link GraphicalEditorPart} */
+ GraphicalEditorPart getGraphicalEditor() {
+ return mEditorDelegate.getGraphicalEditor();
+ }
+
/**
* Sets the result of the layout rendering. The result object indicates if the layout
* rendering succeeded. If it did, it contains a bitmap and the objects rectangles.
@@ -663,22 +750,21 @@ public class LayoutCanvas extends Canvas {
* {@link #showInvisibleViews(boolean)}) where individual invisible nodes
* are padded during certain interactions.
*/
- /* package */ void setSession(RenderSession session, Set<UiElementNode> explodedNodes,
+ void setSession(RenderSession session, Set<UiElementNode> explodedNodes,
boolean layoutlib5) {
// disable any hover
clearHover();
mViewHierarchy.setSession(session, explodedNodes, layoutlib5);
if (mViewHierarchy.isValid() && session != null) {
- Image image = mImageOverlay.setImage(session.getImage(), session.isAlphaChannelImage());
+ Image image = mImageOverlay.setImage(session.getImage(),
+ session.isAlphaChannelImage());
mOutlinePage.setModel(mViewHierarchy.getRoot());
- mEditorDelegate.getGraphicalEditor().setModel(mViewHierarchy.getRoot());
+ getGraphicalEditor().setModel(mViewHierarchy.getRoot());
if (image != null) {
- Rectangle clientArea = getClientArea();
- mHScale.setSize(image.getImageData().width, clientArea.width);
- mVScale.setSize(image.getImageData().height, clientArea.height);
+ updateScrollBars();
if (mZoomFitNextImage) {
// Must be run asynchronously because getClientArea() returns 0 bounds
// when the editor is being initialized
@@ -691,6 +777,9 @@ public class LayoutCanvas extends Canvas {
}
});
}
+
+ // Ensure that if we have a a preview mode enabled, it's shown
+ syncPreviewMode();
}
}
@@ -700,10 +789,9 @@ public class LayoutCanvas extends Canvas {
void ensureZoomed() {
if (mZoomFitNextImage && getClientArea().height > 0) {
mZoomFitNextImage = false;
- LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor()
- .getLayoutActionBar();
+ LayoutActionBar actionBar = getGraphicalEditor().getLayoutActionBar();
if (actionBar.isZoomingAllowed()) {
- setFitScale(true);
+ setFitScale(true, true /*allowZoomIn*/);
}
}
}
@@ -713,11 +801,18 @@ public class LayoutCanvas extends Canvas {
redraw();
}
+ /**
+ * Returns the zoom scale factor of the canvas (the amount the full
+ * resolution render of the device is zoomed before being shown on the
+ * canvas)
+ *
+ * @return the image scale
+ */
public double getScale() {
return mHScale.getScale();
}
- /* package */ void setScale(double scale, boolean redraw) {
+ void setScale(double scale, boolean redraw) {
if (scale <= 0.0) {
scale = 1.0;
}
@@ -746,8 +841,13 @@ public class LayoutCanvas extends Canvas {
* @param onlyZoomOut if true, then the zooming factor will never be larger than 1,
* which means that this function will zoom out if necessary to show the
* rendered image, but it will never zoom in.
+ * TODO: Rename this, it sounds like it conflicts with allowZoomIn,
+ * even though one is referring to the zoom level and one is referring
+ * to the overall act of scaling above/below 1.
+ * @param allowZoomIn if false, then if the computed zoom factor is smaller than
+ * the current zoom factor, it will be ignored.
*/
- void setFitScale(boolean onlyZoomOut) {
+ public void setFitScale(boolean onlyZoomOut, boolean allowZoomIn) {
ImageOverlay imageOverlay = getImageOverlay();
if (imageOverlay == null) {
return;
@@ -758,6 +858,14 @@ public class LayoutCanvas extends Canvas {
int canvasWidth = canvasSize.width;
int canvasHeight = canvasSize.height;
+ boolean hasPreviews = mPreviewManager.hasPreviews();
+ if (hasPreviews) {
+ canvasWidth = 2 * canvasWidth / 3;
+ } else {
+ canvasWidth -= 4;
+ canvasHeight -= 4;
+ }
+
ImageData imageData = image.getImageData();
int sceneWidth = imageData.width;
int sceneHeight = imageData.height;
@@ -796,6 +904,10 @@ public class LayoutCanvas extends Canvas {
scale = Math.min(1.0, scale);
}
+ if (!allowZoomIn && scale > getScale()) {
+ return;
+ }
+
setScale(scale, true);
}
}
@@ -808,7 +920,7 @@ public class LayoutCanvas extends Canvas {
* @param canvasY Y in the canvas coordinates
* @return A new {@link Point} in control client coordinates (not display coordinates)
*/
- /* package */ Point layoutToControlPoint(int canvasX, int canvasY) {
+ Point layoutToControlPoint(int canvasX, int canvasY) {
int x = mHScale.translate(canvasX);
int y = mVScale.translate(canvasY);
return new Point(x, y);
@@ -823,7 +935,7 @@ public class LayoutCanvas extends Canvas {
* <p/>
* Returns null if there's no action for the given id.
*/
- /* package */ IAction getAction(String actionId) {
+ IAction getAction(String actionId) {
String prefix = PREFIX_CANVAS_ACTION;
if (mMenuManager == null ||
actionId == null ||
@@ -857,6 +969,8 @@ public class LayoutCanvas extends Canvas {
mImageOverlay.paint(gc);
}
+ mPreviewManager.paint(gc);
+
if (mShowOutline) {
if (mOutlineOverlay == null) {
mOutlineOverlay = new OutlineOverlay(mViewHierarchy, mHScale, mVScale);
@@ -907,7 +1021,7 @@ public class LayoutCanvas extends Canvas {
* @param show When true, any invisible parent nodes are padded and highlighted
* ("exploded"), and when false any formerly exploded nodes are hidden.
*/
- /* package */ void showInvisibleViews(boolean show) {
+ void showInvisibleViews(boolean show) {
if (mShowInvisible == show) {
return;
}
@@ -968,14 +1082,14 @@ public class LayoutCanvas extends Canvas {
/**
* Clears the hover.
*/
- /* package */ void clearHover() {
+ void clearHover() {
mHoverOverlay.clearHover();
}
/**
* Hover on top of a known child.
*/
- /* package */ void hover(MouseEvent e) {
+ void hover(MouseEvent e) {
// Check if a button is pressed; no hovers during drags
if ((e.stateMask & SWT.BUTTON_MASK) != 0) {
clearHover();
@@ -1027,7 +1141,7 @@ public class LayoutCanvas extends Canvas {
* @param url The layout attribute url of the form @layout/foo
*/
private void showInclude(String url) {
- GraphicalEditorPart graphicalEditor = mEditorDelegate.getGraphicalEditor();
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
IPath filePath = graphicalEditor.findResourceFile(url);
if (filePath == null) {
// Should not be possible - if the URL had been bad, then we wouldn't
@@ -1074,8 +1188,7 @@ public class LayoutCanvas extends Canvas {
try {
// Set initial state of a new file
// TODO: Only set rendering target portion of the state
- QualifiedName qname = ConfigurationChooser.NAME_CONFIG_STATE;
- String state = AdtPlugin.getFileProperty(leavingFile, qname);
+ String state = ConfigurationDescription.getDescription(leavingFile);
xmlFile.setSessionProperty(GraphicalEditorPart.NAME_INITIAL_STATE,
state);
} catch (CoreException e) {
@@ -1127,7 +1240,7 @@ public class LayoutCanvas extends Canvas {
* @return the layout resource name of this layout
*/
public String getLayoutResourceName() {
- GraphicalEditorPart graphicalEditor = mEditorDelegate.getGraphicalEditor();
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
return graphicalEditor.getLayoutResourceName();
}
@@ -1138,7 +1251,7 @@ public class LayoutCanvas extends Canvas {
*/
/*
public String getMe() {
- GraphicalEditorPart graphicalEditor = mEditorDelegate.getGraphicalEditor();
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
IFile editedFile = graphicalEditor.getEditedFile();
return editedFile.getProjectRelativePath().toOSString();
}
@@ -1265,11 +1378,11 @@ public class LayoutCanvas extends Canvas {
copyActionAttributes(mSelectAllAction, ActionFactory.SELECT_ALL);
}
- /* package */ String getCutLabel() {
+ String getCutLabel() {
return mCutAction.getText();
}
- /* package */ String getDeleteLabel() {
+ String getDeleteLabel() {
// verb "Delete" from the DELETE action's title
return mDeleteAction.getText();
}
@@ -1284,7 +1397,7 @@ public class LayoutCanvas extends Canvas {
hasSelection = false;
}
- StyledText errorLabel = mEditorDelegate.getGraphicalEditor().getErrorLabel();
+ StyledText errorLabel = getGraphicalEditor().getErrorLabel();
mCutAction.setEnabled(hasSelection);
mCopyAction.setEnabled(hasSelection || errorLabel.getSelectionCount() > 0);
mDeleteAction.setEnabled(hasSelection);
@@ -1427,7 +1540,7 @@ public class LayoutCanvas extends Canvas {
private void setupStaticMenuActions(IMenuManager manager) {
manager.removeAll();
- manager.add(new SelectionManager.SelectionMenu(mEditorDelegate.getGraphicalEditor()));
+ manager.add(new SelectionManager.SelectionMenu(getGraphicalEditor()));
manager.add(new Separator());
manager.add(mCutAction);
manager.add(mCopyAction);
@@ -1455,7 +1568,7 @@ public class LayoutCanvas extends Canvas {
/**
* Deletes the selection. Equivalent to pressing the Delete key.
*/
- /* package */ void delete() {
+ void delete() {
mDeleteAction.run();
}
@@ -1470,16 +1583,16 @@ public class LayoutCanvas extends Canvas {
* This is invoked by
* {@link MoveGesture#drop(org.eclipse.swt.dnd.DropTargetEvent)}.
*
- * @param rootFqcn A non-null non-empty FQCN that must match an existing
- * {@link ViewElementDescriptor} to add as root to the current
- * empty XML document.
+ * @param root A non-null descriptor of the root element to create.
*/
- /* package */ void createDocumentRoot(String rootFqcn) {
+ void createDocumentRoot(final @NonNull SimpleElement root) {
+ String rootFqcn = root.getFqcn();
// Need a valid empty document to create the new root
final UiDocumentNode uiDoc = mEditorDelegate.getUiRootNode();
if (uiDoc == null || uiDoc.getUiChildren().size() > 0) {
- debugPrintf("Failed to create document root for %1$s: document is not empty", rootFqcn);
+ debugPrintf("Failed to create document root for %1$s: document is not empty",
+ rootFqcn);
return;
}
@@ -1511,6 +1624,16 @@ public class LayoutCanvas extends Canvas {
SdkConstants.NS_RESOURCES,
true /*override*/);
+ IDragAttribute[] attributes = root.getAttributes();
+ if (attributes != null) {
+ for (IDragAttribute attribute : attributes) {
+ String uri = attribute.getUri();
+ String name = attribute.getName();
+ String value = attribute.getValue();
+ uiNew.setAttributeValue(name, uri, value, false /*override*/);
+ }
+ }
+
// Adjust the attributes
DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/);
@@ -1528,8 +1651,7 @@ public class LayoutCanvas extends Canvas {
*/
public Margins getInsets(String fqcn) {
if (ViewMetadataRepository.INSETS_SUPPORTED) {
- ConfigurationChooser configComposite =
- mEditorDelegate.getGraphicalEditor().getConfigurationChooser();
+ ConfigurationChooser configComposite = getGraphicalEditor().getConfigurationChooser();
String theme = configComposite.getThemeName();
Density density = configComposite.getConfiguration().getDensity();
return ViewMetadataRepository.getInsets(fqcn, density, theme);
@@ -1552,4 +1674,47 @@ public class LayoutCanvas extends Canvas {
mLintTooltipManager.hide();
}
}
+
+ /** @see #setPreview(RenderPreview) */
+ private RenderPreview mPreview;
+
+ /**
+ * Sets the {@link RenderPreview} associated with the currently rendering
+ * configuration.
+ * <p>
+ * A {@link RenderPreview} has various additional state beyond its rendering,
+ * such as its display name (which can be edited by the user). When you click on
+ * previews, the layout editor switches to show the given configuration preview.
+ * The preview is then no longer shown in the list of previews and is instead rendered
+ * in the main editor. However, when you then switch away to some other preview, we
+ * want to be able to restore the preview with all its state.
+ *
+ * @param preview the preview associated with the current canvas
+ */
+ public void setPreview(@Nullable RenderPreview preview) {
+ mPreview = preview;
+ }
+
+ /**
+ * Returns the {@link RenderPreview} associated with this layout canvas.
+ *
+ * @see #setPreview(RenderPreview)
+ * @return the {@link RenderPreview}
+ */
+ @Nullable
+ public RenderPreview getPreview() {
+ return mPreview;
+ }
+
+ /** Ensures that the configuration previews are up to date for this canvas */
+ public void syncPreviewMode() {
+ if (mImageOverlay != null && mImageOverlay.getImage() != null &&
+ getGraphicalEditor().getConfigurationChooser().getResources() != null) {
+ if (mPreviewManager.recomputePreviews(false)) {
+ // Zoom when syncing modes
+ mZoomFitNextImage = true;
+ ensureZoomed();
+ }
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java
index a164e3d..b79e3b0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutMetadata.java
@@ -16,44 +16,46 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.SdkConstants.ANDROID_LAYOUT_RESOURCE_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_NUM_COLUMNS;
import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW;
import static com.android.SdkConstants.GRID_VIEW;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VALUE_AUTO_FIT;
-
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.AdapterBinding;
import com.android.ide.common.rendering.api.DataBindingItem;
import com.android.ide.common.rendering.api.ResourceReference;
+import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
-
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.progress.WorkbenchJob;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParser;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
/**
* Design-time metadata lookup for layouts, such as fragment and AdapterView bindings.
*/
-@SuppressWarnings("restriction") // XML DOM model
public class LayoutMetadata {
/** The default layout to use for list items in expandable list views */
public static final String DEFAULT_EXPANDABLE_LIST_ITEM = "simple_expandable_list_item_2"; //$NON-NLS-1$
@@ -64,8 +66,6 @@ public class LayoutMetadata {
/** The string to start metadata comments with */
private static final String COMMENT_PROLOGUE = " Preview: ";
- /** The string to end metadata comments with */
- private static final String COMMENT_EPILOGUE = " ";
/** The property key, included in comments, which references a list item layout */
public static final String KEY_LV_ITEM = "listitem"; //$NON-NLS-1$
/** The property key, included in comments, which references a list header layout */
@@ -74,49 +74,14 @@ public class LayoutMetadata {
public static final String KEY_LV_FOOTER = "listfooter"; //$NON-NLS-1$
/** The property key, included in comments, which references a fragment layout to show */
public static final String KEY_FRAGMENT_LAYOUT = "layout"; //$NON-NLS-1$
+ // NOTE: If you add additional keys related to resources, make sure you update the
+ // ResourceRenameParticipant
/** Utility class, do not create instances */
private LayoutMetadata() {
}
/**
- * Returns the given property of the given DOM node, or null
- *
- * @param document the document to look up and read lock the model for
- * @param node the XML node to associate metadata with
- * @param name the name of the property to look up
- * @return the value stored with the given node and name, or null
- * @deprecated this method gets metadata using the old comment-based style; should
- * only be used for migration at this point
- */
- @Deprecated
- @Nullable
- public static String getProperty(
- @Nullable IDocument document,
- @NonNull Node node,
- @NonNull String name) {
- IStructuredModel model = null;
- try {
- if (document != null) {
- IModelManager modelManager = StructuredModelManager.getModelManager();
- model = modelManager.getExistingModelForRead(document);
- }
-
- Node comment = findComment(node);
- if (comment != null) {
- String text = comment.getNodeValue();
- return getProperty(name, text);
- }
-
- return null;
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
- }
-
- /**
* Returns the given property specified in the <b>current</b> element being
* processed by the given pull parser.
*
@@ -136,124 +101,25 @@ public class LayoutMetadata {
}
/**
- * Returns the given property specified in the given XML comment
+ * Clears the old metadata from the given node
*
- * @param name the name of the property to look up
- * @param text the comment text for an XML node
- * @return the value stored with the given node and name, or null
- */
- public static String getProperty(String name, String text) {
- assert text.startsWith(COMMENT_PROLOGUE);
- String valuesString = text.substring(COMMENT_PROLOGUE.length());
- String[] values = valuesString.split(","); //$NON-NLS-1$
- if (values.length == 1) {
- valuesString = values[0].trim();
- if (valuesString.indexOf('\n') != -1) {
- values = valuesString.split("\n"); //$NON-NLS-1$
- }
- }
- String target = name + '=';
- for (int j = 0; j < values.length; j++) {
- String value = values[j].trim();
- if (value.startsWith(target)) {
- return value.substring(target.length()).trim();
- }
- }
- return null;
- }
-
- /**
- * Sets the given property of the given DOM node to a given value, or if null clears
- * the property.
- *
- * @param document the document to look up and write lock the model for
* @param node the XML node to associate metadata with
- * @param name the name of the property to set
- * @param value the value to store for the given node and name, or null to remove it
- * @deprecated this method sets metadata using the old comment-based style; should
- * only be used for migration at this point
+ * @deprecated this method clears metadata using the old comment-based style;
+ * should only be used for migration at this point
*/
@Deprecated
- public static void setProperty(IDocument document, Node node, String name, String value) {
- // Reserved characters: [,-=]
- assert name.indexOf('-') == -1;
- assert value == null || value.indexOf('-') == -1;
- assert name.indexOf(',') == -1;
- assert value == null || value.indexOf(',') == -1;
- assert name.indexOf('=') == -1;
- assert value == null || value.indexOf('=') == -1;
-
- IStructuredModel model = null;
- try {
- IModelManager modelManager = StructuredModelManager.getModelManager();
- model = modelManager.getExistingModelForEdit(document);
- if (model instanceof IDOMModel) {
- IDOMModel domModel = (IDOMModel) model;
- Document domDocument = domModel.getDocument();
- assert node.getOwnerDocument() == domDocument;
- }
-
- Document doc = node.getOwnerDocument();
- Node commentNode = findComment(node);
-
- String commentText = null;
- if (commentNode != null) {
- String text = commentNode.getNodeValue();
- assert text.startsWith(COMMENT_PROLOGUE);
- String valuesString = text.substring(COMMENT_PROLOGUE.length());
- String[] values = valuesString.split(","); //$NON-NLS-1$
- if (values.length == 1) {
- valuesString = values[0].trim();
- if (valuesString.indexOf('\n') != -1) {
- values = valuesString.split("\n"); //$NON-NLS-1$
- }
- }
- String target = name + '=';
- List<String> preserve = new ArrayList<String>();
- for (int j = 0; j < values.length; j++) {
- String v = values[j].trim();
- if (v.length() == 0) {
- continue;
- }
- if (!v.startsWith(target)) {
- preserve.add(v.trim());
- }
- }
- if (value != null) {
- preserve.add(name + '=' + value.trim());
- }
- if (preserve.size() > 0) {
- if (preserve.size() > 1) {
- Collections.sort(preserve);
- String firstLineIndent = AndroidXmlEditor.getIndent(document, commentNode);
- String oneIndentLevel = " "; //$NON-NLS-1$
- StringBuilder sb = new StringBuilder();
- sb.append(COMMENT_PROLOGUE);
- sb.append('\n');
- for (String s : preserve) {
- sb.append(firstLineIndent);
- sb.append(oneIndentLevel);
- sb.append(s);
- sb.append('\n');
- }
- sb.append(firstLineIndent);
- sb.append(COMMENT_EPILOGUE);
- commentText = sb.toString();
- } else {
- commentText = COMMENT_PROLOGUE + preserve.get(0) + COMMENT_EPILOGUE;
- }
- }
- } else if (value != null) {
- commentText = COMMENT_PROLOGUE + name + '=' + value + COMMENT_EPILOGUE;
- }
-
- if (commentText == null) {
- if (commentNode != null) {
+ public static void clearLegacyComment(Node node) {
+ NodeList children = node.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.COMMENT_NODE) {
+ String text = child.getNodeValue();
+ if (text.startsWith(COMMENT_PROLOGUE)) {
+ Node commentNode = child;
// Remove the comment, along with surrounding whitespace if applicable
Node previous = commentNode.getPreviousSibling();
if (previous != null && previous.getNodeType() == Node.TEXT_NODE) {
- String text = previous.getNodeValue();
- if (text.trim().length() == 0) {
+ if (previous.getNodeValue().trim().length() == 0) {
node.removeChild(previous);
}
}
@@ -261,53 +127,13 @@ public class LayoutMetadata {
Node first = node.getFirstChild();
if (first != null && first.getNextSibling() == null
&& first.getNodeType() == Node.TEXT_NODE) {
- String text = first.getNodeValue();
- if (text.trim().length() == 0) {
+ if (first.getNodeValue().trim().length() == 0) {
node.removeChild(first);
}
}
}
- return;
- }
-
- if (commentNode != null) {
- commentNode.setNodeValue(commentText);
- } else {
- commentNode = doc.createComment(commentText);
- String firstLineIndent = AndroidXmlEditor.getIndent(document, node);
- Node firstChild = node.getFirstChild();
- boolean indentAfter = firstChild == null
- || firstChild.getNodeType() != Node.TEXT_NODE
- || firstChild.getNodeValue().indexOf('\n') == -1;
- String oneIndentLevel = " "; //$NON-NLS-1$
- node.insertBefore(doc.createTextNode('\n' + firstLineIndent + oneIndentLevel),
- firstChild);
- node.insertBefore(commentNode, firstChild);
- if (indentAfter) {
- node.insertBefore(doc.createTextNode('\n' + firstLineIndent), firstChild);
- }
- }
- } finally {
- if (model != null) {
- model.releaseFromEdit();
- }
- }
- }
-
- /** Finds the comment node associated with the given node, or null if not found */
- private static Node findComment(Node node) {
- NodeList children = node.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.COMMENT_NODE) {
- String text = child.getNodeValue();
- if (text.startsWith(COMMENT_PROLOGUE)) {
- return child;
- }
}
}
-
- return null;
}
/**
@@ -344,19 +170,115 @@ public class LayoutMetadata {
* @param value the value to store for the given node and name, or null to remove it
*/
public static void setProperty(
- @NonNull AndroidXmlEditor editor,
+ @NonNull final AndroidXmlEditor editor,
@NonNull final Node node,
@NonNull final String name,
@Nullable final String value) {
// Clear out the old metadata
- IDocument document = editor.getStructuredSourceViewer().getDocument();
- setProperty(document, node, name, null);
+ clearLegacyComment(node);
if (node.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element) node;
- AdtUtils.setToolsAttribute(editor, element, "Bind View", name, value,
+ final Element element = (Element) node;
+ final String undoLabel = "Bind View";
+ AdtUtils.setToolsAttribute(editor, element, undoLabel, name, value,
false /*reveal*/, false /*append*/);
+
+ // Also apply the same layout to any corresponding elements in other configurations
+ // of this layout.
+ final IFile file = editor.getInputFile();
+ if (file != null) {
+ final List<IFile> variations = AdtUtils.getResourceVariations(file, false);
+ if (variations.isEmpty()) {
+ return;
+ }
+ Display display = AdtPlugin.getDisplay();
+ WorkbenchJob job = new WorkbenchJob(display, "Update alternate views") {
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ for (IFile variation : variations) {
+ if (variation.equals(file)) {
+ continue;
+ }
+ try {
+ // If the corresponding file is open in the IDE, use the
+ // editor version instead
+ if (!AdtPrefs.getPrefs().isSharedLayoutEditor()) {
+ if (setPropertyInEditor(undoLabel, variation, element, name,
+ value)) {
+ return Status.OK_STATUS;
+ }
+ }
+
+ boolean old = editor.getIgnoreXmlUpdate();
+ try {
+ editor.setIgnoreXmlUpdate(true);
+ setPropertyInFile(undoLabel, variation, element, name, value);
+ } finally {
+ editor.setIgnoreXmlUpdate(old);
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, variation.getFullPath().toOSString());
+ }
+ }
+ return Status.OK_STATUS;
+ }
+
+ };
+ job.setSystem(true);
+ job.schedule();
+ }
+ }
+ }
+
+ private static boolean setPropertyInEditor(
+ @NonNull String undoLabel,
+ @NonNull IFile variation,
+ @NonNull final Element equivalentElement,
+ @NonNull final String name,
+ @Nullable final String value) {
+ Collection<IEditorPart> editors =
+ AdtUtils.findEditorsFor(variation, false /*restore*/);
+ for (IEditorPart part : editors) {
+ AndroidXmlEditor editor = AdtUtils.getXmlEditor(part);
+ if (editor != null) {
+ Document doc = DomUtilities.getDocument(editor);
+ if (doc != null) {
+ Element element = DomUtilities.findCorresponding(equivalentElement, doc);
+ if (element != null) {
+ AdtUtils.setToolsAttribute(editor, element, undoLabel, name,
+ value, false /*reveal*/, false /*append*/);
+ if (part instanceof GraphicalEditorPart) {
+ GraphicalEditorPart g = (GraphicalEditorPart) part;
+ g.recomputeLayout();
+ g.getCanvasControl().redraw();
+ }
+ return true;
+ }
+ }
+ }
}
+
+ return false;
+ }
+
+ private static boolean setPropertyInFile(
+ @NonNull String undoLabel,
+ @NonNull IFile variation,
+ @NonNull final Element element,
+ @NonNull final String name,
+ @Nullable final String value) {
+ Document doc = DomUtilities.getDocument(variation);
+ if (doc != null && element.getOwnerDocument() != doc) {
+ Element other = DomUtilities.findCorresponding(element, doc);
+ if (other != null) {
+ AdtUtils.setToolsAttribute(variation, other, undoLabel,
+ name, value, false);
+
+ return true;
+ }
+ }
+
+ return false;
}
/** Strips out @layout/ or @android:layout/ from the given layout reference */
@@ -375,10 +297,36 @@ public class LayoutMetadata {
* has not yet chosen a target layout to use for the given AdapterView.
*
* @param viewObject the view object to create an adapter binding for
+ * @param map a map containing tools attribute metadata
+ * @return a binding, or null
+ */
+ @Nullable
+ public static AdapterBinding getNodeBinding(
+ @Nullable Object viewObject,
+ @NonNull Map<String, String> map) {
+ String header = map.get(KEY_LV_HEADER);
+ String footer = map.get(KEY_LV_FOOTER);
+ String layout = map.get(KEY_LV_ITEM);
+ if (layout != null || header != null || footer != null) {
+ int count = 12;
+ return getNodeBinding(viewObject, header, footer, layout, count);
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates an {@link AdapterBinding} for the given view object, or null if the user
+ * has not yet chosen a target layout to use for the given AdapterView.
+ *
+ * @param viewObject the view object to create an adapter binding for
* @param uiNode the ui node corresponding to the view object
* @return a binding, or null
*/
- public static AdapterBinding getNodeBinding(Object viewObject, UiViewElementNode uiNode) {
+ @Nullable
+ public static AdapterBinding getNodeBinding(
+ @Nullable Object viewObject,
+ @NonNull UiViewElementNode uiNode) {
Node xmlNode = uiNode.getXmlNode();
String header = getProperty(xmlNode, KEY_LV_HEADER);
@@ -392,14 +340,30 @@ public class LayoutMetadata {
Element element = (Element) xmlNode;
String columns = element.getAttributeNS(ANDROID_URI, ATTR_NUM_COLUMNS);
int multiplier = 2;
- if (columns != null && columns.length() > 0) {
- int c = Integer.parseInt(columns);
- if (c >= 1 && c <= 10) {
- multiplier = c;
+ if (columns != null && columns.length() > 0 &&
+ !columns.equals(VALUE_AUTO_FIT)) {
+ try {
+ int c = Integer.parseInt(columns);
+ if (c >= 1 && c <= 10) {
+ multiplier = c;
+ }
+ } catch (NumberFormatException nufe) {
+ // some unexpected numColumns value: just stick with 2 columns for
+ // preview purposes
}
}
count *= multiplier;
}
+
+ return getNodeBinding(viewObject, header, footer, layout, count);
+ }
+
+ return null;
+ }
+
+ private static AdapterBinding getNodeBinding(Object viewObject,
+ String header, String footer, String layout, int count) {
+ if (layout != null || header != null || footer != null) {
AdapterBinding binding = new AdapterBinding(count);
if (header != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java
index d40af29..56b86aa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java
@@ -232,7 +232,10 @@ public class LayoutWindowCoordinator implements IPartListener2 {
*
* @param editor the editor to sync
*/
- private void sync(GraphicalEditorPart editor) {
+ private void sync(@Nullable GraphicalEditorPart editor) {
+ if (editor == null) {
+ return;
+ }
if (mEditorMaximized) {
editor.showStructureViews(true /*outline*/, true /*properties*/, true /*layout*/);
} else if (mOutlineOpen) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
index 7ddb372..bce1512 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
@@ -75,6 +75,12 @@ public class LintOverlay extends Overlay {
CanvasTransform mHScale = mCanvas.getHorizontalTransform();
CanvasTransform mVScale = mCanvas.getVerticalTransform();
+ // Right/bottom edges of the canvas image; don't paint overlays outside of
+ // that. (With for example RelativeLayouts with margins rendered on smaller
+ // screens than they are intended for this can happen.)
+ int maxX = mHScale.translate(0) + mHScale.getScaledImgSize();
+ int maxY = mVScale.translate(0) + mVScale.getScaledImgSize();
+
int oldAlpha = gc.getAlpha();
try {
gc.setAlpha(ALPHA);
@@ -95,6 +101,10 @@ public class LintOverlay extends Overlay {
x += w - iconWidth;
y += h - iconHeight;
+ if (x > maxX || y > maxY) {
+ continue;
+ }
+
boolean isError = false;
IMarker marker = editor.getIssueForNode(vi.getUiViewNode());
if (marker != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java
index 076b11a..4577f8d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ListViewTypeMenu.java
@@ -23,24 +23,19 @@ import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutMet
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.Capability;
-import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
-import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.ui.ResourceChooser;
import com.android.resources.ResourceType;
-import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.Separator;
-import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Menu;
-import org.eclipse.swt.widgets.Shell;
import org.w3c.dom.Node;
/**
@@ -171,33 +166,12 @@ public class ListViewTypeMenu extends SubmenuAction {
@Override
public void run() {
LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
- IProject project = delegate.getEditor().getProject();
- // get the resource repository for this project and the system resources.
- ResourceRepository projectRepository = ResourceManager.getInstance()
- .getProjectResources(project);
- Shell shell = mCanvas.getShell();
-
- AndroidTargetData data = delegate.getEditor().getTargetData();
- ResourceRepository systemRepository = data.getFrameworkResources();
-
- ResourceChooser dlg = new ResourceChooser(project,
- ResourceType.LAYOUT, projectRepository,
- systemRepository, shell);
-
- IInputValidator validator =
- CyclicDependencyValidator.create(delegate.getEditor().getInputFile());
-
- if (validator != null) {
- // Ensure wide enough to accommodate validator error message
- dlg.setSize(85, 10);
- dlg.setInputValidator(validator);
- }
-
- String layout = getSelectedLayout();
- if (layout != null) {
- dlg.setCurrentResource(layout);
- }
-
+ IFile file = delegate.getEditor().getInputFile();
+ GraphicalEditorPart editor = delegate.getGraphicalEditor();
+ ResourceChooser dlg = ResourceChooser.create(editor, ResourceType.LAYOUT)
+ .setInputValidator(CyclicDependencyValidator.create(file))
+ .setInitialSize(85, 10)
+ .setCurrentResource(getSelectedLayout());
int result = dlg.open();
if (result == ResourceChooser.CLEAR_RETURN_CODE) {
setNewType(mType, null);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
index 1d87eb7..7cf3a64 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/MoveGesture.java
@@ -383,7 +383,10 @@ public class MoveGesture extends DropGesture {
}
// Make sure we aren't removing the same nodes that are being added
- assert !added.contains(child);
+ // No, that can happen when canceling out of a drop handler such as
+ // when dropping an included layout, then canceling out of the
+ // resource chooser.
+ //assert !added.contains(child);
}
}
};
@@ -829,9 +832,7 @@ public class MoveGesture extends DropGesture {
return;
}
- String rootFqcn = elements[0].getFqcn();
-
- mCanvas.createDocumentRoot(rootFqcn);
+ mCanvas.createDocumentRoot(elements[0]);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
index 119506b..46168b7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PaletteControl.java
@@ -378,6 +378,9 @@ public class PaletteControl extends Composite {
ConfigurationChooser configChooser = mEditor.getConfigurationChooser();
String theme = configChooser.getThemeName();
String device = configChooser.getDeviceName();
+ if (device == null) {
+ return;
+ }
AndroidTargetData targetData =
target != null ? Sdk.getCurrent().getTargetData(target) : null;
if (target == mCurrentTarget && targetData == mCurrentTargetData
@@ -997,14 +1000,11 @@ public class PaletteControl extends Composite {
// This is important since when we fill the size of certain views (like
// a SeekBar), we want it to at most be the width of the screen, and for small
// screens the RENDER_WIDTH was wider.
- Rect screenBounds = editor.getScreenBounds();
- int renderWidth = Math.min(screenBounds.w, MAX_RENDER_WIDTH);
- int renderHeight = Math.min(screenBounds.h, MAX_RENDER_HEIGHT);
LayoutLog silentLogger = new LayoutLog();
session = RenderService.create(editor)
.setModel(model)
- .setSize(renderWidth, renderHeight)
+ .setMaxRenderSize(MAX_RENDER_WIDTH, MAX_RENDER_HEIGHT)
.setLog(silentLogger)
.setOverrideBgColor(overrideBgColor)
.setDecorations(false)
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
index 60e9920..c92ce81 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/PreviewIconFactory.java
@@ -265,7 +265,7 @@ public class PreviewIconFactory {
session = RenderService.create(editor)
.setModel(model)
- .setSize(width, height)
+ .setOverrideRenderSize(width, height)
.setRenderingMode(RenderingMode.FULL_EXPAND)
.setLog(new RenderLogger("palette"))
.setOverrideBgColor(overrideBgColor)
@@ -377,11 +377,15 @@ public class PreviewIconFactory {
*
* @return a pair of possibly null color descriptions
*/
+ @NonNull
private Pair<RGB, RGB> getColorsFromTheme() {
RGB background = null;
RGB foreground = null;
ResourceResolver resources = mPalette.getEditor().getResourceResolver();
+ if (resources == null) {
+ return Pair.of(background, foreground);
+ }
StyleResourceValue theme = resources.getCurrentTheme();
if (theme != null) {
background = resolveThemeColor(resources, "windowBackground"); //$NON-NLS-1$
@@ -436,7 +440,7 @@ public class PreviewIconFactory {
ResourceResolver resources = editor.getResourceResolver();
ResourceValue resourceValue = resources.findItemInTheme(themeItemName);
BufferedImage image = RenderService.create(editor)
- .setSize(100, 100)
+ .setOverrideRenderSize(100, 100)
.renderDrawable(resourceValue);
if (image != null) {
// Use the middle pixel as the color since that works better for gradients;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java
new file mode 100644
index 0000000..07baaeb
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreview.java
@@ -0,0 +1,1333 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_RENDERING;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.DEFAULT;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.INCLUDES;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.ide.common.resources.ResourceFile;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.ScreenOrientationQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DocumentDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.VaryingConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
+import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.io.IAbstractFile;
+import com.android.resources.Density;
+import com.android.resources.ResourceType;
+import com.android.resources.ScreenOrientation;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.jobs.IJobChangeEvent;
+import org.eclipse.core.runtime.jobs.IJobChangeListener;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Region;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.progress.UIJob;
+import org.w3c.dom.Document;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.lang.ref.SoftReference;
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * Represents a preview rendering of a given configuration
+ */
+public class RenderPreview implements IJobChangeListener {
+ /** Whether previews should use large shadows */
+ static final boolean LARGE_SHADOWS = false;
+
+ /**
+ * Still doesn't work; get exceptions from layoutlib:
+ * java.lang.IllegalStateException: After scene creation, #init() must be called
+ * at com.android.layoutlib.bridge.impl.RenderAction.acquire(RenderAction.java:151)
+ * <p>
+ * TODO: Investigate.
+ */
+ private static final boolean RENDER_ASYNC = false;
+
+ /**
+ * Height of the toolbar shown over a preview during hover. Needs to be
+ * large enough to accommodate icons below.
+ */
+ private static final int HEADER_HEIGHT = 20;
+
+ /** Whether to dump out rendering failures of the previews to the log */
+ private static final boolean DUMP_RENDER_DIAGNOSTICS = false;
+
+ /** Extra error checking in debug mode */
+ private static final boolean DEBUG = false;
+
+ private static final Image EDIT_ICON;
+ private static final Image ZOOM_IN_ICON;
+ private static final Image ZOOM_OUT_ICON;
+ private static final Image CLOSE_ICON;
+ private static final int EDIT_ICON_WIDTH;
+ private static final int ZOOM_IN_ICON_WIDTH;
+ private static final int ZOOM_OUT_ICON_WIDTH;
+ private static final int CLOSE_ICON_WIDTH;
+ static {
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ IconFactory icons = IconFactory.getInstance();
+ CLOSE_ICON = sharedImages.getImage(ISharedImages.IMG_ETOOL_DELETE);
+ EDIT_ICON = icons.getIcon("editPreview"); //$NON-NLS-1$
+ ZOOM_IN_ICON = icons.getIcon("zoomplus"); //$NON-NLS-1$
+ ZOOM_OUT_ICON = icons.getIcon("zoomminus"); //$NON-NLS-1$
+ CLOSE_ICON_WIDTH = CLOSE_ICON.getImageData().width;
+ EDIT_ICON_WIDTH = EDIT_ICON.getImageData().width;
+ ZOOM_IN_ICON_WIDTH = ZOOM_IN_ICON.getImageData().width;
+ ZOOM_OUT_ICON_WIDTH = ZOOM_OUT_ICON.getImageData().width;
+ }
+
+ /** The configuration being previewed */
+ private @NonNull Configuration mConfiguration;
+
+ /** Configuration to use if we have an alternate input to be rendered */
+ private @NonNull Configuration mAlternateConfiguration;
+
+ /** The associated manager */
+ private final @NonNull RenderPreviewManager mManager;
+ private final @NonNull LayoutCanvas mCanvas;
+
+ private @NonNull SoftReference<ResourceResolver> mResourceResolver =
+ new SoftReference<ResourceResolver>(null);
+ private @Nullable Job mJob;
+ private @Nullable Image mThumbnail;
+ private @Nullable String mDisplayName;
+ private int mWidth;
+ private int mHeight;
+ private int mX;
+ private int mY;
+ private int mTitleHeight;
+ private double mScale = 1.0;
+ private double mAspectRatio;
+
+ /** If non null, points to a separate file containing the source */
+ private @Nullable IFile mAlternateInput;
+
+ /** If included within another layout, the name of that outer layout */
+ private @Nullable Reference mIncludedWithin;
+
+ /** Whether the mouse is actively hovering over this preview */
+ private boolean mActive;
+
+ /**
+ * Whether this preview cannot be rendered because of a model error - such
+ * as an invalid configuration, a missing resource, an error in the XML
+ * markup, etc. If non null, contains the error message (or a blank string
+ * if not known), and null if the render was successful.
+ */
+ private String mError;
+
+ /** Whether in the current layout, this preview is visible */
+ private boolean mVisible;
+
+ /** Whether the configuration has changed and needs to be refreshed the next time
+ * this preview made visible. This corresponds to the change flags in
+ * {@link ConfigurationClient}. */
+ private int mDirty;
+
+ /**
+ * Creates a new {@linkplain RenderPreview}
+ *
+ * @param manager the manager
+ * @param canvas canvas where preview is painted
+ * @param configuration the associated configuration
+ * @param width the initial width to use for the preview
+ * @param height the initial height to use for the preview
+ */
+ private RenderPreview(
+ @NonNull RenderPreviewManager manager,
+ @NonNull LayoutCanvas canvas,
+ @NonNull Configuration configuration) {
+ mManager = manager;
+ mCanvas = canvas;
+ mConfiguration = configuration;
+ updateSize();
+
+ // Should only attempt to create configurations for fully configured devices
+ assert mConfiguration.getDevice() != null
+ && mConfiguration.getDeviceState() != null
+ && mConfiguration.getLocale() != null
+ && mConfiguration.getTarget() != null
+ && mConfiguration.getTheme() != null
+ && mConfiguration.getFullConfig() != null
+ && mConfiguration.getFullConfig().getScreenSizeQualifier() != null :
+ mConfiguration;
+ }
+
+ /**
+ * Sets the configuration to use for this preview
+ *
+ * @param configuration the new configuration
+ */
+ public void setConfiguration(@NonNull Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ /**
+ * Gets the scale being applied to the thumbnail
+ *
+ * @return the scale being applied to the thumbnail
+ */
+ public double getScale() {
+ return mScale;
+ }
+
+ /**
+ * Sets the scale to apply to the thumbnail
+ *
+ * @param scale the factor to scale the thumbnail picture by
+ */
+ public void setScale(double scale) {
+ disposeThumbnail();
+ mScale = scale;
+ }
+
+ /**
+ * Returns the aspect ratio of this render preview
+ *
+ * @return the aspect ratio
+ */
+ public double getAspectRatio() {
+ return mAspectRatio;
+ }
+
+ /**
+ * Returns whether the preview is actively hovered
+ *
+ * @return whether the mouse is hovering over the preview
+ */
+ public boolean isActive() {
+ return mActive;
+ }
+
+ /**
+ * Sets whether the preview is actively hovered
+ *
+ * @param active if the mouse is hovering over the preview
+ */
+ public void setActive(boolean active) {
+ mActive = active;
+ }
+
+ /**
+ * Returns whether the preview is visible. Previews that are off
+ * screen are typically marked invisible during layout, which means we don't
+ * have to expend effort computing preview thumbnails etc
+ *
+ * @return true if the preview is visible
+ */
+ public boolean isVisible() {
+ return mVisible;
+ }
+
+ /**
+ * Returns whether this preview represents a forked layout
+ *
+ * @return true if this preview represents a separate file
+ */
+ public boolean isForked() {
+ return mAlternateInput != null || mIncludedWithin != null;
+ }
+
+ /**
+ * Returns the file to be used for this preview, or null if this is not a
+ * forked layout meaning that the file is the one used in the chooser
+ *
+ * @return the file or null for non-forked layouts
+ */
+ @Nullable
+ public IFile getAlternateInput() {
+ if (mAlternateInput != null) {
+ return mAlternateInput;
+ } else if (mIncludedWithin != null) {
+ return mIncludedWithin.getFile();
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the area of this render preview, PRIOR to scaling
+ *
+ * @return the area (width times height without scaling)
+ */
+ int getArea() {
+ return mWidth * mHeight;
+ }
+
+ /**
+ * Sets whether the preview is visible. Previews that are off
+ * screen are typically marked invisible during layout, which means we don't
+ * have to expend effort computing preview thumbnails etc
+ *
+ * @param visible whether this preview is visible
+ */
+ public void setVisible(boolean visible) {
+ if (visible != mVisible) {
+ mVisible = visible;
+ if (mVisible) {
+ if (mDirty != 0) {
+ // Just made the render preview visible:
+ configurationChanged(mDirty); // schedules render
+ } else {
+ updateForkStatus();
+ mManager.scheduleRender(this);
+ }
+ } else {
+ dispose();
+ }
+ }
+ }
+
+ /**
+ * Sets the layout position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ void setPosition(int x, int y) {
+ mX = x;
+ mY = y;
+ }
+
+ /**
+ * Gets the layout X position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ int getX() {
+ return mX;
+ }
+
+ /**
+ * Gets the layout Y position relative to the top left corner of the preview
+ * area, in control coordinates
+ */
+ int getY() {
+ return mY;
+ }
+
+ /** Determine whether this configuration has a better match in a different layout file */
+ private void updateForkStatus() {
+ ConfigurationChooser chooser = mManager.getChooser();
+ FolderConfiguration config = mConfiguration.getFullConfig();
+ if (mAlternateInput != null && chooser.isBestMatchFor(mAlternateInput, config)) {
+ return;
+ }
+
+ mAlternateInput = null;
+ IFile editedFile = chooser.getEditedFile();
+ if (editedFile != null) {
+ if (!chooser.isBestMatchFor(editedFile, config)) {
+ ProjectResources resources = chooser.getResources();
+ if (resources != null) {
+ ResourceFile best = resources.getMatchingFile(editedFile.getName(),
+ ResourceType.LAYOUT, config);
+ if (best != null) {
+ IAbstractFile file = best.getFile();
+ if (file instanceof IFileWrapper) {
+ mAlternateInput = ((IFileWrapper) file).getIFile();
+ } else if (file instanceof File) {
+ mAlternateInput = AdtUtils.fileToIFile(((File) file));
+ }
+ }
+ }
+ if (mAlternateInput != null) {
+ mAlternateConfiguration = Configuration.create(mConfiguration,
+ mAlternateInput);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a new {@linkplain RenderPreview}
+ *
+ * @param manager the manager
+ * @param configuration the associated configuration
+ * @return a new configuration
+ */
+ @NonNull
+ public static RenderPreview create(
+ @NonNull RenderPreviewManager manager,
+ @NonNull Configuration configuration) {
+ LayoutCanvas canvas = manager.getCanvas();
+ return new RenderPreview(manager, canvas, configuration);
+ }
+
+ /**
+ * Throws away this preview: cancels any pending rendering jobs and disposes
+ * of image resources etc
+ */
+ public void dispose() {
+ disposeThumbnail();
+
+ if (mJob != null) {
+ mJob.cancel();
+ mJob = null;
+ }
+ }
+
+ /** Disposes the thumbnail rendering. */
+ void disposeThumbnail() {
+ if (mThumbnail != null) {
+ mThumbnail.dispose();
+ mThumbnail = null;
+ }
+ }
+
+ /**
+ * Returns the display name of this preview
+ *
+ * @return the name of the preview
+ */
+ @NonNull
+ public String getDisplayName() {
+ if (mDisplayName == null) {
+ String displayName = getConfiguration().getDisplayName();
+ if (displayName == null) {
+ // No display name: this must be the configuration used by default
+ // for the view which is originally displayed (before adding thumbnails),
+ // and you've switched away to something else; now we need to display a name
+ // for this original configuration. For now, just call it "Original"
+ return "Original";
+ }
+
+ return displayName;
+ }
+
+ return mDisplayName;
+ }
+
+ /**
+ * Sets the display name of this preview. By default, the display name is
+ * the display name of the configuration, but it can be overridden by calling
+ * this setter (which only sets the preview name, without editing the configuration.)
+ *
+ * @param displayName the new display name
+ */
+ public void setDisplayName(@NonNull String displayName) {
+ mDisplayName = displayName;
+ }
+
+ /**
+ * Sets an inclusion context to use for this layout, if any. This will render
+ * the configuration preview as the outer layout with the current layout
+ * embedded within.
+ *
+ * @param includedWithin a reference to a layout which includes this one
+ */
+ public void setIncludedWithin(Reference includedWithin) {
+ mIncludedWithin = includedWithin;
+ }
+
+ /**
+ * Request a new render after the given delay
+ *
+ * @param delay the delay to wait before starting the render job
+ */
+ public void render(long delay) {
+ Job job = mJob;
+ if (job != null) {
+ job.cancel();
+ }
+ if (RENDER_ASYNC) {
+ job = new AsyncRenderJob();
+ } else {
+ job = new RenderJob();
+ }
+ job.schedule(delay);
+ job.addJobChangeListener(this);
+ mJob = job;
+ }
+
+ /** Render immediately */
+ private void renderSync() {
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ if (editor.getReadyLayoutLib(false /*displayError*/) == null) {
+ // Don't attempt to render when there is no ready layout library: most likely
+ // the targets are loading/reloading.
+ return;
+ }
+
+ disposeThumbnail();
+
+ Configuration configuration =
+ mAlternateInput != null && mAlternateConfiguration != null
+ ? mAlternateConfiguration : mConfiguration;
+ ResourceResolver resolver = getResourceResolver(configuration);
+ RenderService renderService = RenderService.create(editor, configuration, resolver);
+
+ if (mIncludedWithin != null) {
+ renderService.setIncludedWithin(mIncludedWithin);
+ }
+
+ if (mAlternateInput != null) {
+ IAndroidTarget target = editor.getRenderingTarget();
+ AndroidTargetData data = null;
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ data = sdk.getTargetData(target);
+ }
+ }
+
+ // Construct UI model from XML
+ DocumentDescriptor documentDescriptor;
+ if (data == null) {
+ documentDescriptor = new DocumentDescriptor("temp", null);//$NON-NLS-1$
+ } else {
+ documentDescriptor = data.getLayoutDescriptors().getDescriptor();
+ }
+ UiDocumentNode model = (UiDocumentNode) documentDescriptor.createUiNode();
+ model.setEditor(mCanvas.getEditorDelegate().getEditor());
+ model.setUnknownDescriptorProvider(editor.getModel().getUnknownDescriptorProvider());
+
+ Document document = DomUtilities.getDocument(mAlternateInput);
+ if (document == null) {
+ mError = "No document";
+ createErrorThumbnail();
+ return;
+ }
+ model.loadFromXmlNode(document);
+ renderService.setModel(model);
+ } else {
+ renderService.setModel(editor.getModel());
+ }
+ RenderLogger log = new RenderLogger(getDisplayName());
+ renderService.setLog(log);
+ RenderSession session = renderService.createRenderSession();
+ Result render = session.render(1000);
+
+ if (DUMP_RENDER_DIAGNOSTICS) {
+ if (log.hasProblems() || !render.isSuccess()) {
+ AdtPlugin.log(IStatus.ERROR, "Found problems rendering preview "
+ + getDisplayName() + ": "
+ + render.getErrorMessage() + " : "
+ + log.getProblems(false));
+ Throwable exception = render.getException();
+ if (exception != null) {
+ AdtPlugin.log(exception, "Failure rendering preview " + getDisplayName());
+ }
+ }
+ }
+
+ if (render.isSuccess()) {
+ mError = null;
+ } else {
+ mError = render.getErrorMessage();
+ if (mError == null) {
+ mError = "";
+ }
+ }
+
+ if (render.getStatus() == Status.ERROR_TIMEOUT) {
+ // TODO: Special handling? schedule update again later
+ return;
+ }
+ if (render.isSuccess()) {
+ BufferedImage image = session.getImage();
+ if (image != null) {
+ createThumbnail(image);
+ }
+ }
+
+ if (mError != null) {
+ createErrorThumbnail();
+ }
+ }
+
+ private ResourceResolver getResourceResolver(Configuration configuration) {
+ ResourceResolver resourceResolver = mResourceResolver.get();
+ if (resourceResolver != null) {
+ return resourceResolver;
+ }
+
+ GraphicalEditorPart graphicalEditor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ String theme = configuration.getTheme();
+ if (theme == null) {
+ return null;
+ }
+
+ Map<ResourceType, Map<String, ResourceValue>> configuredFrameworkRes = null;
+ Map<ResourceType, Map<String, ResourceValue>> configuredProjectRes = null;
+
+ FolderConfiguration config = configuration.getFullConfig();
+ IAndroidTarget target = graphicalEditor.getRenderingTarget();
+ ResourceRepository frameworkRes = null;
+ if (target != null) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk == null) {
+ return null;
+ }
+ AndroidTargetData data = sdk.getTargetData(target);
+
+ if (data != null) {
+ // TODO: SHARE if possible
+ frameworkRes = data.getFrameworkResources();
+ configuredFrameworkRes = frameworkRes.getConfiguredResources(config);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ assert configuredFrameworkRes != null;
+
+
+ // get the resources of the file's project.
+ ProjectResources projectRes = ResourceManager.getInstance().getProjectResources(
+ graphicalEditor.getProject());
+ configuredProjectRes = projectRes.getConfiguredResources(config);
+
+ if (!theme.startsWith(PREFIX_RESOURCE_REF)) {
+ if (frameworkRes.hasResourceItem(ANDROID_STYLE_RESOURCE_PREFIX + theme)) {
+ theme = ANDROID_STYLE_RESOURCE_PREFIX + theme;
+ } else {
+ theme = STYLE_RESOURCE_PREFIX + theme;
+ }
+ }
+
+ resourceResolver = ResourceResolver.create(
+ configuredProjectRes, configuredFrameworkRes,
+ ResourceHelper.styleToTheme(theme),
+ ResourceHelper.isProjectStyle(theme));
+ mResourceResolver = new SoftReference<ResourceResolver>(resourceResolver);
+ return resourceResolver;
+ }
+
+ /**
+ * Sets the new image of the preview and generates a thumbnail
+ *
+ * @param image the full size image
+ */
+ void createThumbnail(BufferedImage image) {
+ if (image == null) {
+ mThumbnail = null;
+ return;
+ }
+
+ ImageOverlay imageOverlay = mCanvas.getImageOverlay();
+ boolean drawShadows = imageOverlay == null || imageOverlay.getShowDropShadow();
+ double scale = getWidth() / (double) image.getWidth();
+ int shadowSize;
+ if (LARGE_SHADOWS) {
+ shadowSize = drawShadows ? SHADOW_SIZE : 0;
+ } else {
+ shadowSize = drawShadows ? SMALL_SHADOW_SIZE : 0;
+ }
+ if (scale < 1.0) {
+ if (LARGE_SHADOWS) {
+ image = ImageUtils.scale(image, scale, scale,
+ shadowSize, shadowSize);
+ if (drawShadows) {
+ ImageUtils.drawRectangleShadow(image, 0, 0,
+ image.getWidth() - shadowSize,
+ image.getHeight() - shadowSize);
+ }
+ } else {
+ image = ImageUtils.scale(image, scale, scale,
+ shadowSize, shadowSize);
+ if (drawShadows) {
+ ImageUtils.drawSmallRectangleShadow(image, 0, 0,
+ image.getWidth() - shadowSize,
+ image.getHeight() - shadowSize);
+ }
+ }
+ }
+
+ mThumbnail = SwtUtils.convertToSwt(mCanvas.getDisplay(), image,
+ true /* transferAlpha */, -1);
+ }
+
+ void createErrorThumbnail() {
+ int shadowSize = LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE;
+ int width = getWidth();
+ int height = getHeight();
+ BufferedImage image = new BufferedImage(width + shadowSize, height + shadowSize,
+ BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g = image.createGraphics();
+ g.setColor(new java.awt.Color(0xfffbfcc6));
+ g.fillRect(0, 0, width, height);
+
+ g.dispose();
+
+ ImageOverlay imageOverlay = mCanvas.getImageOverlay();
+ boolean drawShadows = imageOverlay == null || imageOverlay.getShowDropShadow();
+ if (drawShadows) {
+ if (LARGE_SHADOWS) {
+ ImageUtils.drawRectangleShadow(image, 0, 0,
+ image.getWidth() - SHADOW_SIZE,
+ image.getHeight() - SHADOW_SIZE);
+ } else {
+ ImageUtils.drawSmallRectangleShadow(image, 0, 0,
+ image.getWidth() - SMALL_SHADOW_SIZE,
+ image.getHeight() - SMALL_SHADOW_SIZE);
+ }
+ }
+
+ mThumbnail = SwtUtils.convertToSwt(mCanvas.getDisplay(), image,
+ true /* transferAlpha */, -1);
+ }
+
+ private static double getScale(int width, int height) {
+ int maxWidth = RenderPreviewManager.getMaxWidth();
+ int maxHeight = RenderPreviewManager.getMaxHeight();
+ if (width > 0 && height > 0
+ && (width > maxWidth || height > maxHeight)) {
+ if (width >= height) { // landscape
+ return maxWidth / (double) width;
+ } else { // portrait
+ return maxHeight / (double) height;
+ }
+ }
+
+ return 1.0;
+ }
+
+ /**
+ * Returns the width of the preview, in pixels
+ *
+ * @return the width in pixels
+ */
+ public int getWidth() {
+ return (int) (mWidth * mScale * RenderPreviewManager.getScale());
+ }
+
+ /**
+ * Returns the height of the preview, in pixels
+ *
+ * @return the height in pixels
+ */
+ public int getHeight() {
+ return (int) (mHeight * mScale * RenderPreviewManager.getScale());
+ }
+
+ /**
+ * Handles clicks within the preview (x and y are positions relative within the
+ * preview
+ *
+ * @param x the x coordinate within the preview where the click occurred
+ * @param y the y coordinate within the preview where the click occurred
+ * @return true if this preview handled (and therefore consumed) the click
+ */
+ public boolean click(int x, int y) {
+ if (y >= mTitleHeight && y < mTitleHeight + HEADER_HEIGHT) {
+ int left = 0;
+ left += CLOSE_ICON_WIDTH;
+ if (x <= left) {
+ // Delete
+ mManager.deletePreview(this);
+ return true;
+ }
+ left += ZOOM_IN_ICON_WIDTH;
+ if (x <= left) {
+ // Zoom in
+ mScale = mScale * (1 / 0.5);
+ if (Math.abs(mScale-1.0) < 0.0001) {
+ mScale = 1.0;
+ }
+
+ render(0);
+ mManager.layout(true);
+ mCanvas.redraw();
+ return true;
+ }
+ left += ZOOM_OUT_ICON_WIDTH;
+ if (x <= left) {
+ // Zoom out
+ mScale = mScale * (0.5 / 1);
+ if (Math.abs(mScale-1.0) < 0.0001) {
+ mScale = 1.0;
+ }
+ render(0);
+
+ mManager.layout(true);
+ mCanvas.redraw();
+ return true;
+ }
+ left += EDIT_ICON_WIDTH;
+ if (x <= left) {
+ // Edit. For now, just rename
+ InputDialog d = new InputDialog(
+ AdtPlugin.getShell(),
+ "Rename Preview", // title
+ "Name:",
+ getDisplayName(),
+ null);
+ if (d.open() == Window.OK) {
+ String newName = d.getValue();
+ mConfiguration.setDisplayName(newName);
+ if (mDescription != null) {
+ mManager.rename(mDescription, newName);
+ }
+ mCanvas.redraw();
+ }
+
+ return true;
+ }
+
+ // Clicked anywhere else on header
+ // Perhaps open Edit dialog here?
+ }
+
+ mManager.switchTo(this);
+ return true;
+ }
+
+ /**
+ * Paints the preview at the given x/y position
+ *
+ * @param gc the graphics context to paint it into
+ * @param x the x coordinate to paint the preview at
+ * @param y the y coordinate to paint the preview at
+ */
+ void paint(GC gc, int x, int y) {
+ mTitleHeight = paintTitle(gc, x, y, true /*showFile*/);
+ y += mTitleHeight;
+ y += 2;
+
+ int width = getWidth();
+ int height = getHeight();
+ if (mThumbnail != null && mError == null) {
+ gc.drawImage(mThumbnail, x, y);
+
+ if (mActive) {
+ int oldWidth = gc.getLineWidth();
+ gc.setLineWidth(3);
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_LIST_SELECTION));
+ gc.drawRectangle(x - 1, y - 1, width + 2, height + 2);
+ gc.setLineWidth(oldWidth);
+ }
+ } else if (mError != null) {
+ if (mThumbnail != null) {
+ gc.drawImage(mThumbnail, x, y);
+ } else {
+ gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER));
+ gc.drawRectangle(x, y, width, height);
+ }
+
+ gc.setClipping(x, y, width, height);
+ Image icon = IconFactory.getInstance().getIcon("renderError"); //$NON-NLS-1$
+ ImageData data = icon.getImageData();
+ int prevAlpha = gc.getAlpha();
+ int alpha = 96;
+ if (mThumbnail != null) {
+ alpha -= 32;
+ }
+ gc.setAlpha(alpha);
+ gc.drawImage(icon, x + (width - data.width) / 2, y + (height - data.height) / 2);
+
+ String msg = mError;
+ Density density = mConfiguration.getDensity();
+ if (density == Density.TV || density == Density.LOW) {
+ msg = "Broken rendering library; unsupported DPI. Try using the SDK manager " +
+ "to get updated layout libraries.";
+ }
+ int charWidth = gc.getFontMetrics().getAverageCharWidth();
+ int charsPerLine = (width - 10) / charWidth;
+ msg = SdkUtils.wrap(msg, charsPerLine, null);
+ gc.setAlpha(255);
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK));
+ gc.drawText(msg, x + 5, y + HEADER_HEIGHT, true);
+ gc.setAlpha(prevAlpha);
+ gc.setClipping((Region) null);
+ } else {
+ gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_WIDGET_BORDER));
+ gc.drawRectangle(x, y, width, height);
+
+ Image icon = IconFactory.getInstance().getIcon("refreshPreview"); //$NON-NLS-1$
+ ImageData data = icon.getImageData();
+ int prevAlpha = gc.getAlpha();
+ gc.setAlpha(96);
+ gc.drawImage(icon, x + (width - data.width) / 2,
+ y + (height - data.height) / 2);
+ gc.setAlpha(prevAlpha);
+ }
+
+ if (mActive) {
+ int left = x ;
+ int prevAlpha = gc.getAlpha();
+ gc.setAlpha(208);
+ Color bg = mCanvas.getDisplay().getSystemColor(SWT.COLOR_WHITE);
+ gc.setBackground(bg);
+ gc.fillRectangle(left, y, x + width - left, HEADER_HEIGHT);
+ gc.setAlpha(prevAlpha);
+
+ y += 2;
+
+ // Paint icons
+ gc.drawImage(CLOSE_ICON, left, y);
+ left += CLOSE_ICON_WIDTH;
+
+ gc.drawImage(ZOOM_IN_ICON, left, y);
+ left += ZOOM_IN_ICON_WIDTH;
+
+ gc.drawImage(ZOOM_OUT_ICON, left, y);
+ left += ZOOM_OUT_ICON_WIDTH;
+
+ gc.drawImage(EDIT_ICON, left, y);
+ left += EDIT_ICON_WIDTH;
+ }
+ }
+
+ /**
+ * Paints the preview title at the given position (and returns the required
+ * height)
+ *
+ * @param gc the graphics context to paint into
+ * @param x the left edge of the preview rectangle
+ * @param y the top edge of the preview rectangle
+ */
+ private int paintTitle(GC gc, int x, int y, boolean showFile) {
+ String displayName = getDisplayName();
+ return paintTitle(gc, x, y, showFile, displayName);
+ }
+
+ /**
+ * Paints the preview title at the given position (and returns the required
+ * height)
+ *
+ * @param gc the graphics context to paint into
+ * @param x the left edge of the preview rectangle
+ * @param y the top edge of the preview rectangle
+ * @param displayName the title string to be used
+ */
+ int paintTitle(GC gc, int x, int y, boolean showFile, String displayName) {
+ int titleHeight = 0;
+
+ if (showFile && mIncludedWithin != null) {
+ if (mManager.getMode() != INCLUDES) {
+ displayName = "<include>";
+ } else {
+ // Skip: just paint footer instead
+ displayName = null;
+ }
+ }
+
+ int width = getWidth();
+ int labelTop = y + 1;
+ gc.setClipping(x, labelTop, width, 100);
+
+ // Use font height rather than extent height since we want two adjacent
+ // previews (which may have different display names and therefore end
+ // up with slightly different extent heights) to have identical title
+ // heights such that they are aligned identically
+ int fontHeight = gc.getFontMetrics().getHeight();
+
+ if (displayName != null && displayName.length() > 0) {
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE));
+ Point extent = gc.textExtent(displayName);
+ int labelLeft = Math.max(x, x + (width - extent.x) / 2);
+ Image icon = null;
+ Locale locale = mConfiguration.getLocale();
+ if (locale != null && (locale.hasLanguage() || locale.hasRegion())
+ && (!(mConfiguration instanceof NestedConfiguration)
+ || ((NestedConfiguration) mConfiguration).isOverridingLocale())) {
+ icon = locale.getFlagImage();
+ }
+
+ if (icon != null) {
+ int flagWidth = icon.getImageData().width;
+ int flagHeight = icon.getImageData().height;
+ labelLeft = Math.max(x + flagWidth / 2, labelLeft);
+ gc.drawImage(icon, labelLeft - flagWidth / 2 - 1, labelTop);
+ labelLeft += flagWidth / 2 + 1;
+ gc.drawText(displayName, labelLeft,
+ labelTop - (extent.y - flagHeight) / 2, true);
+ } else {
+ gc.drawText(displayName, labelLeft, labelTop, true);
+ }
+
+ labelTop += extent.y;
+ titleHeight += fontHeight;
+ }
+
+ if (showFile && (mAlternateInput != null || mIncludedWithin != null)) {
+ // Draw file flag, and parent folder name
+ IFile file = mAlternateInput != null
+ ? mAlternateInput : mIncludedWithin.getFile();
+ String fileName = file.getParent().getName() + File.separator
+ + file.getName();
+ Point extent = gc.textExtent(fileName);
+ Image icon = IconFactory.getInstance().getIcon("android_file"); //$NON-NLS-1$
+ int flagWidth = icon.getImageData().width;
+ int flagHeight = icon.getImageData().height;
+
+ int labelLeft = Math.max(x, x + (width - extent.x - flagWidth - 1) / 2);
+
+ gc.drawImage(icon, labelLeft, labelTop);
+
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
+ labelLeft += flagWidth + 1;
+ labelTop -= (extent.y - flagHeight) / 2;
+ gc.drawText(fileName, labelLeft, labelTop, true);
+
+ titleHeight += Math.max(titleHeight, icon.getImageData().height);
+ }
+
+ gc.setClipping((Region) null);
+
+ return titleHeight;
+ }
+
+ /**
+ * Notifies that the preview's configuration has changed.
+ *
+ * @param flags the change flags, a bitmask corresponding to the
+ * {@code CHANGE_} constants in {@link ConfigurationClient}
+ */
+ public void configurationChanged(int flags) {
+ if (!mVisible) {
+ mDirty |= flags;
+ return;
+ }
+
+ if ((flags & MASK_RENDERING) != 0) {
+ mResourceResolver.clear();
+ // Handle inheritance
+ mConfiguration.syncFolderConfig();
+ updateForkStatus();
+ updateSize();
+ }
+
+ // Sanity check to make sure things are working correctly
+ if (DEBUG) {
+ RenderPreviewMode mode = mManager.getMode();
+ if (mode == DEFAULT) {
+ assert mConfiguration instanceof VaryingConfiguration;
+ VaryingConfiguration config = (VaryingConfiguration) mConfiguration;
+ int alternateFlags = config.getAlternateFlags();
+ switch (alternateFlags) {
+ case Configuration.CFG_DEVICE_STATE: {
+ State configState = config.getDeviceState();
+ State chooserState = mManager.getChooser().getConfiguration()
+ .getDeviceState();
+ assert configState != null && chooserState != null;
+ assert !configState.getName().equals(chooserState.getName())
+ : configState.toString() + ':' + chooserState;
+
+ Device configDevice = config.getDevice();
+ Device chooserDevice = mManager.getChooser().getConfiguration()
+ .getDevice();
+ assert configDevice != null && chooserDevice != null;
+ assert configDevice == chooserDevice
+ : configDevice.toString() + ':' + chooserDevice;
+
+ break;
+ }
+ case Configuration.CFG_DEVICE: {
+ Device configDevice = config.getDevice();
+ Device chooserDevice = mManager.getChooser().getConfiguration()
+ .getDevice();
+ assert configDevice != null && chooserDevice != null;
+ assert configDevice != chooserDevice
+ : configDevice.toString() + ':' + chooserDevice;
+
+ State configState = config.getDeviceState();
+ State chooserState = mManager.getChooser().getConfiguration()
+ .getDeviceState();
+ assert configState != null && chooserState != null;
+ assert configState.getName().equals(chooserState.getName())
+ : configState.toString() + ':' + chooserState;
+
+ break;
+ }
+ case Configuration.CFG_LOCALE: {
+ Locale configLocale = config.getLocale();
+ Locale chooserLocale = mManager.getChooser().getConfiguration()
+ .getLocale();
+ assert configLocale != null && chooserLocale != null;
+ assert configLocale != chooserLocale
+ : configLocale.toString() + ':' + chooserLocale;
+ break;
+ }
+ default: {
+ // Some other type of override I didn't anticipate
+ assert false : alternateFlags;
+ }
+ }
+ }
+ }
+
+ mDirty = 0;
+ mManager.scheduleRender(this);
+ }
+
+ private void updateSize() {
+ Device device = mConfiguration.getDevice();
+ if (device == null) {
+ return;
+ }
+ Screen screen = device.getDefaultHardware().getScreen();
+ if (screen == null) {
+ return;
+ }
+
+ FolderConfiguration folderConfig = mConfiguration.getFullConfig();
+ ScreenOrientationQualifier qualifier = folderConfig.getScreenOrientationQualifier();
+ ScreenOrientation orientation = qualifier == null
+ ? ScreenOrientation.PORTRAIT : qualifier.getValue();
+
+ // compute width and height to take orientation into account.
+ int x = screen.getXDimension();
+ int y = screen.getYDimension();
+ int screenWidth, screenHeight;
+
+ if (x > y) {
+ if (orientation == ScreenOrientation.LANDSCAPE) {
+ screenWidth = x;
+ screenHeight = y;
+ } else {
+ screenWidth = y;
+ screenHeight = x;
+ }
+ } else {
+ if (orientation == ScreenOrientation.LANDSCAPE) {
+ screenWidth = y;
+ screenHeight = x;
+ } else {
+ screenWidth = x;
+ screenHeight = y;
+ }
+ }
+
+ int width = RenderPreviewManager.getMaxWidth();
+ int height = RenderPreviewManager.getMaxHeight();
+ if (screenWidth > 0) {
+ double scale = getScale(screenWidth, screenHeight);
+ width = (int) (screenWidth * scale);
+ height = (int) (screenHeight * scale);
+ }
+
+ if (width != mWidth || height != mHeight) {
+ mWidth = width;
+ mHeight = height;
+
+ Image thumbnail = mThumbnail;
+ mThumbnail = null;
+ if (thumbnail != null) {
+ thumbnail.dispose();
+ }
+ if (mHeight != 0) {
+ mAspectRatio = mWidth / (double) mHeight;
+ }
+ }
+ }
+
+ /**
+ * Returns the configuration associated with this preview
+ *
+ * @return the configuration
+ */
+ @NonNull
+ public Configuration getConfiguration() {
+ return mConfiguration;
+ }
+
+ // ---- Implements IJobChangeListener ----
+
+ @Override
+ public void aboutToRun(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void awake(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ mJob = null;
+ }
+
+ @Override
+ public void running(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void scheduled(IJobChangeEvent event) {
+ }
+
+ @Override
+ public void sleeping(IJobChangeEvent event) {
+ }
+
+ // ---- Delayed Rendering ----
+
+ private final class RenderJob extends UIJob {
+ public RenderJob() {
+ super("RenderPreview");
+ setSystem(true);
+ setUser(false);
+ }
+
+ @Override
+ public IStatus runInUIThread(IProgressMonitor monitor) {
+ mJob = null;
+ if (!mCanvas.isDisposed()) {
+ renderSync();
+ mCanvas.redraw();
+ return org.eclipse.core.runtime.Status.OK_STATUS;
+ }
+
+ return org.eclipse.core.runtime.Status.CANCEL_STATUS;
+ }
+
+ @Override
+ public Display getDisplay() {
+ if (mCanvas.isDisposed()) {
+ return null;
+ }
+ return mCanvas.getDisplay();
+ }
+ }
+
+ private final class AsyncRenderJob extends Job {
+ public AsyncRenderJob() {
+ super("RenderPreview");
+ setSystem(true);
+ setUser(false);
+ }
+
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ mJob = null;
+
+ if (mCanvas.isDisposed()) {
+ return org.eclipse.core.runtime.Status.CANCEL_STATUS;
+ }
+
+ renderSync();
+
+ // Update display
+ mCanvas.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ mCanvas.redraw();
+ }
+ });
+
+ return org.eclipse.core.runtime.Status.OK_STATUS;
+ }
+ }
+
+ /**
+ * Sets the input file to use for rendering. If not set, this will just be
+ * the same file as the configuration chooser. This is used to render other
+ * layouts, such as variations of the currently edited layout, which are
+ * not kept in sync with the main layout.
+ *
+ * @param file the file to set as input
+ */
+ public void setAlternateInput(@Nullable IFile file) {
+ mAlternateInput = file;
+ }
+
+ /** Corresponding description for this preview if it is a manually added preview */
+ private @Nullable ConfigurationDescription mDescription;
+
+ /**
+ * Sets the description of this preview, if this preview is a manually added preview
+ *
+ * @param description the description of this preview
+ */
+ public void setDescription(@Nullable ConfigurationDescription description) {
+ mDescription = description;
+ }
+
+ /**
+ * Returns the description of this preview, if this preview is a manually added preview
+ *
+ * @return the description
+ */
+ @Nullable
+ public ConfigurationDescription getDescription() {
+ return mDescription;
+ }
+
+ @Override
+ public String toString() {
+ return getDisplayName() + ':' + mConfiguration;
+ }
+
+ /** Sorts render previews into increasing aspect ratio order */
+ static Comparator<RenderPreview> INCREASING_ASPECT_RATIO = new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ return (int) Math.signum(preview1.mAspectRatio - preview2.mAspectRatio);
+ }
+ };
+ /** Sorts render previews into visual order: row by row, column by column */
+ static Comparator<RenderPreview> VISUAL_ORDER = new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ int delta = preview1.mY - preview2.mY;
+ if (delta == 0) {
+ delta = preview1.mX - preview2.mX;
+ }
+ return delta;
+ }
+ };
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java
new file mode 100644
index 0000000..f5d3290
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewList.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
+import com.android.sdklib.devices.Device;
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.io.Files;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.QualifiedName;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/** A list of render previews */
+class RenderPreviewList {
+ /** Name of file saved in project directory storing previews */
+ private static final String PREVIEW_FILE_NAME = "previews.xml"; //$NON-NLS-1$
+
+ /** Qualified name for the per-project persistent property include-map */
+ private final static QualifiedName PREVIEW_LIST = new QualifiedName(AdtPlugin.PLUGIN_ID,
+ "previewlist");//$NON-NLS-1$
+
+ private final IProject mProject;
+ private final List<ConfigurationDescription> mList = Lists.newArrayList();
+
+ private RenderPreviewList(@NonNull IProject project) {
+ mProject = project;
+ }
+
+ /**
+ * Returns the {@link RenderPreviewList} for the given project
+ *
+ * @param project the project the list is associated with
+ * @return a {@link RenderPreviewList} for the given project, never null
+ */
+ @NonNull
+ public static RenderPreviewList get(@NonNull IProject project) {
+ RenderPreviewList list = null;
+ try {
+ list = (RenderPreviewList) project.getSessionProperty(PREVIEW_LIST);
+ } catch (CoreException e) {
+ // Not a problem; we will just create a new one
+ }
+
+ if (list == null) {
+ list = new RenderPreviewList(project);
+ try {
+ project.setSessionProperty(PREVIEW_LIST, list);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ return list;
+ }
+
+ private File getManualFile() {
+ return new File(AdtUtils.getAbsolutePath(mProject).toFile(), PREVIEW_FILE_NAME);
+ }
+
+ void load(List<Device> deviceList) throws IOException {
+ File file = getManualFile();
+ if (file.exists()) {
+ load(file, deviceList);
+ }
+ }
+
+ void save() throws IOException {
+ deleteFile();
+ if (!mList.isEmpty()) {
+ File file = getManualFile();
+ save(file);
+ }
+ }
+
+ private void save(File file) throws IOException {
+ //Document document = DomUtilities.createEmptyPlainDocument();
+ Document document = DomUtilities.createEmptyDocument();
+ if (document != null) {
+ for (ConfigurationDescription description : mList) {
+ description.toXml(document);
+ }
+ String xml = EclipseXmlPrettyPrinter.prettyPrint(document, true);
+ Files.write(xml, file, Charsets.UTF_8);
+ }
+ }
+
+ void load(File file, List<Device> deviceList) throws IOException {
+ mList.clear();
+
+ String xml = Files.toString(file, Charsets.UTF_8);
+ Document document = DomUtilities.parseDocument(xml, true);
+ if (document == null || document.getDocumentElement() == null) {
+ return;
+ }
+ List<Element> elements = DomUtilities.getChildren(document.getDocumentElement());
+ for (Element element : elements) {
+ ConfigurationDescription description = ConfigurationDescription.fromXml(
+ mProject, element, deviceList);
+ if (description != null) {
+ mList.add(description);
+ }
+ }
+ }
+
+ /**
+ * Create a list of previews for the given canvas that matches the internal
+ * configuration preview list
+ *
+ * @param canvas the associated canvas
+ * @return a new list of previews linked to the given canvas
+ */
+ @NonNull
+ List<RenderPreview> createPreviews(LayoutCanvas canvas) {
+ if (mList.isEmpty()) {
+ return new ArrayList<RenderPreview>();
+ }
+ List<RenderPreview> previews = Lists.newArrayList();
+ RenderPreviewManager manager = canvas.getPreviewManager();
+ ConfigurationChooser chooser = canvas.getEditorDelegate().getGraphicalEditor()
+ .getConfigurationChooser();
+
+ Configuration chooserConfig = chooser.getConfiguration();
+ for (ConfigurationDescription description : mList) {
+ Configuration configuration = Configuration.create(chooser);
+ configuration.setDisplayName(description.displayName);
+ configuration.setActivity(description.activity);
+ configuration.setLocale(
+ description.locale != null ? description.locale : chooserConfig.getLocale(),
+ true);
+ // TODO: Make sure this layout isn't in some v-folder which is incompatible
+ // with this target!
+ configuration.setTarget(
+ description.target != null ? description.target : chooserConfig.getTarget(),
+ true);
+ configuration.setTheme(
+ description.theme != null ? description.theme : chooserConfig.getTheme());
+ configuration.setDevice(
+ description.device != null ? description.device : chooserConfig.getDevice(),
+ true);
+ configuration.setDeviceState(
+ description.state != null ? description.state : chooserConfig.getDeviceState(),
+ true);
+ configuration.setNightMode(
+ description.nightMode != null ? description.nightMode
+ : chooserConfig.getNightMode(), true);
+ configuration.setUiMode(
+ description.uiMode != null ? description.uiMode : chooserConfig.getUiMode(), true);
+
+ //configuration.syncFolderConfig();
+ configuration.getFullConfig().set(description.folder);
+
+ RenderPreview preview = RenderPreview.create(manager, configuration);
+
+ preview.setDescription(description);
+ previews.add(preview);
+ }
+
+ return previews;
+ }
+
+ void remove(@NonNull RenderPreview preview) {
+ ConfigurationDescription description = preview.getDescription();
+ if (description != null) {
+ mList.remove(description);
+ }
+ }
+
+ boolean isEmpty() {
+ return mList.isEmpty();
+ }
+
+ void add(@NonNull RenderPreview preview) {
+ Configuration configuration = preview.getConfiguration();
+ ConfigurationDescription description =
+ ConfigurationDescription.fromConfiguration(mProject, configuration);
+ // RenderPreviews can have display names that aren't reflected in the configuration
+ description.displayName = preview.getDisplayName();
+ mList.add(description);
+ preview.setDescription(description);
+ }
+
+ void delete() {
+ mList.clear();
+ deleteFile();
+ }
+
+ private void deleteFile() {
+ File file = getManualFile();
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java
new file mode 100644
index 0000000..4b0f484
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewManager.java
@@ -0,0 +1,1695 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.CFG_DEVICE_STATE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration.MASK_ALL;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils.SMALL_SHADOW_SIZE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreview.LARGE_SHADOWS;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.CUSTOM;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.NONE;
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode.SCREENS;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.api.Capability;
+import com.android.ide.common.resources.configuration.DensityQualifier;
+import com.android.ide.common.resources.configuration.DeviceConfigHelper;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationClient;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Locale;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.NestedConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.VaryingConfiguration;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.resources.Density;
+import com.android.resources.ScreenSize;
+import com.android.sdklib.devices.Device;
+import com.android.sdklib.devices.Screen;
+import com.android.sdklib.devices.State;
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.dialogs.InputDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.ScrollBar;
+import org.eclipse.ui.IWorkbenchPartSite;
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.ide.IDE;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Manager for the configuration previews, which handles layout computations,
+ * managing the image buffer cache, etc
+ */
+public class RenderPreviewManager {
+ private static double sScale = 1.0;
+ private static final int RENDER_DELAY = 150;
+ private static final int PREVIEW_VGAP = 18;
+ private static final int PREVIEW_HGAP = 12;
+ private static final int MAX_WIDTH = 200;
+ private static final int MAX_HEIGHT = MAX_WIDTH;
+ private static final int ZOOM_ICON_WIDTH = 16;
+ private static final int ZOOM_ICON_HEIGHT = 16;
+ private @Nullable List<RenderPreview> mPreviews;
+ private @Nullable RenderPreviewList mManualList;
+ private final @NonNull LayoutCanvas mCanvas;
+ private final @NonNull CanvasTransform mVScale;
+ private final @NonNull CanvasTransform mHScale;
+ private int mPrevCanvasWidth;
+ private int mPrevCanvasHeight;
+ private int mPrevImageWidth;
+ private int mPrevImageHeight;
+ private @NonNull RenderPreviewMode mMode = NONE;
+ private @Nullable RenderPreview mActivePreview;
+ private @Nullable ScrollBarListener mListener;
+ private int mLayoutHeight;
+ /** Last seen state revision in this {@link RenderPreviewManager}. If less
+ * than {@link #sRevision}, the previews need to be updated on next exposure */
+ private static int mRevision;
+ /** Current global revision count */
+ private static int sRevision;
+ private boolean mNeedLayout;
+ private boolean mNeedRender;
+ private boolean mNeedZoom;
+ private SwapAnimation mAnimation;
+
+ /**
+ * Creates a {@link RenderPreviewManager} associated with the given canvas
+ *
+ * @param canvas the canvas to manage previews for
+ */
+ public RenderPreviewManager(@NonNull LayoutCanvas canvas) {
+ mCanvas = canvas;
+ mHScale = canvas.getHorizontalTransform();
+ mVScale = canvas.getVerticalTransform();
+ }
+
+ /**
+ * Revise the global state revision counter. This will cause all layout
+ * preview managers to refresh themselves to the latest revision when they
+ * are next exposed.
+ */
+ public static void bumpRevision() {
+ sRevision++;
+ }
+
+ /**
+ * Returns the associated chooser
+ *
+ * @return the associated chooser
+ */
+ @NonNull
+ ConfigurationChooser getChooser() {
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ return editor.getConfigurationChooser();
+ }
+
+ /**
+ * Returns the associated canvas
+ *
+ * @return the canvas
+ */
+ @NonNull
+ public LayoutCanvas getCanvas() {
+ return mCanvas;
+ }
+
+ /** Zooms in (grows all previews) */
+ public void zoomIn() {
+ sScale = sScale * (1 / 0.9);
+ if (Math.abs(sScale-1.0) < 0.0001) {
+ sScale = 1.0;
+ }
+
+ updatedZoom();
+ }
+
+ /** Zooms out (shrinks all previews) */
+ public void zoomOut() {
+ sScale = sScale * (0.9 / 1);
+ if (Math.abs(sScale-1.0) < 0.0001) {
+ sScale = 1.0;
+ }
+ updatedZoom();
+ }
+
+ /** Zooms to 100 (resets zoom) */
+ public void zoomReset() {
+ sScale = 1.0;
+ updatedZoom();
+ mNeedZoom = mNeedLayout = true;
+ mCanvas.redraw();
+ }
+
+ private void updatedZoom() {
+ if (hasPreviews()) {
+ for (RenderPreview preview : mPreviews) {
+ preview.disposeThumbnail();
+ }
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ preview.disposeThumbnail();
+ }
+ }
+
+ mNeedLayout = mNeedRender = true;
+ mCanvas.redraw();
+ }
+
+ static int getMaxWidth() {
+ return (int) (sScale * MAX_WIDTH);
+ }
+
+ static int getMaxHeight() {
+ return (int) (sScale * MAX_HEIGHT);
+ }
+
+ static double getScale() {
+ return sScale;
+ }
+
+ /**
+ * Returns whether there are any manual preview items (provided the current
+ * mode is manual previews
+ *
+ * @return true if there are items in the manual preview list
+ */
+ public boolean hasManualPreviews() {
+ assert mMode == CUSTOM;
+ return mManualList != null && !mManualList.isEmpty();
+ }
+
+ /** Delete all the previews */
+ public void deleteManualPreviews() {
+ disposePreviews();
+ selectMode(NONE);
+ mCanvas.setFitScale(true /* onlyZoomOut */, true /*allowZoomIn*/);
+
+ if (mManualList != null) {
+ mManualList.delete();
+ }
+ }
+
+ /** Dispose all the previews */
+ public void disposePreviews() {
+ if (mPreviews != null) {
+ List<RenderPreview> old = mPreviews;
+ mPreviews = null;
+ for (RenderPreview preview : old) {
+ preview.dispose();
+ }
+ }
+ }
+
+ /**
+ * Deletes the given preview
+ *
+ * @param preview the preview to be deleted
+ */
+ public void deletePreview(RenderPreview preview) {
+ mPreviews.remove(preview);
+ preview.dispose();
+ layout(true);
+ mCanvas.redraw();
+
+ if (mManualList != null) {
+ mManualList.remove(preview);
+ saveList();
+ }
+ }
+
+ /**
+ * Compute the total width required for the previews, including internal padding
+ *
+ * @return total width in pixels
+ */
+ public int computePreviewWidth() {
+ int maxPreviewWidth = 0;
+ if (hasPreviews()) {
+ for (RenderPreview preview : mPreviews) {
+ maxPreviewWidth = Math.max(maxPreviewWidth, preview.getWidth());
+ }
+
+ if (maxPreviewWidth > 0) {
+ maxPreviewWidth += 2 * PREVIEW_HGAP; // 2x for left and right side
+ maxPreviewWidth += LARGE_SHADOWS ? SHADOW_SIZE : SMALL_SHADOW_SIZE;
+ }
+
+ return maxPreviewWidth;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Layout Algorithm. This sets the {@link RenderPreview#getX()} and
+ * {@link RenderPreview#getY()} coordinates of all the previews. It also
+ * marks previews as visible or invisible via
+ * {@link RenderPreview#setVisible(boolean)} according to their position and
+ * the current visible view port in the layout canvas. Finally, it also sets
+ * the {@code mLayoutHeight} field, such that the scrollbars can compute the
+ * right scrolled area, and that scrolling can cause render refreshes on
+ * views that are made visible.
+ * <p>
+ * This is not a traditional bin packing problem, because the objects to be
+ * packaged do not have a fixed size; we can scale them up and down in order
+ * to provide an "optimal" size.
+ * <p>
+ * See http://en.wikipedia.org/wiki/Packing_problem See
+ * http://en.wikipedia.org/wiki/Bin_packing_problem
+ */
+ void layout(boolean refresh) {
+ mNeedLayout = false;
+
+ if (mPreviews == null || mPreviews.isEmpty()) {
+ return;
+ }
+
+ int scaledImageWidth = mHScale.getScaledImgSize();
+ int scaledImageHeight = mVScale.getScaledImgSize();
+ Rectangle clientArea = mCanvas.getClientArea();
+
+ if (!refresh &&
+ (scaledImageWidth == mPrevImageWidth
+ && scaledImageHeight == mPrevImageHeight
+ && clientArea.width == mPrevCanvasWidth
+ && clientArea.height == mPrevCanvasHeight)) {
+ // No change
+ return;
+ }
+
+ mPrevImageWidth = scaledImageWidth;
+ mPrevImageHeight = scaledImageHeight;
+ mPrevCanvasWidth = clientArea.width;
+ mPrevCanvasHeight = clientArea.height;
+
+ if (mListener == null) {
+ mListener = new ScrollBarListener();
+ mCanvas.getVerticalBar().addSelectionListener(mListener);
+ }
+
+ beginRenderScheduling();
+
+ mLayoutHeight = 0;
+
+ if (previewsHaveIdenticalSize() || fixedOrder()) {
+ // If all the preview boxes are of identical sizes, or if the order is predetermined,
+ // just lay them out in rows.
+ rowLayout();
+ } else if (previewsFit()) {
+ layoutFullFit();
+ } else {
+ rowLayout();
+ }
+
+ mCanvas.updateScrollBars();
+ }
+
+ /**
+ * Performs a simple layout where the views are laid out in a row, wrapping
+ * around the top left canvas image.
+ */
+ private void rowLayout() {
+ // TODO: Separate layout heuristics for portrait and landscape orientations (though
+ // it also depends on the dimensions of the canvas window, which determines the
+ // shape of the leftover space)
+
+ int scaledImageWidth = mHScale.getScaledImgSize();
+ int scaledImageHeight = mVScale.getScaledImgSize();
+ Rectangle clientArea = mCanvas.getClientArea();
+
+ int availableWidth = clientArea.x + clientArea.width - getX();
+ int availableHeight = clientArea.y + clientArea.height - getY();
+ int maxVisibleY = clientArea.y + clientArea.height;
+
+ int bottomBorder = scaledImageHeight;
+ int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
+ int nextY = 0;
+
+ // First lay out images across the top right hand side
+ int x = rightHandSide;
+ int y = 0;
+ boolean wrapped = false;
+
+ int vgap = PREVIEW_VGAP;
+ for (RenderPreview preview : mPreviews) {
+ // If we have forked previews, double the vgap to allow space for two labels
+ if (preview.isForked()) {
+ vgap *= 2;
+ break;
+ }
+ }
+
+ List<RenderPreview> aspectOrder;
+ if (!fixedOrder()) {
+ aspectOrder = new ArrayList<RenderPreview>(mPreviews);
+ Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO);
+ } else {
+ aspectOrder = mPreviews;
+ }
+
+ for (RenderPreview preview : aspectOrder) {
+ if (x > 0 && x + preview.getWidth() > availableWidth) {
+ x = rightHandSide;
+ int prevY = y;
+ y = nextY;
+ if ((prevY <= bottomBorder ||
+ y <= bottomBorder)
+ && Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
+ // If there's really no visible room below, don't bother
+ // Similarly, don't wrap individually scaled views
+ if (bottomBorder < availableHeight - 40 && preview.getScale() < 1.2) {
+ // If it's closer to the top row than the bottom, just
+ // mark the next row for left justify instead
+ if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
+ rightHandSide = 0;
+ wrapped = true;
+ } else if (!wrapped) {
+ y = nextY = Math.max(nextY, bottomBorder + vgap);
+ x = rightHandSide = 0;
+ wrapped = true;
+ }
+ }
+ }
+ }
+ if (x > 0 && y <= bottomBorder
+ && Math.max(nextY, y + preview.getHeight()) > bottomBorder) {
+ if (clientArea.height - bottomBorder < preview.getHeight()) {
+ // No room below the device on the left; just continue on the
+ // bottom row
+ } else if (preview.getScale() < 1.2) {
+ if (bottomBorder - y > y + preview.getHeight() - bottomBorder) {
+ rightHandSide = 0;
+ wrapped = true;
+ } else {
+ y = nextY = Math.max(nextY, bottomBorder + vgap);
+ x = rightHandSide = 0;
+ wrapped = true;
+ }
+ }
+ }
+
+ preview.setPosition(x, y);
+
+ if (y > maxVisibleY && maxVisibleY > 0) {
+ preview.setVisible(false);
+ } else if (!preview.isVisible()) {
+ preview.setVisible(true);
+ }
+
+ x += preview.getWidth();
+ x += PREVIEW_HGAP;
+ nextY = Math.max(nextY, y + preview.getHeight() + vgap);
+ }
+
+ mLayoutHeight = nextY;
+ }
+
+ private boolean fixedOrder() {
+ return mMode == SCREENS;
+ }
+
+ /** Returns true if all the previews have the same identical size */
+ private boolean previewsHaveIdenticalSize() {
+ if (!hasPreviews()) {
+ return true;
+ }
+
+ Iterator<RenderPreview> iterator = mPreviews.iterator();
+ RenderPreview first = iterator.next();
+ int width = first.getWidth();
+ int height = first.getHeight();
+
+ while (iterator.hasNext()) {
+ RenderPreview preview = iterator.next();
+ if (width != preview.getWidth() || height != preview.getHeight()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /** Returns true if all the previews can fully fit in the available space */
+ private boolean previewsFit() {
+ int scaledImageWidth = mHScale.getScaledImgSize();
+ int scaledImageHeight = mVScale.getScaledImgSize();
+ Rectangle clientArea = mCanvas.getClientArea();
+ int availableWidth = clientArea.x + clientArea.width - getX();
+ int availableHeight = clientArea.y + clientArea.height - getY();
+ int bottomBorder = scaledImageHeight;
+ int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
+
+ // First see if we can fit everything; if so, we can try to make the layouts
+ // larger such that they fill up all the available space
+ long availableArea = rightHandSide * bottomBorder +
+ availableWidth * (Math.max(0, availableHeight - bottomBorder));
+
+ long requiredArea = 0;
+ for (RenderPreview preview : mPreviews) {
+ // Note: This does not include individual preview scale; the layout
+ // algorithm itself may be tweaking the scales to fit elements within
+ // the layout
+ requiredArea += preview.getArea();
+ }
+
+ return requiredArea * sScale < availableArea;
+ }
+
+ private void layoutFullFit() {
+ int scaledImageWidth = mHScale.getScaledImgSize();
+ int scaledImageHeight = mVScale.getScaledImgSize();
+ Rectangle clientArea = mCanvas.getClientArea();
+ int availableWidth = clientArea.x + clientArea.width - getX();
+ int availableHeight = clientArea.y + clientArea.height - getY();
+ int maxVisibleY = clientArea.y + clientArea.height;
+ int bottomBorder = scaledImageHeight;
+ int rightHandSide = scaledImageWidth + PREVIEW_HGAP;
+
+ int minWidth = Integer.MAX_VALUE;
+ int minHeight = Integer.MAX_VALUE;
+ for (RenderPreview preview : mPreviews) {
+ minWidth = Math.min(minWidth, preview.getWidth());
+ minHeight = Math.min(minHeight, preview.getHeight());
+ }
+
+ BinPacker packer = new BinPacker(minWidth, minHeight);
+
+ // TODO: Instead of this, just start with client area and occupy scaled image size!
+
+ // Add in gap on right and bottom since we'll add that requirement on the width and
+ // height rectangles too (for spacing)
+ packer.addSpace(new Rect(rightHandSide, 0,
+ availableWidth - rightHandSide + PREVIEW_HGAP,
+ availableHeight + PREVIEW_VGAP));
+ if (maxVisibleY > bottomBorder) {
+ packer.addSpace(new Rect(0, bottomBorder + PREVIEW_VGAP,
+ availableWidth + PREVIEW_HGAP, maxVisibleY - bottomBorder + PREVIEW_VGAP));
+ }
+
+ // TODO: Sort previews first before attempting to position them?
+
+ ArrayList<RenderPreview> aspectOrder = new ArrayList<RenderPreview>(mPreviews);
+ Collections.sort(aspectOrder, RenderPreview.INCREASING_ASPECT_RATIO);
+
+ for (RenderPreview preview : aspectOrder) {
+ int previewWidth = preview.getWidth();
+ int previewHeight = preview.getHeight();
+ previewHeight += PREVIEW_VGAP;
+ if (preview.isForked()) {
+ previewHeight += PREVIEW_VGAP;
+ }
+ previewWidth += PREVIEW_HGAP;
+ // title height? how do I account for that?
+ Rect position = packer.occupy(previewWidth, previewHeight);
+ if (position != null) {
+ preview.setPosition(position.x, position.y);
+ preview.setVisible(true);
+ } else {
+ // Can't fit: give up and do plain row layout
+ rowLayout();
+ return;
+ }
+ }
+
+ mLayoutHeight = availableHeight;
+ }
+ /**
+ * Paints the configuration previews
+ *
+ * @param gc the graphics context to paint into
+ */
+ void paint(GC gc) {
+ if (hasPreviews()) {
+ // Ensure up to date at all times; consider moving if it's too expensive
+ layout(mNeedLayout);
+ if (mNeedRender) {
+ renderPreviews();
+ }
+ if (mNeedZoom) {
+ boolean allowZoomIn = true /*mMode == NONE*/;
+ mCanvas.setFitScale(false /*onlyZoomOut*/, allowZoomIn);
+ mNeedZoom = false;
+ }
+ int rootX = getX();
+ int rootY = getY();
+
+ for (RenderPreview preview : mPreviews) {
+ if (preview.isVisible()) {
+ int x = rootX + preview.getX();
+ int y = rootY + preview.getY();
+ preview.paint(gc, x, y);
+ }
+ }
+
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ String displayName = null;
+ Configuration configuration = preview.getConfiguration();
+ if (configuration instanceof VaryingConfiguration) {
+ // Use override flags from stashed preview, but configuration
+ // data from live (not varying) configured configuration
+ VaryingConfiguration cfg = (VaryingConfiguration) configuration;
+ int flags = cfg.getAlternateFlags() | cfg.getOverrideFlags();
+ displayName = NestedConfiguration.computeDisplayName(flags,
+ getChooser().getConfiguration());
+ } else if (configuration instanceof NestedConfiguration) {
+ int flags = ((NestedConfiguration) configuration).getOverrideFlags();
+ displayName = NestedConfiguration.computeDisplayName(flags,
+ getChooser().getConfiguration());
+ } else {
+ displayName = configuration.getDisplayName();
+ }
+ if (displayName != null) {
+ CanvasTransform hi = mHScale;
+ CanvasTransform vi = mVScale;
+
+ int destX = hi.translate(0);
+ int destY = vi.translate(0);
+ int destWidth = hi.getScaledImgSize();
+ int destHeight = vi.getScaledImgSize();
+
+ int x = destX + destWidth / 2 - preview.getWidth() / 2;
+ int y = destY + destHeight;
+
+ preview.paintTitle(gc, x, y, false /*showFile*/, displayName);
+ }
+ }
+
+ // Zoom overlay
+ int x = getZoomX();
+ if (x > 0) {
+ int y = getZoomY();
+ int oldAlpha = gc.getAlpha();
+
+ // Paint background oval rectangle behind the zoom and close icons
+ gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
+ gc.setAlpha(128);
+ int padding = 3;
+ int arc = 5;
+ gc.fillRoundRectangle(x - padding, y - padding,
+ ZOOM_ICON_WIDTH + 2 * padding,
+ 4 * ZOOM_ICON_HEIGHT + 2 * padding, arc, arc);
+
+ gc.setAlpha(255);
+ IconFactory iconFactory = IconFactory.getInstance();
+ Image zoomOut = iconFactory.getIcon("zoomminus"); //$NON-NLS-1$);
+ Image zoomIn = iconFactory.getIcon("zoomplus"); //$NON-NLS-1$);
+ Image zoom100 = iconFactory.getIcon("zoom100"); //$NON-NLS-1$);
+ Image close = iconFactory.getIcon("close"); //$NON-NLS-1$);
+
+ gc.drawImage(zoomIn, x, y);
+ y += ZOOM_ICON_HEIGHT;
+ gc.drawImage(zoomOut, x, y);
+ y += ZOOM_ICON_HEIGHT;
+ gc.drawImage(zoom100, x, y);
+ y += ZOOM_ICON_HEIGHT;
+ gc.drawImage(close, x, y);
+ y += ZOOM_ICON_HEIGHT;
+ gc.setAlpha(oldAlpha);
+ }
+ } else if (mMode == CUSTOM) {
+ int rootX = getX();
+ rootX += mHScale.getScaledImgSize();
+ rootX += 2 * PREVIEW_HGAP;
+ int rootY = getY();
+ rootY += 20;
+ gc.setFont(mCanvas.getFont());
+ gc.setForeground(mCanvas.getDisplay().getSystemColor(SWT.COLOR_BLACK));
+ gc.drawText("Add previews with \"Add as Thumbnail\"\nin the configuration menu",
+ rootX, rootY, true);
+ }
+
+ if (mAnimation != null) {
+ mAnimation.tick(gc);
+ }
+ }
+
+ private void addPreview(@NonNull RenderPreview preview) {
+ if (mPreviews == null) {
+ mPreviews = Lists.newArrayList();
+ }
+ mPreviews.add(preview);
+ }
+
+ /** Adds the current configuration as a new configuration preview */
+ public void addAsThumbnail() {
+ ConfigurationChooser chooser = getChooser();
+ String name = chooser.getConfiguration().getDisplayName();
+ if (name == null || name.isEmpty()) {
+ name = getUniqueName();
+ }
+ InputDialog d = new InputDialog(
+ AdtPlugin.getShell(),
+ "Add as Thumbnail Preview", // title
+ "Name of thumbnail:",
+ name,
+ null);
+ if (d.open() == Window.OK) {
+ selectMode(CUSTOM);
+
+ String newName = d.getValue();
+ // Create a new configuration from the current settings in the composite
+ Configuration configuration = Configuration.copy(chooser.getConfiguration());
+ configuration.setDisplayName(newName);
+
+ RenderPreview preview = RenderPreview.create(this, configuration);
+ addPreview(preview);
+
+ layout(true);
+ beginRenderScheduling();
+ scheduleRender(preview);
+ mCanvas.setFitScale(true /* onlyZoomOut */, false /*allowZoomIn*/);
+
+ if (mManualList == null) {
+ loadList();
+ }
+ if (mManualList != null) {
+ mManualList.add(preview);
+ saveList();
+ }
+ }
+ }
+
+ /**
+ * Computes a unique new name for a configuration preview that represents
+ * the current, default configuration
+ *
+ * @return a unique name
+ */
+ private String getUniqueName() {
+ if (mPreviews == null || mPreviews.isEmpty()) {
+ // NO, not for the first preview!
+ return "Config1";
+ }
+
+ Set<String> names = new HashSet<String>(mPreviews.size());
+ for (RenderPreview preview : mPreviews) {
+ names.add(preview.getDisplayName());
+ }
+
+ int index = 2;
+ while (true) {
+ String name = String.format("Config%1$d", index);
+ if (!names.contains(name)) {
+ return name;
+ }
+ index++;
+ }
+ }
+
+ /** Generates a bunch of default configuration preview thumbnails */
+ public void addDefaultPreviews() {
+ ConfigurationChooser chooser = getChooser();
+ Configuration parent = chooser.getConfiguration();
+ if (parent instanceof NestedConfiguration) {
+ parent = ((NestedConfiguration) parent).getParent();
+ }
+ if (mCanvas.getImageOverlay().getImage() != null) {
+ // Create Language variation
+ createLocaleVariation(chooser, parent);
+
+ // Vary screen size
+ // TODO: Be smarter here: Pick a screen that is both as differently as possible
+ // from the current screen as well as also supported. So consider
+ // things like supported screens, targetSdk etc.
+ createScreenVariations(parent);
+
+ // Vary orientation
+ createStateVariation(chooser, parent);
+
+ // Vary render target
+ createRenderTargetVariation(chooser, parent);
+ }
+
+ // Also add in include-context previews, if any
+ addIncludedInPreviews();
+
+ // Make a placeholder preview for the current screen, in case we switch from it
+ RenderPreview preview = RenderPreview.create(this, parent);
+ mCanvas.setPreview(preview);
+
+ sortPreviewsByOrientation();
+ }
+
+ private void createRenderTargetVariation(ConfigurationChooser chooser, Configuration parent) {
+ /* This is disabled for now: need to load multiple versions of layoutlib.
+ When I did this, there seemed to be some drug interactions between
+ them, and I would end up with NPEs in layoutlib code which normally works.
+ VaryingConfiguration configuration =
+ VaryingConfiguration.create(chooser, parent);
+ configuration.setAlternatingTarget(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ */
+ }
+
+ private void createStateVariation(ConfigurationChooser chooser, Configuration parent) {
+ State currentState = parent.getDeviceState();
+ State nextState = parent.getNextDeviceState(currentState);
+ if (nextState != currentState) {
+ VaryingConfiguration configuration =
+ VaryingConfiguration.create(chooser, parent);
+ configuration.setAlternateDeviceState(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ }
+ }
+
+ private void createLocaleVariation(ConfigurationChooser chooser, Configuration parent) {
+ LanguageQualifier currentLanguage = parent.getLocale().language;
+ for (Locale locale : chooser.getLocaleList()) {
+ LanguageQualifier language = locale.language;
+ if (!language.equals(currentLanguage)) {
+ VaryingConfiguration configuration =
+ VaryingConfiguration.create(chooser, parent);
+ configuration.setAlternateLocale(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ break;
+ }
+ }
+ }
+
+ private void createScreenVariations(Configuration parent) {
+ ConfigurationChooser chooser = getChooser();
+ VaryingConfiguration configuration;
+
+ configuration = VaryingConfiguration.create(chooser, parent);
+ configuration.setVariation(0);
+ configuration.setAlternateDevice(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+
+ configuration = VaryingConfiguration.create(chooser, parent);
+ configuration.setVariation(1);
+ configuration.setAlternateDevice(true);
+ configuration.syncFolderConfig();
+ addPreview(RenderPreview.create(this, configuration));
+ }
+
+ /**
+ * Returns the current mode as seen by this {@link RenderPreviewManager}.
+ * Note that it may not yet have been synced with the global mode kept in
+ * {@link AdtPrefs#getRenderPreviewMode()}.
+ *
+ * @return the current preview mode
+ */
+ @NonNull
+ public RenderPreviewMode getMode() {
+ return mMode;
+ }
+
+ /**
+ * Update the set of previews for the current mode
+ *
+ * @param force force a refresh even if the preview type has not changed
+ * @return true if the views were recomputed, false if the previews were
+ * already showing and the mode not changed
+ */
+ public boolean recomputePreviews(boolean force) {
+ RenderPreviewMode newMode = AdtPrefs.getPrefs().getRenderPreviewMode();
+ if (newMode == mMode && !force
+ && (mRevision == sRevision
+ || mMode == NONE
+ || mMode == CUSTOM)) {
+ return false;
+ }
+
+ RenderPreviewMode oldMode = mMode;
+ mMode = newMode;
+ mRevision = sRevision;
+
+ sScale = 1.0;
+ disposePreviews();
+
+ switch (mMode) {
+ case DEFAULT:
+ addDefaultPreviews();
+ break;
+ case INCLUDES:
+ addIncludedInPreviews();
+ break;
+ case LOCALES:
+ addLocalePreviews();
+ break;
+ case SCREENS:
+ addScreenSizePreviews();
+ break;
+ case VARIATIONS:
+ addVariationPreviews();
+ break;
+ case CUSTOM:
+ addManualPreviews();
+ break;
+ case NONE:
+ // Can't just set mNeedZoom because with no previews, the paint
+ // method does nothing
+ mCanvas.setFitScale(false /*onlyZoomOut*/, true /*allowZoomIn*/);
+ break;
+ default:
+ assert false : mMode;
+ }
+
+ // We schedule layout for the next redraw rather than process it here immediately;
+ // not only does this let us avoid doing work for windows where the tab is in the
+ // background, but when a file is opened we may not know the size of the canvas
+ // yet, and the layout methods need it in order to do a good job. By the time
+ // the canvas is painted, we have accurate bounds.
+ mNeedLayout = mNeedRender = true;
+ mCanvas.redraw();
+
+ if (oldMode != mMode && (oldMode == NONE || mMode == NONE)) {
+ // If entering or exiting preview mode: updating padding which is compressed
+ // only in preview mode.
+ mCanvas.getHorizontalTransform().refresh();
+ mCanvas.getVerticalTransform().refresh();
+ }
+
+ return true;
+ }
+
+ /**
+ * Sets the new render preview mode to use
+ *
+ * @param mode the new mode
+ */
+ public void selectMode(@NonNull RenderPreviewMode mode) {
+ if (mode != mMode) {
+ AdtPrefs.getPrefs().setPreviewMode(mode);
+ recomputePreviews(false);
+ }
+ }
+
+ /** Similar to {@link #addDefaultPreviews()} but for locales */
+ public void addLocalePreviews() {
+
+ ConfigurationChooser chooser = getChooser();
+ List<Locale> locales = chooser.getLocaleList();
+ Configuration parent = chooser.getConfiguration();
+
+ for (Locale locale : locales) {
+ if (!locale.hasLanguage() && !locale.hasRegion()) {
+ continue;
+ }
+ NestedConfiguration configuration = NestedConfiguration.create(chooser, parent);
+ configuration.setOverrideLocale(true);
+ configuration.setLocale(locale, false);
+
+ String displayName = ConfigurationChooser.getLocaleLabel(chooser, locale, false);
+ assert displayName != null; // it's never non null when locale is non null
+ configuration.setDisplayName(displayName);
+
+ addPreview(RenderPreview.create(this, configuration));
+ }
+
+ // Make a placeholder preview for the current screen, in case we switch from it
+ Configuration configuration = parent;
+ Locale locale = configuration.getLocale();
+ String label = ConfigurationChooser.getLocaleLabel(chooser, locale, false);
+ if (label == null) {
+ label = "default";
+ }
+ configuration.setDisplayName(label);
+ RenderPreview preview = RenderPreview.create(this, parent);
+ if (preview != null) {
+ mCanvas.setPreview(preview);
+ }
+
+ // No need to sort: they should all be identical
+ }
+
+ /** Similar to {@link #addDefaultPreviews()} but for screen sizes */
+ public void addScreenSizePreviews() {
+ ConfigurationChooser chooser = getChooser();
+ List<Device> devices = chooser.getDeviceList();
+ Configuration configuration = chooser.getConfiguration();
+ boolean canScaleNinePatch = configuration.supports(Capability.FIXED_SCALABLE_NINE_PATCH);
+
+ // Rearrange the devices a bit such that the most interesting devices bubble
+ // to the front
+ // 10" tablet, 7" tablet, reference phones, tiny phone, and in general the first
+ // version of each seen screen size
+ List<Device> sorted = new ArrayList<Device>(devices);
+ Set<ScreenSize> seenSizes = new HashSet<ScreenSize>();
+ State currentState = configuration.getDeviceState();
+ String currentStateName = currentState != null ? currentState.getName() : "";
+
+ for (int i = 0, n = sorted.size(); i < n; i++) {
+ Device device = sorted.get(i);
+ boolean interesting = false;
+
+ State state = device.getState(currentStateName);
+ if (state == null) {
+ state = device.getAllStates().get(0);
+ }
+
+ if (device.getName().startsWith("Nexus ") //$NON-NLS-1$
+ || device.getName().endsWith(" Nexus")) { //$NON-NLS-1$
+ // Not String#contains("Nexus") because that would also pick up all the generic
+ // entries ("3.7in WVGA (Nexus One)") so we'd have them duplicated
+ interesting = true;
+ }
+
+ FolderConfiguration c = DeviceConfigHelper.getFolderConfig(state);
+ if (c != null) {
+ ScreenSizeQualifier sizeQualifier = c.getScreenSizeQualifier();
+ if (sizeQualifier != null) {
+ ScreenSize size = sizeQualifier.getValue();
+ if (!seenSizes.contains(size)) {
+ seenSizes.add(size);
+ interesting = true;
+ }
+ }
+
+ // Omit LDPI, not really used anymore
+ DensityQualifier density = c.getDensityQualifier();
+ if (density != null) {
+ Density d = density.getValue();
+ if (d == Density.LOW) {
+ interesting = false;
+ }
+
+ if (!canScaleNinePatch && d == Density.TV) {
+ interesting = false;
+ }
+ }
+ }
+
+ if (interesting) {
+ NestedConfiguration screenConfig = NestedConfiguration.create(chooser,
+ configuration);
+ screenConfig.setOverrideDevice(true);
+ screenConfig.setDevice(device, true);
+ screenConfig.syncFolderConfig();
+ screenConfig.setDisplayName(ConfigurationChooser.getDeviceLabel(device, true));
+ addPreview(RenderPreview.create(this, screenConfig));
+ }
+ }
+
+ // Sorted by screen size, in decreasing order
+ sortPreviewsByScreenSize();
+ }
+
+ /**
+ * Previews this layout as included in other layouts
+ */
+ public void addIncludedInPreviews() {
+ ConfigurationChooser chooser = getChooser();
+ IProject project = chooser.getProject();
+ if (project == null) {
+ return;
+ }
+ IncludeFinder finder = IncludeFinder.get(project);
+
+ final List<Reference> includedBy = finder.getIncludedBy(chooser.getEditedFile());
+
+ if (includedBy == null || includedBy.isEmpty()) {
+ // TODO: Generate some useful defaults, such as including it in a ListView
+ // as the list item layout?
+ return;
+ }
+
+ for (final Reference reference : includedBy) {
+ String title = reference.getDisplayName();
+ Configuration config = Configuration.create(chooser.getConfiguration(),
+ reference.getFile());
+ RenderPreview preview = RenderPreview.create(this, config);
+ preview.setDisplayName(title);
+ preview.setIncludedWithin(reference);
+
+ addPreview(preview);
+ }
+
+ sortPreviewsByOrientation();
+ }
+
+ /**
+ * Previews this layout as included in other layouts
+ */
+ public void addVariationPreviews() {
+ ConfigurationChooser chooser = getChooser();
+
+ IFile file = chooser.getEditedFile();
+ List<IFile> variations = AdtUtils.getResourceVariations(file, false /*includeSelf*/);
+
+ // Sort by parent folder
+ Collections.sort(variations, new Comparator<IFile>() {
+ @Override
+ public int compare(IFile file1, IFile file2) {
+ return file1.getParent().getName().compareTo(file2.getParent().getName());
+ }
+ });
+
+ Configuration currentConfig = chooser.getConfiguration();
+
+ for (IFile variation : variations) {
+ String title = variation.getParent().getName();
+ Configuration config = Configuration.create(chooser.getConfiguration(), variation);
+ config.setTheme(currentConfig.getTheme());
+ config.setActivity(currentConfig.getActivity());
+ RenderPreview preview = RenderPreview.create(this, config);
+ preview.setDisplayName(title);
+ preview.setAlternateInput(variation);
+
+ addPreview(preview);
+ }
+
+ sortPreviewsByOrientation();
+ }
+
+ /**
+ * Previews this layout using a custom configured set of layouts
+ */
+ public void addManualPreviews() {
+ if (mManualList == null) {
+ loadList();
+ } else {
+ mPreviews = mManualList.createPreviews(mCanvas);
+ }
+ }
+
+ private void loadList() {
+ IProject project = getChooser().getProject();
+ if (project == null) {
+ return;
+ }
+
+ if (mManualList == null) {
+ mManualList = RenderPreviewList.get(project);
+ }
+
+ try {
+ mManualList.load(getChooser().getDeviceList());
+ mPreviews = mManualList.createPreviews(mCanvas);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ private void saveList() {
+ if (mManualList != null) {
+ try {
+ mManualList.save();
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ void rename(ConfigurationDescription description, String newName) {
+ IProject project = getChooser().getProject();
+ if (project == null) {
+ return;
+ }
+
+ if (mManualList == null) {
+ mManualList = RenderPreviewList.get(project);
+ }
+ description.displayName = newName;
+ saveList();
+ }
+
+
+ /**
+ * Notifies that the main configuration has changed.
+ *
+ * @param flags the change flags, a bitmask corresponding to the
+ * {@code CHANGE_} constants in {@link ConfigurationClient}
+ */
+ public void configurationChanged(int flags) {
+ // Similar to renderPreviews, but only acts on incomplete previews
+ if (hasPreviews()) {
+ // Do zoomed images first
+ beginRenderScheduling();
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() > 1.2) {
+ preview.configurationChanged(flags);
+ }
+ }
+ for (RenderPreview preview : mPreviews) {
+ if (preview.getScale() <= 1.2) {
+ preview.configurationChanged(flags);
+ }
+ }
+ RenderPreview preview = mCanvas.getPreview();
+ if (preview != null) {
+ preview.configurationChanged(flags);
+ preview.dispose();
+ }
+ mNeedLayout = true;
+ mCanvas.redraw();
+ }
+ }
+
+ /** Updates the configuration preview thumbnails */
+ public void renderPreviews() {
+ if (hasPreviews()) {
+ beginRenderScheduling();
+
+ // Process in visual order
+ ArrayList<RenderPreview> visualOrder = new ArrayList<RenderPreview>(mPreviews);
+ Collections.sort(visualOrder, RenderPreview.VISUAL_ORDER);
+
+ // Do zoomed images first
+ for (RenderPreview preview : visualOrder) {
+ if (preview.getScale() > 1.2 && preview.isVisible()) {
+ scheduleRender(preview);
+ }
+ }
+ // Non-zoomed images
+ for (RenderPreview preview : visualOrder) {
+ if (preview.getScale() <= 1.2 && preview.isVisible()) {
+ scheduleRender(preview);
+ }
+ }
+ }
+
+ mNeedRender = false;
+ }
+
+ private int mPendingRenderCount;
+
+ /**
+ * Reset rendering scheduling. The next render request will be scheduled
+ * after a single delay unit.
+ */
+ public void beginRenderScheduling() {
+ mPendingRenderCount = 0;
+ }
+
+ /**
+ * Schedule rendering the given preview. Each successive call will add an additional
+ * delay unit to the schedule from the previous {@link #scheduleRender(RenderPreview)}
+ * call, until {@link #beginRenderScheduling()} is called again.
+ *
+ * @param preview the preview to render
+ */
+ public void scheduleRender(@NonNull RenderPreview preview) {
+ mPendingRenderCount++;
+ preview.render(mPendingRenderCount * RENDER_DELAY);
+ }
+
+ /**
+ * Switch to the given configuration preview
+ *
+ * @param preview the preview to switch to
+ */
+ public void switchTo(@NonNull RenderPreview preview) {
+ IFile input = preview.getAlternateInput();
+ if (input != null) {
+ IWorkbenchPartSite site = mCanvas.getEditorDelegate().getEditor().getSite();
+ try {
+ // This switches to the given file, but the file might not have
+ // an identical configuration to what was shown in the preview.
+ // For example, while viewing a 10" layout-xlarge file, it might
+ // show a preview for a 5" version tied to the default layout. If
+ // you click on it, it will open the default layout file, but it might
+ // be using a different screen size; any of those that match the
+ // default layout, say a 3.8".
+ //
+ // Thus, we need to also perform a screen size sync first
+ Configuration configuration = preview.getConfiguration();
+ boolean setSize = false;
+ if (configuration instanceof NestedConfiguration) {
+ NestedConfiguration nestedConfig = (NestedConfiguration) configuration;
+ setSize = nestedConfig.isOverridingDevice();
+ if (configuration instanceof VaryingConfiguration) {
+ VaryingConfiguration c = (VaryingConfiguration) configuration;
+ setSize |= c.isAlternatingDevice();
+ }
+
+ if (setSize) {
+ ConfigurationChooser chooser = getChooser();
+ IFile editedFile = chooser.getEditedFile();
+ if (editedFile != null) {
+ chooser.syncToVariations(CFG_DEVICE|CFG_DEVICE_STATE,
+ editedFile, configuration, false, false);
+ }
+ }
+ }
+
+ IDE.openEditor(site.getWorkbenchWindow().getActivePage(), input,
+ CommonXmlEditor.ID);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, null);
+ }
+ return;
+ }
+
+ GraphicalEditorPart editor = mCanvas.getEditorDelegate().getGraphicalEditor();
+ ConfigurationChooser chooser = editor.getConfigurationChooser();
+
+ Configuration originalConfiguration = chooser.getConfiguration();
+
+ // The new configuration is the configuration which will become the configuration
+ // in the layout editor's chooser
+ Configuration previewConfiguration = preview.getConfiguration();
+ Configuration newConfiguration = previewConfiguration;
+ if (newConfiguration instanceof NestedConfiguration) {
+ // Should never use a complementing configuration for the main
+ // rendering's configuration; instead, create a new configuration
+ // with a snapshot of the configuration's current values
+ newConfiguration = Configuration.copy(previewConfiguration);
+
+ // Remap all the previews to be parented to this new copy instead
+ // of the old one (which is no longer controlled by the chooser)
+ for (RenderPreview p : mPreviews) {
+ Configuration configuration = p.getConfiguration();
+ if (configuration instanceof NestedConfiguration) {
+ NestedConfiguration nested = (NestedConfiguration) configuration;
+ nested.setParent(newConfiguration);
+ }
+ }
+ }
+
+ // Make a preview for the configuration which *was* showing in the
+ // chooser up until this point:
+ RenderPreview newPreview = mCanvas.getPreview();
+ if (newPreview == null) {
+ newPreview = RenderPreview.create(this, originalConfiguration);
+ }
+
+ // Update its configuration such that it is complementing or inheriting
+ // from the new chosen configuration
+ if (previewConfiguration instanceof VaryingConfiguration) {
+ VaryingConfiguration varying = VaryingConfiguration.create(
+ (VaryingConfiguration) previewConfiguration,
+ newConfiguration);
+ varying.updateDisplayName();
+ originalConfiguration = varying;
+ newPreview.setConfiguration(originalConfiguration);
+ } else if (previewConfiguration instanceof NestedConfiguration) {
+ NestedConfiguration nested = NestedConfiguration.create(
+ (NestedConfiguration) previewConfiguration,
+ originalConfiguration,
+ newConfiguration);
+ nested.setDisplayName(nested.computeDisplayName());
+ originalConfiguration = nested;
+ newPreview.setConfiguration(originalConfiguration);
+ }
+
+ // Replace clicked preview with preview of the formerly edited main configuration
+ // This doesn't work yet because the image overlay has had its image
+ // replaced by the configuration previews! I should make a list of them
+ //newPreview.setFullImage(mImageOverlay.getAwtImage());
+ for (int i = 0, n = mPreviews.size(); i < n; i++) {
+ if (preview == mPreviews.get(i)) {
+ mPreviews.set(i, newPreview);
+ break;
+ }
+ }
+
+ // Stash the corresponding preview (not active) on the canvas so we can
+ // retrieve it if clicking to some other preview later
+ mCanvas.setPreview(preview);
+ preview.setVisible(false);
+
+ // Switch to the configuration from the clicked preview (though it's
+ // most likely a copy, see above)
+ chooser.setConfiguration(newConfiguration);
+ editor.changed(MASK_ALL);
+
+ // Scroll to the top again, if necessary
+ mCanvas.getVerticalBar().setSelection(mCanvas.getVerticalBar().getMinimum());
+
+ mNeedLayout = mNeedZoom = true;
+ mCanvas.redraw();
+ mAnimation = new SwapAnimation(preview, newPreview);
+ }
+
+ /**
+ * Gets the preview at the given location, or null if none. This is
+ * currently deeply tied to where things are painted in onPaint().
+ */
+ RenderPreview getPreview(ControlPoint mousePos) {
+ if (hasPreviews()) {
+ int rootX = getX();
+ if (mousePos.x < rootX) {
+ return null;
+ }
+ int rootY = getY();
+
+ for (RenderPreview preview : mPreviews) {
+ int x = rootX + preview.getX();
+ int y = rootY + preview.getY();
+ if (mousePos.x >= x && mousePos.x <= x + preview.getWidth()) {
+ if (mousePos.y >= y && mousePos.y <= y + preview.getHeight()) {
+ return preview;
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private int getX() {
+ return mHScale.translate(0);
+ }
+
+ private int getY() {
+ return mVScale.translate(0);
+ }
+
+ private int getZoomX() {
+ Rectangle clientArea = mCanvas.getClientArea();
+ int x = clientArea.x + clientArea.width - ZOOM_ICON_WIDTH;
+ if (x < mHScale.getScaledImgSize() + PREVIEW_HGAP) {
+ // No visible previews because the main image is zoomed too far
+ return -1;
+ }
+
+ return x - 6;
+ }
+
+ private int getZoomY() {
+ Rectangle clientArea = mCanvas.getClientArea();
+ return clientArea.y + 5;
+ }
+
+ /**
+ * Returns the height of the layout
+ *
+ * @return the height
+ */
+ public int getHeight() {
+ return mLayoutHeight;
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has moved to the
+ * given control position within the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void moved(ControlPoint mousePos) {
+ RenderPreview hovered = getPreview(mousePos);
+ if (hovered != mActivePreview) {
+ if (mActivePreview != null) {
+ mActivePreview.setActive(false);
+ }
+ mActivePreview = hovered;
+ if (mActivePreview != null) {
+ mActivePreview.setActive(true);
+ }
+ mCanvas.redraw();
+ }
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has entered the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void enter(ControlPoint mousePos) {
+ moved(mousePos);
+ }
+
+ /**
+ * Notifies that preview manager that the mouse cursor has exited the layout canvas
+ *
+ * @param mousePos the mouse position, relative to the layout canvas
+ */
+ public void exit(ControlPoint mousePos) {
+ if (mActivePreview != null) {
+ mActivePreview.setActive(false);
+ }
+ mActivePreview = null;
+ mCanvas.redraw();
+ }
+
+ /**
+ * Process a mouse click, and return true if it was handled by this manager
+ * (e.g. the click was on a preview)
+ *
+ * @param mousePos the mouse position where the click occurred
+ * @return true if the click occurred over a preview and was handled, false otherwise
+ */
+ public boolean click(ControlPoint mousePos) {
+ // Clicked zoom?
+ int x = getZoomX();
+ if (x > 0) {
+ if (mousePos.x >= x && mousePos.x <= x + ZOOM_ICON_WIDTH) {
+ int y = getZoomY();
+ if (mousePos.y >= y && mousePos.y <= y + 4 * ZOOM_ICON_HEIGHT) {
+ if (mousePos.y < y + ZOOM_ICON_HEIGHT) {
+ zoomIn();
+ } else if (mousePos.y < y + 2 * ZOOM_ICON_HEIGHT) {
+ zoomOut();
+ } else if (mousePos.y < y + 3 * ZOOM_ICON_HEIGHT) {
+ zoomReset();
+ } else {
+ selectMode(NONE);
+ }
+ return true;
+ }
+ }
+ }
+
+ RenderPreview preview = getPreview(mousePos);
+ if (preview != null) {
+ boolean handled = preview.click(mousePos.x - getX() - preview.getX(),
+ mousePos.y - getY() - preview.getY());
+ if (handled) {
+ // In case layout was performed, there could be a new preview
+ // under this coordinate now, so make sure it's hover etc
+ // shows up
+ moved(mousePos);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns true if there are thumbnail previews
+ *
+ * @return true if thumbnails are being shown
+ */
+ public boolean hasPreviews() {
+ return mPreviews != null && !mPreviews.isEmpty();
+ }
+
+
+ private void sortPreviewsByScreenSize() {
+ if (mPreviews != null) {
+ Collections.sort(mPreviews, new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ Configuration config1 = preview1.getConfiguration();
+ Configuration config2 = preview2.getConfiguration();
+ Device device1 = config1.getDevice();
+ Device device2 = config1.getDevice();
+ if (device1 != null && device2 != null) {
+ Screen screen1 = device1.getDefaultHardware().getScreen();
+ Screen screen2 = device2.getDefaultHardware().getScreen();
+ if (screen1 != null && screen2 != null) {
+ double delta = screen1.getDiagonalLength()
+ - screen2.getDiagonalLength();
+ if (delta != 0.0) {
+ return (int) Math.signum(delta);
+ } else {
+ if (screen1.getPixelDensity() != screen2.getPixelDensity()) {
+ return screen1.getPixelDensity().compareTo(
+ screen2.getPixelDensity());
+ }
+ }
+ }
+
+ }
+ State state1 = config1.getDeviceState();
+ State state2 = config2.getDeviceState();
+ if (state1 != state2 && state1 != null && state2 != null) {
+ return state1.getName().compareTo(state2.getName());
+ }
+
+ return preview1.getDisplayName().compareTo(preview2.getDisplayName());
+ }
+ });
+ }
+ }
+
+ private void sortPreviewsByOrientation() {
+ if (mPreviews != null) {
+ Collections.sort(mPreviews, new Comparator<RenderPreview>() {
+ @Override
+ public int compare(RenderPreview preview1, RenderPreview preview2) {
+ Configuration config1 = preview1.getConfiguration();
+ Configuration config2 = preview2.getConfiguration();
+ State state1 = config1.getDeviceState();
+ State state2 = config2.getDeviceState();
+ if (state1 != state2 && state1 != null && state2 != null) {
+ return state1.getName().compareTo(state2.getName());
+ }
+
+ return preview1.getDisplayName().compareTo(preview2.getDisplayName());
+ }
+ });
+ }
+ }
+
+ /**
+ * Vertical scrollbar listener which updates render previews which are not
+ * visible and triggers a redraw
+ */
+ private class ScrollBarListener implements SelectionListener {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mPreviews == null) {
+ return;
+ }
+
+ ScrollBar bar = mCanvas.getVerticalBar();
+ int selection = bar.getSelection();
+ int thumb = bar.getThumb();
+ int maxY = selection + thumb;
+ beginRenderScheduling();
+ for (RenderPreview preview : mPreviews) {
+ if (!preview.isVisible() && preview.getY() <= maxY) {
+ preview.setVisible(true);
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+ }
+
+ /** Animation overlay shown briefly after swapping two previews */
+ private class SwapAnimation implements Runnable {
+ private long begin;
+ private long end;
+ private static final long DURATION = 400; // ms
+ private Rect initialRect1;
+ private Rect targetRect1;
+ private Rect initialRect2;
+ private Rect targetRect2;
+ private RenderPreview preview;
+
+ SwapAnimation(RenderPreview preview1, RenderPreview preview2) {
+ begin = System.currentTimeMillis();
+ end = begin + DURATION;
+
+ initialRect1 = new Rect(preview1.getX(), preview1.getY(),
+ preview1.getWidth(), preview1.getHeight());
+
+ CanvasTransform hi = mCanvas.getHorizontalTransform();
+ CanvasTransform vi = mCanvas.getVerticalTransform();
+ initialRect2 = new Rect(hi.translate(0), vi.translate(0),
+ hi.getScaledImgSize(), vi.getScaledImgSize());
+ preview = preview2;
+ }
+
+ void tick(GC gc) {
+ long now = System.currentTimeMillis();
+ if (now > end || mCanvas.isDisposed()) {
+ mAnimation = null;
+ return;
+ }
+
+ CanvasTransform hi = mCanvas.getHorizontalTransform();
+ CanvasTransform vi = mCanvas.getVerticalTransform();
+ if (targetRect1 == null) {
+ targetRect1 = new Rect(hi.translate(0), vi.translate(0),
+ hi.getScaledImgSize(), vi.getScaledImgSize());
+ }
+ double portion = (now - begin) / (double) DURATION;
+ Rect rect1 = new Rect(
+ (int) (portion * (targetRect1.x - initialRect1.x) + initialRect1.x),
+ (int) (portion * (targetRect1.y - initialRect1.y) + initialRect1.y),
+ (int) (portion * (targetRect1.w - initialRect1.w) + initialRect1.w),
+ (int) (portion * (targetRect1.h - initialRect1.h) + initialRect1.h));
+
+ if (targetRect2 == null) {
+ targetRect2 = new Rect(preview.getX(), preview.getY(),
+ preview.getWidth(), preview.getHeight());
+ }
+ portion = (now - begin) / (double) DURATION;
+ Rect rect2 = new Rect(
+ (int) (portion * (targetRect2.x - initialRect2.x) + initialRect2.x),
+ (int) (portion * (targetRect2.y - initialRect2.y) + initialRect2.y),
+ (int) (portion * (targetRect2.w - initialRect2.w) + initialRect2.w),
+ (int) (portion * (targetRect2.h - initialRect2.h) + initialRect2.h));
+
+ gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
+ gc.drawRectangle(rect1.x, rect1.y, rect1.w, rect1.h);
+ gc.drawRectangle(rect2.x, rect2.y, rect2.w, rect2.h);
+
+ mCanvas.getDisplay().timerExec(5, this);
+ }
+
+ @Override
+ public void run() {
+ mCanvas.redraw();
+ }
+ }
+
+ /**
+ * Notifies the {@linkplain RenderPreviewManager} that the configuration used
+ * in the main chooser has been changed. This may require updating parent references
+ * in the preview configurations inheriting from it.
+ *
+ * @param oldConfiguration the previous configuration
+ * @param newConfiguration the new configuration in the chooser
+ */
+ public void updateChooserConfig(
+ @NonNull Configuration oldConfiguration,
+ @NonNull Configuration newConfiguration) {
+ if (hasPreviews()) {
+ for (RenderPreview preview : mPreviews) {
+ Configuration configuration = preview.getConfiguration();
+ if (configuration instanceof NestedConfiguration) {
+ NestedConfiguration nestedConfig = (NestedConfiguration) configuration;
+ if (nestedConfig.getParent() == oldConfiguration) {
+ nestedConfig.setParent(newConfiguration);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java
new file mode 100644
index 0000000..0f06d7f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderPreviewMode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+/**
+ * The {@linkplain RenderPreviewMode} records what type of configurations to
+ * render in the layout editor
+ */
+public enum RenderPreviewMode {
+ /** Generate a set of default previews with maximum variation */
+ DEFAULT,
+
+ /** Preview all the locales */
+ LOCALES,
+
+ /** Preview all the screen sizes */
+ SCREENS,
+
+ /** Preview layout as included in other layouts */
+ INCLUDES,
+
+ /** Preview all the variations of this layout */
+ VARIATIONS,
+
+ /** Show a manually configured set of previews */
+ CUSTOM,
+
+ /** No previews */
+ NONE;
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
index e0c3add..fdc5fed 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderService.java
@@ -17,11 +17,15 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
+import com.android.annotations.NonNull;
import com.android.ide.common.api.IClientRulesEngine;
import com.android.ide.common.api.INode;
import com.android.ide.common.api.Rect;
+import com.android.ide.common.rendering.HardwareConfigHelper;
import com.android.ide.common.rendering.LayoutLibrary;
+import com.android.ide.common.rendering.api.Capability;
import com.android.ide.common.rendering.api.DrawableParams;
+import com.android.ide.common.rendering.api.HardwareConfig;
import com.android.ide.common.rendering.api.IImageFactory;
import com.android.ide.common.rendering.api.ILayoutPullParser;
import com.android.ide.common.rendering.api.LayoutLog;
@@ -32,10 +36,9 @@ import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.SessionParams.RenderingMode;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.ide.common.resources.ResourceResolver;
-import com.android.ide.common.resources.configuration.ScreenSizeQualifier;
+import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.ContextPullParser;
-import com.android.ide.eclipse.adt.internal.editors.layout.ExplodedRenderingHelper;
import com.android.ide.eclipse.adt.internal.editors.layout.ProjectCallback;
import com.android.ide.eclipse.adt.internal.editors.layout.UiElementPullParser;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.Configuration;
@@ -47,7 +50,12 @@ import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElement
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.resources.Density;
+import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.devices.Device;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
import org.eclipse.core.resources.IProject;
import org.xmlpull.v1.XmlPullParser;
@@ -55,8 +63,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.awt.image.BufferedImage;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.StringReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -81,18 +89,12 @@ public class RenderService {
private final int mTargetSdkVersion;
private final LayoutLibrary mLayoutLib;
private final IImageFactory mImageFactory;
- private final Density mDensity;
- private final float mXdpi;
- private final float mYdpi;
- private final ScreenSizeQualifier mScreenSize;
+ private final HardwareConfigHelper mHardwareConfigHelper;
// The following fields are optional or configurable using the various chained
// setters:
private UiDocumentNode mModel;
- private int mWidth = -1;
- private int mHeight = -1;
- private boolean mUseExplodeMode;
private Reference mIncludedWithin;
private RenderingMode mRenderingMode = RenderingMode.NORMAL;
private LayoutLog mLogger;
@@ -109,10 +111,14 @@ public class RenderService {
mImageFactory = canvas.getImageOverlay();
ConfigurationChooser chooser = editor.getConfigurationChooser();
Configuration config = chooser.getConfiguration();
- mDensity = config.getDensity();
- mXdpi = config.getXDpi();
- mYdpi = config.getYDpi();
- mScreenSize = chooser.getConfiguration().getFullConfig().getScreenSizeQualifier();
+ FolderConfiguration folderConfig = config.getFullConfig();
+
+ Device device = config.getDevice();
+ assert device != null; // Should only attempt render with configuration that has device
+ mHardwareConfigHelper = new HardwareConfigHelper(device);
+ mHardwareConfigHelper.setOrientation(
+ folderConfig.getScreenOrientationQualifier().getValue());
+
mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/);
mResourceResolver = editor.getResourceResolver();
mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib);
@@ -120,6 +126,53 @@ public class RenderService {
mTargetSdkVersion = editor.getTargetSdkVersion();
}
+ private RenderService(GraphicalEditorPart editor,
+ Configuration configuration, ResourceResolver resourceResolver) {
+ mEditor = editor;
+
+ mProject = editor.getProject();
+ LayoutCanvas canvas = editor.getCanvasControl();
+ mImageFactory = canvas.getImageOverlay();
+ FolderConfiguration folderConfig = configuration.getFullConfig();
+
+ Device device = configuration.getDevice();
+ assert device != null;
+ mHardwareConfigHelper = new HardwareConfigHelper(device);
+ mHardwareConfigHelper.setOrientation(
+ folderConfig.getScreenOrientationQualifier().getValue());
+
+ mLayoutLib = editor.getReadyLayoutLib(true /*displayError*/);
+ mResourceResolver = resourceResolver != null ? resourceResolver : editor.getResourceResolver();
+ mProjectCallback = editor.getProjectCallback(true /*reset*/, mLayoutLib);
+ mMinSdkVersion = editor.getMinSdkVersion();
+ mTargetSdkVersion = editor.getTargetSdkVersion();
+ }
+
+ /**
+ * Returns true if this configuration supports the given rendering
+ * capability
+ *
+ * @param target the target to look up the layout library for
+ * @param capability the capability to check
+ * @return true if the capability is supported
+ */
+ public static boolean supports(
+ @NonNull IAndroidTarget target,
+ @NonNull Capability capability) {
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ AndroidTargetData targetData = sdk.getTargetData(target);
+ if (targetData != null) {
+ LayoutLibrary layoutLib = targetData.getLayoutLibrary();
+ if (layoutLib != null) {
+ return layoutLib.supports(capability);
+ }
+ }
+ }
+
+ return false;
+ }
+
/**
* Creates a new {@link RenderService} associated with the given editor.
*
@@ -133,6 +186,21 @@ public class RenderService {
}
/**
+ * Creates a new {@link RenderService} associated with the given editor.
+ *
+ * @param editor the editor to provide configuration data such as the render target
+ * @param configuration the configuration to use (and fallback to editor for the rest)
+ * @param resolver a resource resolver to use to look up resources
+ * @return a {@link RenderService} which can perform rendering services
+ */
+ public static RenderService create(GraphicalEditorPart editor,
+ Configuration configuration, ResourceResolver resolver) {
+ RenderService renderService = new RenderService(editor, configuration, resolver);
+
+ return renderService;
+ }
+
+ /**
* Renders the given model, using this editor's theme and screen settings, and returns
* the result as a {@link RenderSession}.
*
@@ -176,16 +244,34 @@ public class RenderService {
}
/**
- * Sets the width and height to be used during rendering (which might be adjusted if
+ * Overrides the width and height to be used during rendering (which might be adjusted if
* the {@link #setRenderingMode(RenderingMode)} is {@link RenderingMode#FULL_EXPAND}.
*
- * @param width the width in pixels of the layout to be rendered
- * @param height the height in pixels of the layout to be rendered
+ * A value of -1 will make the rendering use the normal width and height coming from the
+ * {@link Configuration#getDevice()} object.
+ *
+ * @param overrideRenderWidth the width in pixels of the layout to be rendered
+ * @param overrideRenderHeight the height in pixels of the layout to be rendered
* @return this (such that chains of setters can be stringed together)
*/
- public RenderService setSize(int width, int height) {
- mWidth = width;
- mHeight = height;
+ public RenderService setOverrideRenderSize(int overrideRenderWidth, int overrideRenderHeight) {
+ mHardwareConfigHelper.setOverrideRenderSize(overrideRenderWidth, overrideRenderHeight);
+ return this;
+ }
+
+ /**
+ * Sets the max width and height to be used during rendering (which might be adjusted if
+ * the {@link #setRenderingMode(RenderingMode)} is {@link RenderingMode#FULL_EXPAND}.
+ *
+ * A value of -1 will make the rendering use the normal width and height coming from the
+ * {@link Configuration#getDevice()} object.
+ *
+ * @param maxRenderWidth the max width in pixels of the layout to be rendered
+ * @param maxRenderHeight the max height in pixels of the layout to be rendered
+ * @return this (such that chains of setters can be stringed together)
+ */
+ public RenderService setMaxRenderSize(int maxRenderWidth, int maxRenderHeight) {
+ mHardwareConfigHelper.setMaxRenderSize(maxRenderWidth, maxRenderHeight);
return this;
}
@@ -265,7 +351,7 @@ public class RenderService {
* @return the {@link RenderSession} resulting from rendering the current model
*/
public RenderSession createRenderSession() {
- assert mModel != null && mWidth != -1 && mHeight != -1 : "Incomplete service config";
+ assert mModel != null : "Incomplete service config";
finishConfiguration();
if (mResourceResolver == null) {
@@ -273,26 +359,10 @@ public class RenderService {
return null;
}
- int width = mWidth;
- int height = mHeight;
- if (mUseExplodeMode) {
- // compute how many padding in x and y will bump the screen size
- List<UiElementNode> children = mModel.getUiChildren();
- if (children.size() == 1) {
- ExplodedRenderingHelper helper = new ExplodedRenderingHelper(
- children.get(0).getXmlNode(), mProject);
-
- // there are 2 paddings for each view
- // left and right, or top and bottom.
- int paddingValue = ExplodedRenderingHelper.PADDING_VALUE * 2;
-
- width += helper.getWidthPadding() * paddingValue;
- height += helper.getHeightPadding() * paddingValue;
- }
- }
+ HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig();
UiElementPullParser modelParser = new UiElementPullParser(mModel,
- mUseExplodeMode, mExpandNodes, mDensity, mXdpi, mProject);
+ false, mExpandNodes, hardwareConfig.getDensity(), mProject);
ILayoutPullParser topParser = modelParser;
// Code to support editing included layout
@@ -316,11 +386,12 @@ public class RenderService {
mProjectCallback.setLayoutParser(queryLayoutName, modelParser);
topParser = new ContextPullParser(mProjectCallback, layoutFile);
topParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- topParser.setInput(new FileInputStream(layoutFile), "UTF-8"); //$NON-NLS-1$
+ String xmlText = Files.toString(layoutFile, Charsets.UTF_8);
+ topParser.setInput(new StringReader(xmlText));
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
} catch (XmlPullParserException e) {
- AdtPlugin.log(e, ""); //$NON-NLS-1$
- } catch (FileNotFoundException e) {
- // this will not happen since we check above.
+ AdtPlugin.log(e, null);
}
}
}
@@ -330,8 +401,7 @@ public class RenderService {
topParser,
mRenderingMode,
mProject /* projectKey */,
- width, height,
- mDensity, mXdpi, mYdpi,
+ hardwareConfig,
mResourceResolver,
mProjectCallback,
mMinSdkVersion,
@@ -357,10 +427,6 @@ public class RenderService {
}
}
- if (mScreenSize != null) {
- params.setConfigScreenSize(mScreenSize.getValue());
- }
-
if (mOverrideBgColor != null) {
params.setOverrideBgColor(mOverrideBgColor.intValue());
}
@@ -396,8 +462,10 @@ public class RenderService {
finishConfiguration();
- DrawableParams params = new DrawableParams(drawableResourceValue, mProject, mWidth, mHeight,
- mDensity, mXdpi, mYdpi, mResourceResolver, mProjectCallback, mMinSdkVersion,
+ HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig();
+
+ DrawableParams params = new DrawableParams(drawableResourceValue, mProject, hardwareConfig,
+ mResourceResolver, mProjectCallback, mMinSdkVersion,
mTargetSdkVersion, mLogger);
params.setForceNoDecor();
Result result = mLayoutLib.renderDrawable(params);
@@ -422,14 +490,13 @@ public class RenderService {
public Map<INode, Rect> measureChildren(INode parent,
final IClientRulesEngine.AttributeFilter filter) {
finishConfiguration();
-
- int width = parent.getBounds().w;
- int height = parent.getBounds().h;
+ HardwareConfig hardwareConfig = mHardwareConfigHelper.getConfig();
final NodeFactory mNodeFactory = mEditor.getCanvasControl().getNodeFactory();
UiElementNode parentNode = ((NodeProxy) parent).getNode();
UiElementPullParser topParser = new UiElementPullParser(parentNode,
- false, Collections.<UiElementNode>emptySet(), mDensity, mXdpi, mProject) {
+ false, Collections.<UiElementNode>emptySet(), hardwareConfig.getDensity(),
+ mProject) {
@Override
public String getAttributeValue(String namespace, String localName) {
if (filter != null) {
@@ -465,8 +532,7 @@ public class RenderService {
topParser,
RenderingMode.FULL_EXPAND,
mProject /* projectKey */,
- width, height,
- mDensity, mXdpi, mYdpi,
+ hardwareConfig,
mResourceResolver,
mProjectCallback,
mMinSdkVersion,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
index 7c5cd4b..eb3d6f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.FQCN_SPACE;
import static com.android.SdkConstants.FQCN_SPACE_V7;
@@ -22,9 +23,7 @@ import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_MARGIN;
import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS;
-
import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.api.INode;
@@ -39,6 +38,8 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.resources.ResourceType;
import com.android.utils.Pair;
@@ -1184,40 +1185,78 @@ public class SelectionManager implements ISelectionProvider {
if (selections.size() > 0) {
NodeProxy primary = selections.get(0).getNode();
if (primary != null) {
- String currentId = primary.getStringAttr(ANDROID_URI, ATTR_ID);
- currentId = BaseViewRule.stripIdPrefix(currentId);
- InputDialog d = new InputDialog(
- AdtPlugin.getDisplay().getActiveShell(),
- "Set ID",
- "New ID:",
- currentId,
- ResourceNameValidator.create(false, (IProject) null, ResourceType.ID));
- if (d.open() == Window.OK) {
- final String s = d.getValue();
- mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Set ID",
- new Runnable() {
- @Override
- public void run() {
- String newId = s;
- newId = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(s);
- for (SelectionItem item : selections) {
- item.getNode().setAttribute(ANDROID_URI, ATTR_ID, newId);
- }
+ performRename(primary, selections);
+ }
+ }
+ }
- LayoutCanvas canvas = mCanvas;
- CanvasViewInfo root = canvas.getViewHierarchy().getRoot();
- if (root != null) {
- UiViewElementNode uiViewNode = root.getUiViewNode();
- NodeFactory nodeFactory = canvas.getNodeFactory();
- NodeProxy rootNode = nodeFactory.create(uiViewNode);
- if (rootNode != null) {
- rootNode.applyPendingChanges();
- }
+ /**
+ * Performs renaming the given node.
+ *
+ * @param primary the node to be renamed, or the primary node (to get the
+ * current value from if more than one node should be renamed)
+ * @param selections if not null, a list of nodes to apply the setting to
+ * (which should include the primary)
+ * @return the result of the renaming operation
+ */
+ @NonNull
+ public RenameResult performRename(
+ final @NonNull INode primary,
+ final @Nullable List<SelectionItem> selections) {
+ String id = primary.getStringAttr(ANDROID_URI, ATTR_ID);
+ if (id != null && !id.isEmpty()) {
+ RenameResult result = RenameResourceWizard.renameResource(
+ mCanvas.getShell(),
+ mCanvas.getEditorDelegate().getGraphicalEditor().getProject(),
+ ResourceType.ID, BaseViewRule.stripIdPrefix(id), null, true /*canClear*/);
+ if (result.isCanceled()) {
+ return result;
+ } else if (!result.isUnavailable()) {
+ return result;
+ }
+ }
+ String currentId = primary.getStringAttr(ANDROID_URI, ATTR_ID);
+ currentId = BaseViewRule.stripIdPrefix(currentId);
+ InputDialog d = new InputDialog(
+ AdtPlugin.getDisplay().getActiveShell(),
+ "Set ID",
+ "New ID:",
+ currentId,
+ ResourceNameValidator.create(false, (IProject) null, ResourceType.ID));
+ if (d.open() == Window.OK) {
+ final String s = d.getValue();
+ mCanvas.getEditorDelegate().getEditor().wrapUndoEditXmlModel("Set ID",
+ new Runnable() {
+ @Override
+ public void run() {
+ String newId = s;
+ newId = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(s);
+ if (selections != null) {
+ for (SelectionItem item : selections) {
+ NodeProxy node = item.getNode();
+ if (node != null) {
+ node.setAttribute(ANDROID_URI, ATTR_ID, newId);
}
}
- });
+ } else {
+ primary.setAttribute(ANDROID_URI, ATTR_ID, newId);
+ }
+
+ LayoutCanvas canvas = mCanvas;
+ CanvasViewInfo root = canvas.getViewHierarchy().getRoot();
+ if (root != null) {
+ UiViewElementNode uiViewNode = root.getUiViewNode();
+ NodeFactory nodeFactory = canvas.getNodeFactory();
+ NodeProxy rootNode = nodeFactory.create(uiViewNode);
+ if (rootNode != null) {
+ rootNode.applyPendingChanges();
+ }
+ }
}
- }
+ });
+ return RenameResult.name(BaseViewRule.stripIdPrefix(s));
+ } else {
+ return RenameResult.canceled();
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
index c5f976f..388907a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ClientRulesEngine.java
@@ -21,6 +21,7 @@ import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.AUTO_URI;
import static com.android.SdkConstants.CLASS_FRAGMENT;
import static com.android.SdkConstants.CLASS_V4_FRAGMENT;
+import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.URI_PREFIX;
@@ -50,6 +51,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ViewHierarchy;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult;
import com.android.ide.eclipse.adt.internal.resources.CyclicDependencyValidator;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
@@ -116,6 +118,11 @@ import java.util.concurrent.atomic.AtomicReference;
* with a few methods they can use to access functionality from this {@link RulesEngine}.
*/
class ClientRulesEngine implements IClientRulesEngine {
+ /** The return code from the dialog for the user choosing "Clear" */
+ public static final int CLEAR_RETURN_CODE = -5;
+ /** The dialog button ID for the user choosing "Clear" */
+ private static final int CLEAR_BUTTON_ID = CLEAR_RETURN_CODE;
+
private final RulesEngine mRulesEngine;
private final String mFqcn;
@@ -145,12 +152,21 @@ class ClientRulesEngine implements IClientRulesEngine {
@Override
public void displayAlert(@NonNull String message) {
MessageDialog.openInformation(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
mFqcn, // title
message);
}
@Override
+ public boolean rename(INode node) {
+ GraphicalEditorPart editor = mRulesEngine.getEditor();
+ SelectionManager manager = editor.getCanvasControl().getSelectionManager();
+ RenameResult result = manager.performRename(node, null);
+
+ return !result.isCanceled() && !result.isUnavailable();
+ }
+
+ @Override
public String displayInput(@NonNull String message, @Nullable String value,
final @Nullable IValidator filter) {
IInputValidator validator = null;
@@ -170,12 +186,32 @@ class ClientRulesEngine implements IClientRulesEngine {
}
InputDialog d = new InputDialog(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
mFqcn, // title
message,
value == null ? "" : value, //$NON-NLS-1$
- validator);
- if (d.open() == Window.OK) {
+ validator) {
+ @Override
+ protected void createButtonsForButtonBar(Composite parent) {
+ createButton(parent, CLEAR_BUTTON_ID, "Clear", false /*defaultButton*/);
+ super.createButtonsForButtonBar(parent);
+ }
+
+ @Override
+ protected void buttonPressed(int buttonId) {
+ super.buttonPressed(buttonId);
+
+ if (buttonId == CLEAR_BUTTON_ID) {
+ assert CLEAR_RETURN_CODE != Window.OK && CLEAR_RETURN_CODE != Window.CANCEL;
+ setReturnCode(CLEAR_RETURN_CODE);
+ close();
+ }
+ }
+ };
+ int result = d.open();
+ if (result == ResourceChooser.CLEAR_RETURN_CODE) {
+ return "";
+ } else if (result == Window.OK) {
return d.getValue();
}
return null;
@@ -299,7 +335,7 @@ class ClientRulesEngine implements IClientRulesEngine {
// get the resource repository for this project and the system resources.
ResourceRepository projectRepository =
ResourceManager.getInstance().getProjectResources(project);
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
if (shell == null) {
return null;
}
@@ -338,7 +374,7 @@ class ClientRulesEngine implements IClientRulesEngine {
GraphicalEditorPart editor = mRulesEngine.getEditor();
IProject project = editor.getProject();
if (project != null) {
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
if (shell == null) {
return null;
}
@@ -437,7 +473,7 @@ class ClientRulesEngine implements IClientRulesEngine {
scope = SearchEngine.createJavaSearchScope(subTypes, IJavaSearchScope.SOURCES);
}
- Shell parent = AdtPlugin.getDisplay().getActiveShell();
+ Shell parent = AdtPlugin.getShell();
final AtomicReference<String> returnValue =
new AtomicReference<String>();
final AtomicReference<SelectionDialog> dialogHolder =
@@ -477,7 +513,8 @@ class ClientRulesEngine implements IClientRulesEngine {
int modifiers = typeInfoRequestor.getModifiers();
if (!Flags.isPublic(modifiers)
|| Flags.isInterface(modifiers)
- || Flags.isEnum(modifiers)) {
+ || Flags.isEnum(modifiers)
+ || Flags.isAbstract(modifiers)) {
return false;
}
return true;
@@ -509,6 +546,98 @@ class ClientRulesEngine implements IClientRulesEngine {
}
@Override
+ public String displayCustomViewClassInput() {
+ try {
+ IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
+ IProject project = mRulesEngine.getProject();
+ final IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject != null) {
+ // Look up sub-types of each (new fragment class and compatibility fragment
+ // class, if any) and merge the two arrays - then create a scope from these
+ // elements.
+ IType[] viewTypes = new IType[0];
+ IType fragmentType = javaProject.findType(CLASS_VIEW);
+ if (fragmentType != null) {
+ ITypeHierarchy hierarchy =
+ fragmentType.newTypeHierarchy(new NullProgressMonitor());
+ viewTypes = hierarchy.getAllSubtypes(fragmentType);
+ }
+ scope = SearchEngine.createJavaSearchScope(viewTypes, IJavaSearchScope.SOURCES);
+ }
+
+ Shell parent = AdtPlugin.getShell();
+ final AtomicReference<String> returnValue =
+ new AtomicReference<String>();
+ final AtomicReference<SelectionDialog> dialogHolder =
+ new AtomicReference<SelectionDialog>();
+ final SelectionDialog dialog = JavaUI.createTypeDialog(
+ parent,
+ new ProgressMonitorDialog(parent),
+ scope,
+ IJavaElementSearchConstants.CONSIDER_CLASSES, false,
+ // Use ? as a default filter to fill dialog with matches
+ "?", //$NON-NLS-1$
+ new TypeSelectionExtension() {
+ @Override
+ public Control createContentArea(Composite parentComposite) {
+ Composite composite = new Composite(parentComposite, SWT.NONE);
+ composite.setLayout(new GridLayout(1, false));
+ Button button = new Button(composite, SWT.PUSH);
+ button.setText("Create New...");
+ button.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ String fqcn = createNewCustomViewClass(javaProject);
+ if (fqcn != null) {
+ returnValue.set(fqcn);
+ dialogHolder.get().close();
+ }
+ }
+ });
+ return composite;
+ }
+
+ @Override
+ public ITypeInfoFilterExtension getFilterExtension() {
+ return new ITypeInfoFilterExtension() {
+ @Override
+ public boolean select(ITypeInfoRequestor typeInfoRequestor) {
+ int modifiers = typeInfoRequestor.getModifiers();
+ if (!Flags.isPublic(modifiers)
+ || Flags.isInterface(modifiers)
+ || Flags.isEnum(modifiers)
+ || Flags.isAbstract(modifiers)) {
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+ });
+ dialogHolder.set(dialog);
+
+ dialog.setTitle("Choose Custom View Class");
+ dialog.setMessage("Select a Custom View class (? = any character, * = any string):");
+ if (dialog.open() == IDialogConstants.CANCEL_ID) {
+ return null;
+ }
+ if (returnValue.get() != null) {
+ return returnValue.get();
+ }
+
+ Object[] types = dialog.getResult();
+ if (types != null && types.length > 0) {
+ return ((IType) types[0]).getFullyQualifiedName();
+ }
+ } catch (JavaModelException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ return null;
+ }
+
+ @Override
public void redraw() {
mRulesEngine.getEditor().getCanvasControl().redraw();
}
@@ -548,13 +677,17 @@ class ClientRulesEngine implements IClientRulesEngine {
return (int) (pixels / mRulesEngine.getEditor().getCanvasControl().getScale());
}
- String createNewFragmentClass(IJavaProject javaProject) {
+ private String createNewFragmentClass(IJavaProject javaProject) {
NewClassWizardPage page = new NewClassWizardPage();
IProject project = mRulesEngine.getProject();
- IAndroidTarget target = Sdk.getCurrent().getTarget(project);
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk == null) {
+ return null;
+ }
+ IAndroidTarget target = sdk.getTarget(project);
String superClass;
- if (target.getVersion().getApiLevel() < 11) {
+ if (target == null || target.getVersion().getApiLevel() < 11) {
superClass = CLASS_V4_FRAGMENT;
} else {
superClass = CLASS_FRAGMENT;
@@ -580,6 +713,32 @@ class ClientRulesEngine implements IClientRulesEngine {
}
}
+ private String createNewCustomViewClass(IJavaProject javaProject) {
+ NewClassWizardPage page = new NewClassWizardPage();
+
+ IProject project = mRulesEngine.getProject();
+ String superClass = CLASS_VIEW;
+ page.setSuperClass(superClass, true /* canBeModified */);
+ IPackageFragmentRoot root = ManifestInfo.getSourcePackageRoot(javaProject);
+ if (root != null) {
+ page.setPackageFragmentRoot(root, true /* canBeModified */);
+ }
+ ManifestInfo manifestInfo = ManifestInfo.get(project);
+ IPackageFragment pkg = manifestInfo.getPackageFragment();
+ if (pkg != null) {
+ page.setPackageFragment(pkg, true /* canBeModified */);
+ }
+ OpenNewClassWizardAction action = new OpenNewClassWizardAction();
+ action.setConfiguredWizardPage(page);
+ action.run();
+ IType createdType = page.getCreatedType();
+ if (createdType != null) {
+ return createdType.getFullyQualifiedName();
+ } else {
+ return null;
+ }
+ }
+
@Override
public @NonNull String getUniqueId(@NonNull String fqcn) {
UiDocumentNode root = mRulesEngine.getEditor().getModel();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
index f7eac4434..8f99237 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gre;
import static com.android.SdkConstants.ANDROID_WIDGET_PREFIX;
import static com.android.SdkConstants.VIEW_MERGE;
+import static com.android.SdkConstants.VIEW_TAG;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -792,6 +793,12 @@ public class RulesEngine {
String baseName = realFqcn.substring(dotIndex+1);
// Capitalize rule class name to match naming conventions, if necessary (<merge>)
if (Character.isLowerCase(baseName.charAt(0))) {
+ if (baseName.equals(VIEW_TAG)) {
+ // Hack: ViewRule is generic for the "View" class, so we can't use it
+ // for the special XML "view" tag (lowercase); instead, the rule is
+ // named "ViewTagRule" instead.
+ baseName = "ViewTag"; //$NON-NLS-1$
+ }
baseName = Character.toUpperCase(baseName.charAt(0)) + baseName.substring(1);
}
ruleClassName = packageName + "." + //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
index 586da12..5f2659e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/ViewMetadataRepository.java
@@ -37,6 +37,8 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewEleme
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.Density;
import com.android.utils.Pair;
+import com.google.common.base.Splitter;
+import com.google.common.io.Closeables;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -140,6 +142,8 @@ public class ViewMetadataRepository {
} catch (Exception e) {
AdtPlugin.log(e, "Parsing palette file failed");
return null;
+ } finally {
+ Closeables.closeQuietly(paletteStream);
}
}
@@ -195,6 +199,7 @@ public class ViewMetadataRepository {
}
/** Returns an ordered list of categories and views, parsed from a metadata file */
+ @SuppressWarnings("resource") // streams passed to parser InputSource closed by parser
private List<CategoryData> getCategories() {
if (mCategories == null) {
mCategories = new ArrayList<CategoryData>();
@@ -536,13 +541,12 @@ public class ViewMetadataRepository {
if (mRelatedTo == null || mRelatedTo.length() == 0) {
return Collections.emptyList();
} else {
- String[] basenames = mRelatedTo.split(","); //$NON-NLS-1$
List<String> result = new ArrayList<String>();
ViewMetadataRepository repository = ViewMetadataRepository.get();
Map<String, ViewData> classToView = repository.getClassToView();
List<String> fqns = new ArrayList<String>(classToView.keySet());
- for (String basename : basenames) {
+ for (String basename : Splitter.on(',').split(mRelatedTo)) {
boolean found = false;
for (String fqcn : fqns) {
String suffix = '.' + basename;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml
index db3bd7b..6a67b1d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/extra-view-metadata.xml
@@ -293,11 +293,13 @@
class="android.widget.ImageView"
topAttrs="src,scaleType"
resize="scaled"
+ render="skip"
relatedTo="ImageButton,VideoView" />
<view
class="android.widget.ImageButton"
topAttrs="src,background,style"
resize="scaled"
+ render="skip"
relatedTo="Button,ImageView" />
<view
class="android.widget.Gallery"
@@ -395,6 +397,10 @@
topAttrs="layout,inflatedId,visibility"
render="skip" />
<view
+ class="view"
+ topAttrs="class"
+ render="skip" />
+ <view
class="android.gesture.GestureOverlayView"
topAttrs="gestureStrokeType,uncertainGestureColor,eventsInterceptionEnabled,gestureColor,orientation"
render="skip" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml
index ec32882..96c7fe7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/rendering-configs.xml
@@ -188,18 +188,6 @@
android:layout_height="wrap_content">
</TextView>
- <ImageButton
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:id="@+id/android_widget_ImageButton"
- android:src="@android:drawable/ic_menu_gallery">
- </ImageButton>
- <ImageView
- android:id="@+id/android_widget_ImageView"
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:src="@android:drawable/ic_menu_gallery">
- </ImageView>
<MultiAutoCompleteTextView
android:layout_height="wrap_content"
android:layout_width="200dip"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java
index 0276b6c..5e1e702 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/FlagXmlPropertyDialog.java
@@ -82,6 +82,11 @@ implements IStructuredContentProvider, ICheckStateListener, SelectionListener, K
mTable = mViewer.getTable();
mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+ Composite workaround = PropertyFactory.addWorkaround(container);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ }
+
mViewer.setContentProvider(this);
mViewer.setInput(mFlags);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
index 59754af..2b8cfbf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/PropertyFactory.java
@@ -37,12 +37,26 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.wb.internal.core.editor.structure.property.PropertyListIntersector;
import org.eclipse.wb.internal.core.model.property.ComplexProperty;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.property.category.PropertyCategory;
import org.eclipse.wb.internal.core.model.property.editor.PropertyEditor;
+import org.eclipse.wb.internal.core.model.property.editor.presentation.ButtonPropertyEditorPresentation;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -66,6 +80,7 @@ import java.util.WeakHashMap;
*/
public class PropertyFactory {
/** Disable cache during development only */
+ @SuppressWarnings("unused")
private static final boolean CACHE_ENABLED = true || !LintUtils.assertionsEnabled();
static {
if (!CACHE_ENABLED) {
@@ -253,6 +268,9 @@ public class PropertyFactory {
Map<String, ComplexProperty> categoryToProperty = new HashMap<String, ComplexProperty>();
Multimap<String, Property> categoryToProperties = ArrayListMultimap.create();
+ if (properties.isEmpty()) {
+ return properties;
+ }
ViewElementDescriptor parent = (ViewElementDescriptor) properties.get(0).getDescriptor()
.getParent();
@@ -685,4 +703,48 @@ public class PropertyFactory {
public void setSortingMode(SortingMode sortingMode) {
mSortMode = sortingMode;
}
+
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574
+ public static Composite addWorkaround(Composite parent) {
+ if (ButtonPropertyEditorPresentation.isInWorkaround) {
+ Composite top = new Composite(parent, SWT.NONE);
+ top.setLayout(new GridLayout(1, false));
+ Label label = new Label(top, SWT.WRAP);
+ label.setText(
+ "This dialog is shown instead of an inline text editor as a\n" +
+ "workaround for an Eclipse bug specific to OSX Mountain Lion.\n" +
+ "It should be fixed in Eclipse 4.3.");
+ label.setForeground(top.getDisplay().getSystemColor(SWT.COLOR_RED));
+ GridData data = new GridData();
+ data.grabExcessVerticalSpace = false;
+ data.grabExcessHorizontalSpace = false;
+ data.horizontalAlignment = GridData.FILL;
+ data.verticalAlignment = GridData.BEGINNING;
+ label.setLayoutData(data);
+
+ Link link = new Link(top, SWT.NO_FOCUS);
+ link.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ link.setText("<a>https://bugs.eclipse.org/bugs/show_bug.cgi?id=388574</a>");
+ link.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ try {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser();
+ browser.openURL(new URL(event.text));
+ } catch (Exception e) {
+ String message = String.format(
+ "Could not open browser. Vist\n%1$s\ninstead.",
+ event.text);
+ MessageDialog.openError(((Link)event.getSource()).getShell(),
+ "Browser Error", message);
+ }
+ }
+ });
+
+ return top;
+ }
+
+ return null;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java
index 3fb72a9..fb7e459 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/StringXmlPropertyDialog.java
@@ -15,6 +15,10 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.properties;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wb.internal.core.model.property.Property;
import org.eclipse.wb.internal.core.model.property.editor.string.StringPropertyDialog;
@@ -28,4 +32,16 @@ class StringXmlPropertyDialog extends StringPropertyDialog {
protected boolean isMultiLine() {
return false;
}
+
+ @Override
+ protected Control createDialogArea(Composite parent) {
+ Composite area = (Composite) super.createDialogArea(parent);
+
+ Composite workaround = PropertyFactory.addWorkaround(area);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ }
+
+ return area;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
index 72577a5..87fb0e6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/properties/XmlPropertyEditor.java
@@ -21,21 +21,31 @@ import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.DOT_PNG;
import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.ide.common.layout.BaseViewRule.stripIdPrefix;
import com.android.annotations.NonNull;
import com.android.ide.common.api.IAttributeInfo;
import com.android.ide.common.api.IAttributeInfo.Format;
+import com.android.ide.common.layout.BaseViewRule;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutCanvas;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderService;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils;
+import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResult;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.ui.ReferenceChooserDialog;
@@ -47,6 +57,9 @@ import com.google.common.collect.Maps;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.QualifiedName;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.MessageDialogWithToggle;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
@@ -137,7 +150,7 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor {
// TODO: do I have to strip off the @ too?
isFramework = isFramework
|| value.startsWith(ANDROID_PREFIX)
- || value.startsWith(ANDROID_THEME_PREFIX);;
+ || value.startsWith(ANDROID_THEME_PREFIX);
ResourceValue v = resolver.findResValue(text, isFramework);
if (v != null && !value.equals(v.getValue())) {
resValue = v;
@@ -200,7 +213,7 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor {
XmlProperty xmlProperty = (XmlProperty) property;
GraphicalEditorPart graphicalEditor = xmlProperty.getGraphicalEditor();
RenderService service = RenderService.create(graphicalEditor);
- service.setSize(SAMPLE_SIZE, SAMPLE_SIZE);
+ service.setOverrideRenderSize(SAMPLE_SIZE, SAMPLE_SIZE);
BufferedImage drawable = service.renderDrawable(resValue);
if (drawable != null) {
swtImage = SwtUtils.convertToSwt(gc.getDevice(), drawable,
@@ -301,16 +314,101 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor {
@Override
protected boolean setEditorText(Property property, String text) throws Exception {
+ Object oldValue = property.getValue();
+ String old = oldValue != null ? oldValue.toString() : null;
+
+ // If users enters a new id without specifying the @id/@+id prefix, insert it
+ boolean isId = isIdProperty(property);
+ if (isId && !text.startsWith(PREFIX_RESOURCE_REF)) {
+ text = NEW_ID_PREFIX + text;
+ }
+
+ // Handle id refactoring: if you change an id, may want to update references too.
+ // Ask user.
+ if (isId && property instanceof XmlProperty
+ && old != null && !old.isEmpty()
+ && text != null && !text.isEmpty()
+ && !text.equals(old)) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ String refactorPref = store.getString(AdtPrefs.PREFS_REFACTOR_IDS);
+ boolean performRefactor = false;
+ Shell shell = AdtPlugin.getShell();
+ if (refactorPref == null
+ || refactorPref.isEmpty()
+ || refactorPref.equals(MessageDialogWithToggle.PROMPT)) {
+ MessageDialogWithToggle dialog =
+ MessageDialogWithToggle.openYesNoCancelQuestion(
+ shell,
+ "Update References?",
+ "Update all references as well? " +
+ "This will update all XML references and Java R field references.",
+ "Do not show again",
+ false,
+ store,
+ AdtPrefs.PREFS_REFACTOR_IDS);
+ switch (dialog.getReturnCode()) {
+ case IDialogConstants.CANCEL_ID:
+ return false;
+ case IDialogConstants.YES_ID:
+ performRefactor = true;
+ break;
+ case IDialogConstants.NO_ID:
+ performRefactor = false;
+ break;
+ }
+ } else {
+ performRefactor = refactorPref.equals(MessageDialogWithToggle.ALWAYS);
+ }
+ if (performRefactor) {
+ CommonXmlEditor xmlEditor = xmlProperty.getXmlEditor();
+ if (xmlEditor != null) {
+ IProject project = xmlEditor.getProject();
+ if (project != null && shell != null) {
+ RenameResourceWizard.renameResource(shell, project,
+ ResourceType.ID, stripIdPrefix(old), stripIdPrefix(text), false);
+ }
+ }
+ }
+ }
+
property.setValue(text);
+
return true;
}
+ private static boolean isIdProperty(Property property) {
+ XmlProperty xmlProperty = (XmlProperty) property;
+ return xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID);
+ }
+
private void openDialog(PropertyTable propertyTable, Property property) throws Exception {
XmlProperty xmlProperty = (XmlProperty) property;
IAttributeInfo attributeInfo = xmlProperty.getDescriptor().getAttributeInfo();
- boolean isId = xmlProperty.getDescriptor().getXmlLocalName().equals(ATTR_ID);
- if (isId) {
+ if (isIdProperty(property)) {
+ Object value = xmlProperty.getValue();
+ if (value != null && !value.toString().isEmpty()) {
+ GraphicalEditorPart editor = xmlProperty.getGraphicalEditor();
+ if (editor != null) {
+ LayoutCanvas canvas = editor.getCanvasControl();
+ SelectionManager manager = canvas.getSelectionManager();
+
+ NodeProxy primary = canvas.getNodeFactory().create(xmlProperty.getNode());
+ if (primary != null) {
+ RenameResult result = manager.performRename(primary, null);
+ if (result.isCanceled()) {
+ return;
+ } else if (!result.isUnavailable()) {
+ String name = result.getName();
+ String id = NEW_ID_PREFIX + BaseViewRule.stripIdPrefix(name);
+ xmlProperty.setValue(id);
+ return;
+ }
+ }
+ }
+ }
+
// When editing the id attribute, don't offer a resource chooser: usually
// you want to enter a *new* id here
attributeInfo = null;
@@ -370,7 +468,7 @@ class XmlPropertyEditor extends AbstractTextPropertyEditor {
// get the resource repository for this project and the system resources.
ResourceRepository projectRepository =
ResourceManager.getInstance().getProjectResources(project);
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
ReferenceChooserDialog dlg = new ReferenceChooserDialog(
project,
projectRepository,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java
index b01b4b1..d8c85aa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ChangeLayoutRefactoring.java
@@ -41,9 +41,9 @@ import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
index 65edd54..f58ac55 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractIncludeRefactoring.java
@@ -37,10 +37,10 @@ import static com.android.resources.ResourceType.LAYOUT;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
@@ -330,8 +330,9 @@ public class ExtractIncludeRefactoring extends VisualRefactoring {
String newFile = sb.toString();
if (AdtPrefs.getPrefs().getFormatGuiXml()) {
- newFile = XmlPrettyPrinter.prettyPrint(newFile,
- XmlFormatPreferences.create(), XmlFormatStyle.LAYOUT, null /*lineSeparator*/);
+ newFile = EclipseXmlPrettyPrinter.prettyPrint(newFile,
+ EclipseXmlFormatPreferences.create(), XmlFormatStyle.LAYOUT,
+ null /*lineSeparator*/);
}
addFile.setEdit(new InsertEdit(0, newFile));
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java
index ffe6892..9b1770d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/ExtractStyleRefactoring.java
@@ -43,10 +43,10 @@ import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceResolver;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java
index 868d790..fe673a5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/GridLayoutConverter.java
@@ -15,8 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_HORIZ_MASK;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_VERT_MASK;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_BACKGROUND;
import static com.android.SdkConstants.ATTR_COLUMN_COUNT;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_BASELINE;
@@ -34,10 +33,10 @@ import static com.android.SdkConstants.ATTR_LAYOUT_ROW_SPAN;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_ORIENTATION;
import static com.android.SdkConstants.FQCN_GRID_LAYOUT;
+import static com.android.SdkConstants.FQCN_SPACE;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_HORIZONTAL;
import static com.android.SdkConstants.GRAVITY_VALUE_FILL_VERTICAL;
-import static com.android.SdkConstants.GRID_LAYOUT;
import static com.android.SdkConstants.ID_PREFIX;
import static com.android.SdkConstants.LINEAR_LAYOUT;
import static com.android.SdkConstants.NEW_ID_PREFIX;
@@ -51,10 +50,9 @@ import static com.android.SdkConstants.VALUE_HORIZONTAL;
import static com.android.SdkConstants.VALUE_MATCH_PARENT;
import static com.android.SdkConstants.VALUE_VERTICAL;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_HORIZ_MASK;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_VERT_MASK;
-
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.ide.common.api.IViewMetadata.FillPreference;
import com.android.ide.common.layout.BaseLayoutRule;
import com.android.ide.common.layout.GravityHelper;
@@ -66,7 +64,9 @@ import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewEleme
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepository;
+import com.android.ide.eclipse.adt.internal.project.SupportLibraryHelper;
+import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.text.edits.InsertEdit;
@@ -227,9 +227,12 @@ class GridLayoutConverter {
int column = columnFixed.size();
StringBuilder sb = new StringBuilder(64);
String spaceTag = SPACE;
- if (!gridLayout.equals(GRID_LAYOUT) && gridLayout.length() > GRID_LAYOUT.length()) {
- String pkg = gridLayout.substring(0, gridLayout.length() - GRID_LAYOUT.length());
- spaceTag = pkg + spaceTag;
+ IFile file = mRefactoring.getFile();
+ if (file != null) {
+ spaceTag = SupportLibraryHelper.getTagFor(file.getProject(), FQCN_SPACE);
+ if (spaceTag.equals(FQCN_SPACE)) {
+ spaceTag = SPACE;
+ }
}
sb.append('<').append(spaceTag).append(' ');
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
index 51360e8..88423e4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
@@ -20,8 +20,13 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceProcessor;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceWizard;
+import com.android.ide.eclipse.adt.internal.refactorings.core.RenameResourceXmlTextAction;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
+import com.android.resources.ResourceType;
+import com.android.utils.Pair;
import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.IDocument;
@@ -36,6 +41,7 @@ import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import org.eclipse.swt.graphics.Image;
@@ -100,6 +106,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
boolean isTagName = false;
boolean isAttributeName = false;
boolean isStylableAttribute = false;
+ Pair<ResourceType, String> resource = null;
IStructuredModel model = null;
try {
model = xmlEditor.getModelForRead();
@@ -115,6 +122,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
isValue = true;
if (value.startsWith("'@") || value.startsWith("\"@")) { //$NON-NLS-1$ //$NON-NLS-2$
isReferenceValue = true;
+ resource = RenameResourceXmlTextAction.findResource(doc, offset);
}
} else if (type.equals(DOMRegionContext.XML_TAG_NAME)
|| type.equals(DOMRegionContext.XML_TAG_OPEN)
@@ -132,6 +140,8 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
// On the edge of an attribute name and an attribute value
isAttributeName = true;
isStylableAttribute = true;
+ } else if (type.equals(DOMRegionContext.XML_CONTENT)) {
+ resource = RenameResourceXmlTextAction.findResource(doc, offset);
}
}
} finally {
@@ -141,7 +151,7 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
}
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
- if (isTagName || isAttributeName || isValue) {
+ if (isTagName || isAttributeName || isValue || resource != null) {
StructuredTextEditor structuredEditor = xmlEditor.getStructuredTextEditor();
ISelectionProvider provider = structuredEditor.getSelectionProvider();
ISelection selection = provider.getSelection();
@@ -173,6 +183,12 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
if (isValue && !isReferenceValue) {
proposals.add(new RefactoringProposal(xmlEditor,
new ExtractStringRefactoring(file, xmlEditor, textSelection)));
+ } else if (resource != null) {
+ RenameResourceProcessor processor = new RenameResourceProcessor(
+ file.getProject(), resource.getFirst(),
+ resource.getSecond(), null);
+ RenameRefactoring refactoring = new RenameRefactoring(processor);
+ proposals.add(new RefactoringProposal(xmlEditor, refactoring));
}
LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(xmlEditor);
@@ -275,6 +291,12 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
} else if (mRefactoring instanceof ExtractStringRefactoring) {
wizard = new ExtractStringWizard((ExtractStringRefactoring) mRefactoring,
mEditor.getProject());
+ } else if (mRefactoring instanceof RenameRefactoring) {
+ RenameRefactoring refactoring = (RenameRefactoring) mRefactoring;
+ RenameResourceProcessor processor =
+ (RenameResourceProcessor) refactoring.getProcessor();
+ ResourceType type = processor.getType();
+ wizard = new RenameResourceWizard((RenameRefactoring) mRefactoring, type, false);
} else {
throw new IllegalArgumentException();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
index 7f9cc71..e0d6313 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
@@ -15,15 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_HORIZ;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_VERT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_LEFT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_TOP;
-import static com.android.ide.common.layout.GravityHelper.GRAVITY_VERT_MASK;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_BACKGROUND;
import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
import static com.android.SdkConstants.ATTR_LAYOUT_ABOVE;
@@ -58,10 +50,16 @@ import static com.android.SdkConstants.VALUE_N_DP;
import static com.android.SdkConstants.VALUE_TRUE;
import static com.android.SdkConstants.VALUE_VERTICAL;
import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_BOTTOM;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_HORIZ;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_CENTER_VERT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_HORIZ;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_FILL_VERT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_LEFT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_RIGHT;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_TOP;
+import static com.android.ide.common.layout.GravityHelper.GRAVITY_VERT_MASK;
-
-import com.android.SdkConstants;
-import static com.android.SdkConstants.ANDROID_URI;
import com.android.ide.common.layout.GravityHelper;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java
index 1dcc1b7..4eff2cd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java
@@ -22,7 +22,7 @@ import static com.android.SdkConstants.EXT_XML;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
index 8f678c1..0e56bdf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UseCompoundDrawableRefactoring.java
@@ -42,10 +42,10 @@ import static com.android.SdkConstants.VALUE_VERTICAL;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
@@ -353,13 +353,10 @@ public class UseCompoundDrawableRefactoring extends VisualRefactoring {
}
}
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
- XmlPrettyPrinter printer = new XmlPrettyPrinter(formatPrefs, XmlFormatStyle.LAYOUT,
- null /*lineSeparator*/);
- StringBuilder sb = new StringBuilder(300);
- printer.prettyPrint(-1, tempDocument, null, null, sb, false /*openTagOnly*/);
- String xml = sb.toString();
-
+ String xml = EclipseXmlPrettyPrinter.prettyPrint(
+ tempDocument.getDocumentElement(),
+ EclipseXmlFormatPreferences.create(),
+ XmlFormatStyle.LAYOUT, null, false);
TextEdit replace = new ReplaceEdit(mSelectionStart, mSelectionEnd - mSelectionStart, xml);
rootEdit.addChild(replace);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
index c2035f2..904a3a0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
@@ -29,13 +29,13 @@ import static com.android.SdkConstants.XMLNS_PREFIX;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
-import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationChooser;
+import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationDescription;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
@@ -54,7 +54,6 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
@@ -295,6 +294,10 @@ public abstract class VisualRefactoring extends Refactoring {
return args;
}
+ IFile getFile() {
+ return mFile;
+ }
+
// ---- Shared functionality ----
@@ -304,8 +307,7 @@ public abstract class VisualRefactoring extends Refactoring {
try {
// Duplicate the current state into the newly created file
- QualifiedName qname = ConfigurationChooser.NAME_CONFIG_STATE;
- String state = AdtPlugin.getFileProperty(leavingFile, qname);
+ String state = ConfigurationDescription.getDescription(leavingFile);
// TODO: Look for a ".NoTitleBar.Fullscreen" theme version of the current
// theme to show.
@@ -1310,8 +1312,8 @@ public abstract class VisualRefactoring extends Refactoring {
//int end = actual.length() - distanceFromEnd;
//int length = end - start;
//TextEdit format = AndroidXmlFormattingStrategy.format(model, start, length);
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
- String formatted = XmlPrettyPrinter.prettyPrint(actual, formatPrefs, style,
+ EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
+ String formatted = EclipseXmlPrettyPrinter.prettyPrint(actual, formatPrefs, style,
null /*lineSeparator*/);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java
index ff2e9bd..07b00b8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/WrapInRefactoring.java
@@ -28,8 +28,8 @@ import static com.android.SdkConstants.VALUE_WRAP_CONTENT;
import com.android.annotations.NonNull;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.CanvasViewInfo;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
index 7050be4..d9d2722 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
@@ -153,11 +153,17 @@ public class UiViewElementNode extends UiElementNode {
if (className != null && className.length() > 0) {
int index = className.lastIndexOf('.');
if (index != -1) {
- className = className.substring(index + 1);
+ className = "customView"; //$NON-NLS-1$
}
img = icons.getIcon(className);
}
}
+
+ if (img == null) {
+ // Can't have both view.png and View.png; issues on case sensitive vs
+ // case insensitive file systems
+ img = icons.getIcon("View"); //$NON-NLS-1$
+ }
}
if (img == null) {
img = desc.getGenericIcon();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
index 8f78a0f..1492adb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
@@ -48,7 +48,7 @@ public final class ManifestContentAssist extends AndroidContentAssist {
}
@Override
- protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
if (attributeName.endsWith(ATTRIBUTE_MIN_SDK_VERSION)
@@ -60,7 +60,11 @@ public final class ManifestContentAssist extends AndroidContentAssist {
List<Pair<String, String>> choices = new ArrayList<Pair<String, String>>();
int max = AdtUtils.getHighestKnownApiLevel();
// Look for any more recent installed versions the user may have
- IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk == null) {
+ return false;
+ }
+ IAndroidTarget[] targets = sdk.getTargets();
for (IAndroidTarget target : targets) {
AndroidVersion version = target.getVersion();
int apiLevel = version.getApiLevel();
@@ -81,9 +85,10 @@ public final class ManifestContentAssist extends AndroidContentAssist {
addMatchingProposals(proposals, choices.toArray(), offset, node, wordPrefix,
needTag, true /* isAttribute */, false /* isNew */,
skipEndTag /* skipEndTag */, replaceLength);
+ return true;
} else {
- super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
- wordPrefix, skipEndTag, replaceLength);
+ return super.computeAttributeValues(proposals, offset, parentTagName, attributeName,
+ node, wordPrefix, skipEndTag, replaceLength);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java
index b1bfa88..55ebf59 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestEditor.java
@@ -321,8 +321,8 @@ public final class ManifestEditor extends AndroidXmlEditor {
mMarkerMonitor = new IFileListener() {
@Override
public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
- if (file.equals(inputFile)) {
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (isAndroidProject && file.equals(inputFile)) {
processMarkerChanges(markerDeltas);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java
index a1a4b58..4c829d9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/model/UiClassAttributeNode.java
@@ -82,6 +82,10 @@ import org.eclipse.ui.forms.widgets.TableWrapData;
import org.w3c.dom.Element;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* Represents an XML attribute for a class, that can be modified using a simple text field or
@@ -686,7 +690,46 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
@Override
public String[] getPossibleValues(String prefix) {
- // TODO: compute a list of existing classes for content assist completion
+ // Compute a list of existing classes for content assist completion
+ IProject project = getProject();
+ if (project == null || mReferenceClass == null) {
+ return null;
+ }
+
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ IType type = javaProject.findType(mReferenceClass);
+ // Use sets because query sometimes repeats the same class
+ Set<String> libraryTypes = new HashSet<String>(80);
+ Set<String> localTypes = new HashSet<String>(30);
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newTypeHierarchy(new NullProgressMonitor());
+ IType[] allSubtypes = hierarchy.getAllSubtypes(type);
+ for (IType subType : allSubtypes) {
+ int flags = subType.getFlags();
+ if (Flags.isPublic(flags) && !Flags.isAbstract(flags)) {
+ String fqcn = subType.getFullyQualifiedName();
+ if (subType.getResource() != null) {
+ localTypes.add(fqcn);
+ } else {
+ libraryTypes.add(fqcn);
+ }
+ }
+ }
+ }
+
+ List<String> local = new ArrayList<String>(localTypes);
+ List<String> library = new ArrayList<String>(libraryTypes);
+ Collections.sort(local);
+ Collections.sort(library);
+ List<String> combined = new ArrayList<String>(local.size() + library.size());
+ combined.addAll(local);
+ combined.addAll(library);
+ return combined.toArray(new String[combined.size()]);
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+
return null;
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java
index 71cb35d..ffe637c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiAttributeNode.java
@@ -16,17 +16,7 @@
package com.android.ide.eclipse.adt.internal.editors.uimodel;
-import static com.android.SdkConstants.ATTR_ID;
-import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
-import static com.android.SdkConstants.ATTR_LAYOUT_RESOURCE_PREFIX;
-import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
-import static com.android.SdkConstants.ATTR_NAME;
-import static com.android.SdkConstants.ATTR_STYLE;
-import static com.android.ide.eclipse.adt.internal.editors.color.ColorDescriptors.ATTR_COLOR;
-import static com.google.common.base.Strings.nullToEmpty;
-
-import com.android.annotations.NonNull;
-import com.android.annotations.Nullable;
+import com.android.ide.common.xml.XmlAttributeSortOrder;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
@@ -34,8 +24,6 @@ import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.forms.IManagedForm;
import org.w3c.dom.Node;
-import java.util.Comparator;
-
/**
* Represents an XML attribute that can be modified by the XML editor's user interface.
* <p/>
@@ -177,107 +165,10 @@ public abstract class UiAttributeNode implements Comparable<UiAttributeNode> {
public abstract void commit();
// ---- Implements Comparable ----
+
@Override
public int compareTo(UiAttributeNode o) {
- return compareAttributes(mDescriptor.getXmlLocalName(), o.mDescriptor.getXmlLocalName());
- }
-
- /**
- * Returns {@link Comparator} values for ordering attributes in the following
- * order:
- * <ul>
- * <li> id
- * <li> style
- * <li> layout_width
- * <li> layout_height
- * <li> other layout params, sorted alphabetically
- * <li> other attributes, sorted alphabetically
- * </ul>
- *
- * @param name1 the first attribute name to compare
- * @param name2 the second attribute name to compare
- * @return a negative number if name1 should be ordered before name2
- */
- public static int compareAttributes(String name1, String name2) {
- int priority1 = getAttributePriority(name1);
- int priority2 = getAttributePriority(name2);
- if (priority1 != priority2) {
- return priority1 - priority2;
- }
-
- // Sort remaining attributes alphabetically
- return name1.compareTo(name2);
- }
-
- /**
- * Returns {@link Comparator} values for ordering attributes in the following
- * order:
- * <ul>
- * <li> id
- * <li> style
- * <li> layout_width
- * <li> layout_height
- * <li> other layout params, sorted alphabetically
- * <li> other attributes, sorted alphabetically, first by namespace, then by name
- * </ul>
- * @param prefix1 the namespace prefix, if any, of {@code name1}
- * @param name1 the first attribute name to compare
- * @param prefix2 the namespace prefix, if any, of {@code name2}
- * @param name2 the second attribute name to compare
- * @return a negative number if name1 should be ordered before name2
- */
- public static int compareAttributes(
- @Nullable String prefix1, @NonNull String name1,
- @Nullable String prefix2, @NonNull String name2) {
- int priority1 = getAttributePriority(name1);
- int priority2 = getAttributePriority(name2);
- if (priority1 != priority2) {
- return priority1 - priority2;
- }
-
- int namespaceDelta = nullToEmpty(prefix1).compareTo(nullToEmpty(prefix2));
- if (namespaceDelta != 0) {
- return namespaceDelta;
- }
-
- // Sort remaining attributes alphabetically
- return name1.compareTo(name2);
- }
-
-
- /** Returns a sorting priority for the given attribute name */
- private static int getAttributePriority(String name) {
- if (ATTR_ID.equals(name)) {
- return 10;
- }
-
- if (ATTR_NAME.equals(name)) {
- return 15;
- }
-
- if (ATTR_STYLE.equals(name)) {
- return 20;
- }
-
- if (name.startsWith(ATTR_LAYOUT_RESOURCE_PREFIX)) {
- // Width and height are special cased because we (a) want width and height
- // before the other layout attributes, and (b) we want width to sort before height
- // even though it comes after it alphabetically.
- if (name.equals(ATTR_LAYOUT_WIDTH)) {
- return 30;
- }
- if (name.equals(ATTR_LAYOUT_HEIGHT)) {
- return 40;
- }
-
- return 50;
- }
-
- // "color" sorts to the end
- if (ATTR_COLOR.equals(name)) {
- return 100;
- }
-
- return 60;
+ return XmlAttributeSortOrder.compareAttributes(mDescriptor.getXmlLocalName(),
+ o.mDescriptor.getXmlLocalName());
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
index f905c73..ed447c6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
@@ -26,6 +26,7 @@ import com.android.SdkConstants;
import com.android.annotations.VisibleForTesting;
import com.android.ide.common.api.IAttributeInfo.Format;
import com.android.ide.common.resources.platform.AttributeInfo;
+import com.android.ide.common.xml.XmlAttributeSortOrder;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
@@ -1663,7 +1664,7 @@ public class UiElementNode implements IPropertySource {
List<Attr> move = new ArrayList<Attr>();
for (int i = 0, n = attributes.getLength(); i < n; i++) {
Attr attribute = (Attr) attributes.item(i);
- if (UiAttributeNode.compareAttributes(
+ if (XmlAttributeSortOrder.compareAttributes(
attribute.getPrefix(), attribute.getLocalName(),
firstNamePrefix, firstName) > 0) {
move.add(attribute);
@@ -1699,7 +1700,7 @@ public class UiElementNode implements IPropertySource {
String domAttributeName = domAttribute.getLocalName();
String uiAttributeName = uiAttribute.getDescriptor().getXmlLocalName();
- compare = UiAttributeNode.compareAttributes(domAttributeName,
+ compare = XmlAttributeSortOrder.compareAttributes(domAttributeName,
uiAttributeName);
} else {
compare = 1;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
index bce3db4..eb51d3f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiResourceAttributeNode.java
@@ -84,6 +84,13 @@ import java.util.regex.Pattern;
public class UiResourceAttributeNode extends UiTextAttributeNode {
private ResourceType mType;
+ /**
+ * Creates a new {@linkplain UiResourceAttributeNode}
+ *
+ * @param type the associated resource type
+ * @param attributeDescriptor the attribute descriptor for this attribute
+ * @param uiParent the parent ui node, if any
+ */
public UiResourceAttributeNode(ResourceType type,
AttributeDescriptor attributeDescriptor, UiElementNode uiParent) {
super(attributeDescriptor, uiParent);
@@ -138,10 +145,15 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
}
/**
- * Shows a dialog letting the user choose a set of enum, and returns a string
- * containing the result.
+ * Shows a dialog letting the user choose a set of enum, and returns a
+ * string containing the result.
+ *
+ * @param shell the parent shell
+ * @param currentValue an initial value, if any
+ * @return the chosen string, or null
*/
- public String showDialog(Shell shell, String currentValue) {
+ @Nullable
+ public String showDialog(@NonNull Shell shell, @Nullable String currentValue) {
// we need to get the project of the file being edited.
UiElementNode uiNode = getUiParent();
AndroidXmlEditor editor = uiNode.getEditor();
@@ -154,17 +166,8 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
if (mType != null) {
// get the Target Data to get the system resources
AndroidTargetData data = editor.getTargetData();
- ResourceRepository frameworkRepository = data.getFrameworkResources();
-
- // open a resource chooser dialog for specified resource type.
- ResourceChooser dlg = new ResourceChooser(project,
- mType,
- projectRepository,
- frameworkRepository,
- shell);
-
- dlg.setCurrentResource(currentValue);
-
+ ResourceChooser dlg = ResourceChooser.create(project, mType, data, shell)
+ .setCurrentResource(currentValue);
if (dlg.open() == Window.OK) {
return dlg.getCurrentResource();
}
@@ -329,6 +332,13 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
if (resTypes.contains(ResourceType.ATTR)
|| resTypes.contains(ResourceType.STYLE)) {
results.add(PREFIX_THEME_REF + ResourceType.ATTR.getName() + '/');
+ if (prefix != null && prefix.startsWith(ANDROID_THEME_PREFIX)) {
+ // including attr isn't required
+ for (ResourceItem item : repository.getResourceItemsOfType(
+ ResourceType.ATTR)) {
+ results.add(ANDROID_THEME_PREFIX + item.getName());
+ }
+ }
}
return results.toArray(new String[results.size()]);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
index bd6c079..d0ee92c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
@@ -70,7 +70,7 @@ public class ValuesContentAssist extends AndroidContentAssist {
}
@Override
- protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset,
+ protected boolean computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
super.computeAttributeValues(proposals, offset, parentTagName, attributeName, node,
@@ -129,10 +129,12 @@ public class ValuesContentAssist extends AndroidContentAssist {
addMatchingProposals(proposals, sorted.toArray(), offset, node, wordPrefix,
needTag, true /* isAttribute */, false /* isNew */,
skipEndTag /* skipEndTag */, replaceLength);
- return;
+ return true;
}
}
}
+
+ return false;
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java
index d9aab14..4281f19 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java
@@ -377,6 +377,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
AvdInfo preferredAvd = null;
if (config.mAvdName != null) {
preferredAvd = avdManager.getAvd(config.mAvdName, true /*validAvdOnly*/);
+ }
+
+ if (preferredAvd != null) {
IAndroidTarget preferredAvdTarget = preferredAvd.getTarget();
if (preferredAvdTarget != null
&& !preferredAvdTarget.getVersion().canRun(minApiVersion)) {
@@ -606,7 +609,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
// open the chooser dialog. It'll fill 'response' with the device to use
// or the AVD to launch.
DeviceChooserDialog dialog = new DeviceChooserDialog(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
response, launchInfo.getPackageName(),
desiredProjectTarget, minApiVersion);
if (dialog.open() == Dialog.OK) {
@@ -1377,7 +1380,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener
}
}
} catch (CoreException e) {
- MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
+ MessageDialog.openError(AdtPlugin.getShell(),
"Launch Error", e.getStatus().getMessage());
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
index 80dac65..b047b1b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAnnotation.java
@@ -25,6 +25,7 @@ import static org.eclipse.jdt.core.dom.SingleMemberAnnotation.VALUE_PROPERTY;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
@@ -331,7 +332,8 @@ class AddSuppressAnnotation implements IMarkerResolution2 {
}
int api = -1;
- if (id.equals(ApiDetector.UNSUPPORTED.getId())) {
+ if (id.equals(ApiDetector.UNSUPPORTED.getId()) ||
+ id.equals(ApiDetector.INLINED.getId())) {
String message = marker.getAttribute(IMarker.MESSAGE, null);
if (message != null) {
Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
@@ -343,7 +345,8 @@ class AddSuppressAnnotation implements IMarkerResolution2 {
}
Issue issue = EclipseLintClient.getRegistry().getIssue(id);
- boolean isClassDetector = issue != null && issue.getScope().contains(Scope.CLASS_FILE);
+ boolean isClassDetector = issue != null && issue.getImplementation().getScope().contains(
+ Scope.CLASS_FILE);
// Don't offer to suppress (with an annotation) the annotation checks
if (issue == AnnotationDetector.ISSUE) {
@@ -407,7 +410,7 @@ class AddSuppressAnnotation implements IMarkerResolution2 {
// @TargetApi is only valid on methods and classes, not fields etc
&& (body instanceof MethodDeclaration
|| body instanceof TypeDeclaration)) {
- String apiString = AdtUtils.getBuildCodes(api);
+ String apiString = SdkVersionInfo.getBuildCode(api);
if (apiString == null) {
apiString = Integer.toString(api);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index 23943d5..b77b475 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -17,15 +17,19 @@
package com.android.ide.eclipse.adt.internal.lint;
import static com.android.SdkConstants.ATTR_IGNORE;
+import static com.android.SdkConstants.ATTR_TARGET_API;
import static com.android.SdkConstants.DOT_XML;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.tools.lint.checks.ApiDetector;
+import com.google.common.collect.Lists;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
@@ -38,6 +42,12 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Fix for adding {@code tools:ignore="id"} attributes in XML files.
*/
@@ -47,14 +57,26 @@ class AddSuppressAttribute implements ICompletionProposal {
private final IMarker mMarker;
private final Element mElement;
private final String mDescription;
+ /**
+ * Should it create a {@code tools:targetApi} attribute instead of a
+ * {@code tools:ignore} attribute? If so pass a non null API level
+ */
+ private final String mTargetApi;
+
- private AddSuppressAttribute(AndroidXmlEditor editor, String id, IMarker marker,
- Element element, String description) {
+ private AddSuppressAttribute(
+ @NonNull AndroidXmlEditor editor,
+ @NonNull String id,
+ @NonNull IMarker marker,
+ @NonNull Element element,
+ @NonNull String description,
+ @Nullable String targetApi) {
mEditor = editor;
mId = id;
mMarker = marker;
mElement = element;
mDescription = description;
+ mTargetApi = targetApi;
}
@Override
@@ -84,7 +106,16 @@ class AddSuppressAttribute implements ICompletionProposal {
@Override
public void apply(IDocument document) {
- AdtUtils.setToolsAttribute(mEditor, mElement, "Suppress Lint Warning", ATTR_IGNORE, mId,
+ String attribute;
+ String value;
+ if (mTargetApi != null) {
+ attribute = ATTR_TARGET_API;
+ value = mTargetApi;
+ } else {
+ attribute = ATTR_IGNORE;
+ value = mId;
+ }
+ AdtUtils.setToolsAttribute(mEditor, mElement, mDescription, attribute, value,
true /*reveal*/, true /*append*/);
try {
@@ -92,7 +123,7 @@ class AddSuppressAttribute implements ICompletionProposal {
// (so the user doesn't have to re-run lint just to see it disappear)
mMarker.delete();
} catch (CoreException e) {
- AdtPlugin.log(e, "Could not add suppress annotation");
+ AdtPlugin.log(e, "Could not remove marker");
}
}
@@ -103,17 +134,17 @@ class AddSuppressAttribute implements ICompletionProposal {
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
* @param id the issue id
- * @return a fix for this marker, or null if unable
+ * @return a list of fixes for this marker, possibly empty
*/
- @Nullable
- public static AddSuppressAttribute createFix(
+ @NonNull
+ public static List<AddSuppressAttribute> createFixes(
@NonNull AndroidXmlEditor editor,
@NonNull IMarker marker,
@NonNull String id) {
// This only applies to XML files:
String fileName = marker.getResource().getName();
if (!fileName.endsWith(DOT_XML)) {
- return null;
+ return Collections.emptyList();
}
int offset = marker.getAttribute(IMarker.CHAR_START, -1);
@@ -127,7 +158,7 @@ class AddSuppressAttribute implements ICompletionProposal {
node = DomUtilities.getNode(editor.getStructuredDocument(), offset);
}
if (node == null) {
- return null;
+ return Collections.emptyList();
}
Document document = node.getOwnerDocument();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
@@ -136,13 +167,41 @@ class AddSuppressAttribute implements ICompletionProposal {
if (node == null) {
node = document.getDocumentElement();
if (node == null) {
- return null;
+ return Collections.emptyList();
}
}
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
- return new AddSuppressAttribute(editor, id, marker, element, desc);
+ List<AddSuppressAttribute> fixes = Lists.newArrayListWithExpectedSize(2);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element, desc, null));
+
+ int api = -1;
+ if (id.equals(ApiDetector.UNSUPPORTED.getId())
+ || id.equals(ApiDetector.INLINED.getId())) {
+ String message = marker.getAttribute(IMarker.MESSAGE, null);
+ if (message != null) {
+ Pattern pattern = Pattern.compile("\\s(\\d+)\\s"); //$NON-NLS-1$
+ Matcher matcher = pattern.matcher(message);
+ if (matcher.find()) {
+ api = Integer.parseInt(matcher.group(1));
+ String targetApi;
+ String buildCode = SdkVersionInfo.getBuildCode(api);
+ if (buildCode != null) {
+ targetApi = buildCode.toLowerCase(Locale.US);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element,
+ String.format("Add targetApi '%1$s\' to element", targetApi),
+ targetApi));
+ }
+ targetApi = Integer.toString(api);
+ fixes.add(new AddSuppressAttribute(editor, id, marker, element,
+ String.format("Add targetApi '%1$s\' to element", targetApi),
+ targetApi));
+ }
+ }
+ }
+
+ return fixes;
}
/**
@@ -170,7 +229,7 @@ class AddSuppressAttribute implements ICompletionProposal {
node = node.getOwnerDocument().getDocumentElement();
String desc = String.format("Add ignore '%1$s\' to element", id);
Element element = (Element) node;
- return new AddSuppressAttribute(editor, id, marker, element, desc);
+ return new AddSuppressAttribute(editor, id, marker, element, desc, null);
}
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
index 44b676f..628972f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ConvertToDpFix.java
@@ -54,7 +54,7 @@ final class ConvertToDpFix extends DocumentFix implements IInputValidator {
@Override
protected void apply(IDocument document, IStructuredModel model, Node node, int start,
int end) {
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
InputDensityDialog densityDialog = new InputDensityDialog(shell);
if (densityDialog.open() == Window.OK) {
int dpi = densityDialog.getDensity();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/DosLineEndingsFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/DosLineEndingsFix.java
new file mode 100644
index 0000000..9a5456b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/DosLineEndingsFix.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.lint;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+
+/** Quickfix for correcting line endings in the file */
+class DosLineEndingsFix extends LintFix {
+
+ protected DosLineEndingsFix(String id, IMarker marker) {
+ super(id, marker);
+ }
+
+ @Override
+ public boolean needsFocus() {
+ return false;
+ }
+
+ @Override
+ public boolean isCancelable() {
+ return false;
+ }
+
+ @Override
+ public String getDisplayString() {
+ return "Fix line endings";
+ }
+
+ @Override
+ public void apply(IDocument document) {
+ char next = 0;
+ for (int i = document.getLength() - 1; i >= 0; i--) {
+ try {
+ char c = document.getChar(i);
+ if (c == '\r' && next != '\n') {
+ document.replace(i, 1, "\n"); //$NON-NLS-1$
+ }
+ next = c;
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ return;
+ }
+ }
+
+ deleteMarker();
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index b3303b3..45ae2c5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -37,6 +37,7 @@ import com.android.tools.lint.client.api.IDomParser;
import com.android.tools.lint.client.api.IJavaParser;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.client.api.LintClient;
+import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.DefaultPosition;
import com.android.tools.lint.detector.api.Detector;
@@ -59,9 +60,13 @@ import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
@@ -100,6 +105,7 @@ import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
+import lombok.ast.TypeReference;
import lombok.ast.ecj.EcjTreeConverter;
import lombok.ast.grammar.ParseProblem;
import lombok.ast.grammar.Source;
@@ -282,6 +288,19 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return null;
}
+ @Override
+ @NonNull
+ public String getProjectName(@NonNull Project project) {
+ // Initialize the lint project's name to the name of the Eclipse project,
+ // which might differ from the directory name
+ IProject eclipseProject = getProject(project);
+ if (eclipseProject != null) {
+ return eclipseProject.getName();
+ }
+
+ return super.getProjectName(project);
+ }
+
@NonNull
@Override
public Configuration getConfiguration(@NonNull Project project) {
@@ -526,6 +545,9 @@ public class EclipseLintClient extends LintClient implements IDomParser {
*/
private static Pair<Integer, Integer> adjustOffsets(IDocument doc, int startOffset,
int endOffset) {
+ int originalStart = startOffset;
+ int originalEnd = endOffset;
+
if (doc != null) {
while (endOffset > startOffset && endOffset < doc.getLength()) {
try {
@@ -547,6 +569,9 @@ public class EclipseLintClient extends LintClient implements IDomParser {
char c = doc.getChar(lineEnd);
if (c == '\n' || c == '\r') {
endOffset = lineEnd;
+ if (endOffset > 0 && doc.getChar(endOffset - 1) == '\r') {
+ endOffset--;
+ }
break;
}
} catch (BadLocationException e) {
@@ -557,6 +582,13 @@ public class EclipseLintClient extends LintClient implements IDomParser {
}
}
+ if (startOffset >= endOffset) {
+ // Selecting nothing (for example, for the mangled CRLF delimiter issue selecting
+ // just the newline)
+ // In that case, use the real range
+ return Pair.of(originalStart, originalEnd);
+ }
+
return Pair.of(startOffset, endOffset);
}
@@ -583,8 +615,8 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return "";
}
- String summary = issue.getDescription();
- String explanation = issue.getExplanationAsSimpleText();
+ String summary = issue.getDescription(Issue.OutputFormat.TEXT);
+ String explanation = issue.getExplanation(Issue.OutputFormat.TEXT);
StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
try {
@@ -872,6 +904,125 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return Sdk.getCurrent().getTargets();
}
+ private boolean mSearchForSuperClasses;
+
+ /**
+ * Sets whether this client should search for super types on its own. This
+ * is typically not needed when doing a full lint run (because lint will
+ * look at all classes and libraries), but is useful during incremental
+ * analysis when lint is only looking at a subset of classes. In that case,
+ * we want to use Eclipse's data structures for super classes.
+ *
+ * @param search whether to use a custom Eclipse search for super class
+ * names
+ */
+ public void setSearchForSuperClasses(boolean search) {
+ mSearchForSuperClasses = search;
+ }
+
+ /**
+ * Whether this lint client is searching for super types. See
+ * {@link #setSearchForSuperClasses(boolean)} for details.
+ *
+ * @return whether the client will search for super types
+ */
+ public boolean getSearchForSuperClasses() {
+ return mSearchForSuperClasses;
+ }
+
+ @Override
+ @Nullable
+ public String getSuperClass(@NonNull Project project, @NonNull String name) {
+ if (!mSearchForSuperClasses) {
+ // Super type search using the Eclipse index is potentially slow, so
+ // only do this when necessary
+ return null;
+ }
+
+ IProject eclipseProject = getProject(project);
+ if (eclipseProject == null) {
+ return null;
+ }
+
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(eclipseProject);
+ if (javaProject == null) {
+ return null;
+ }
+
+ String typeFqcn = ClassContext.getFqcn(name);
+ IType type = javaProject.findType(typeFqcn);
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
+ IType superType = hierarchy.getSuperclass(type);
+ if (superType != null) {
+ String key = superType.getKey();
+ if (!key.isEmpty()
+ && key.charAt(0) == 'L'
+ && key.charAt(key.length() - 1) == ';') {
+ return key.substring(1, key.length() - 1);
+ } else {
+ String fqcn = superType.getFullyQualifiedName();
+ return ClassContext.getInternalName(fqcn);
+ }
+ }
+ }
+ } catch (JavaModelException e) {
+ log(Severity.INFORMATIONAL, e, null);
+ } catch (CoreException e) {
+ log(Severity.INFORMATIONAL, e, null);
+ }
+
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public Boolean isSubclassOf(
+ @NonNull Project project,
+ @NonNull String name, @NonNull
+ String superClassName) {
+ if (!mSearchForSuperClasses) {
+ // Super type search using the Eclipse index is potentially slow, so
+ // only do this when necessary
+ return null;
+ }
+
+ IProject eclipseProject = getProject(project);
+ if (eclipseProject == null) {
+ return null;
+ }
+
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(eclipseProject);
+ if (javaProject == null) {
+ return null;
+ }
+
+ String typeFqcn = ClassContext.getFqcn(name);
+ IType type = javaProject.findType(typeFqcn);
+ if (type != null) {
+ ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
+ IType[] allSupertypes = hierarchy.getAllSuperclasses(type);
+ if (allSupertypes != null) {
+ String target = 'L' + superClassName + ';';
+ for (IType superType : allSupertypes) {
+ if (target.equals(superType.getKey())) {
+ return Boolean.TRUE;
+ }
+ }
+ return Boolean.FALSE;
+ }
+ }
+ } catch (JavaModelException e) {
+ log(Severity.INFORMATIONAL, e, null);
+ } catch (CoreException e) {
+ log(Severity.INFORMATIONAL, e, null);
+ }
+
+ return null;
+ }
+
private static class LazyLocation extends Location implements Location.Handle {
private final IStructuredDocument mDocument;
private final IndexedRegion mRegion;
@@ -1063,6 +1214,19 @@ public class EclipseLintClient extends LintClient implements IDomParser {
@NonNull lombok.ast.Node compilationUnit) {
}
+ @Override
+ @Nullable
+ public lombok.ast.Node resolve(@NonNull JavaContext context,
+ @NonNull lombok.ast.Node node) {
+ return null;
+ }
+
+ @Override
+ @Nullable
+ public TypeReference getType(@NonNull JavaContext context, @NonNull lombok.ast.Node node) {
+ return null;
+ }
+
/* Handle for creating positions cheaply and returning full fledged locations later */
private class LocationHandle implements Handle {
private File mFile;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
index d69412b..43cd48d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
@@ -127,7 +127,9 @@ public class EclipseLintRunner {
@Nullable IResource source,
boolean show) {
if (resources != null && !resources.isEmpty()) {
- resources = addLibraries(resources);
+ if (!AdtPrefs.getPrefs().getSkipLibrariesFromLint()) {
+ resources = addLibraries(resources);
+ }
cancelCurrentJobs(false);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ExtractStringFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ExtractStringFix.java
index 196743b..7eafd43 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ExtractStringFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/ExtractStringFix.java
@@ -16,7 +16,7 @@
package com.android.ide.eclipse.adt.internal.lint;
import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
@@ -28,6 +28,7 @@ import org.eclipse.jface.text.TextSelection;
import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
@@ -59,16 +60,13 @@ final class ExtractStringFix extends DocumentFix {
@Override
protected void apply(IDocument document, IStructuredModel model, Node node, int start,
int end) {
- // Invoke refactoring
- LayoutEditorDelegate delegate =
- LayoutEditorDelegate.fromEditor(AdtUtils.getActiveEditor());
-
- if (delegate != null) {
+ IEditorPart editorPart = AdtUtils.getActiveEditor();
+ if (editorPart instanceof AndroidXmlEditor) {
IFile file = (IFile) mMarker.getResource();
ITextSelection selection = new TextSelection(start, end - start);
ExtractStringRefactoring refactoring =
- new ExtractStringRefactoring(file, delegate.getEditor(), selection);
+ new ExtractStringRefactoring(file, editorPart, selection);
RefactoringWizard wizard = new ExtractStringWizard(refactoring, file.getProject());
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
try {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
index df8d9af..ebb9a59 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintDeltaProcessor.java
@@ -167,9 +167,10 @@ public class LintDeltaProcessor implements Runnable {
@Override
public void fileChanged(@NonNull IFile file,
@NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
- if (flags == IResourceDelta.MARKERS) {
- // ONLY the markers changed. Ignore these since they happen
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (!isAndroidProject || flags == IResourceDelta.MARKERS) {
+ // If not an Android project or ONLY the markers changed.
+ // Ignore these since they happen
// when we add markers for lint errors found in the current file,
// which would cause us to repeatedly enter this method over and over
// again.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
index 7683f8d..401703e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFix.java
@@ -21,9 +21,11 @@ import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.checks.DetectMissingPrefix;
+import com.android.tools.lint.checks.DosLineEndingDetector;
import com.android.tools.lint.checks.HardcodedValuesDetector;
import com.android.tools.lint.checks.InefficientWeightDetector;
import com.android.tools.lint.checks.ManifestOrderDetector;
+import com.android.tools.lint.checks.MissingIdDetector;
import com.android.tools.lint.checks.ObsoleteLayoutParamsDetector;
import com.android.tools.lint.checks.PxUsageDetector;
import com.android.tools.lint.checks.ScrollViewChildDetector;
@@ -35,6 +37,7 @@ import com.android.tools.lint.checks.TypographyDetector;
import com.android.tools.lint.checks.UseCompoundDrawableDetector;
import com.android.tools.lint.checks.UselessViewDetector;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Issue.OutputFormat;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.CoreException;
@@ -107,7 +110,7 @@ abstract class LintFix implements ICompletionProposal {
public String getAdditionalProposalInfo() {
Issue issue = EclipseLintClient.getRegistry().getIssue(mId);
if (issue != null) {
- return issue.getExplanationAsHtml();
+ return issue.getExplanation(OutputFormat.HTML);
}
return null;
@@ -150,6 +153,7 @@ abstract class LintFix implements ICompletionProposal {
sFixes.put(AccessibilityDetector.ISSUE.getId(), SetAttributeFix.class);
sFixes.put(InefficientWeightDetector.BASELINE_WEIGHTS.getId(), SetAttributeFix.class);
sFixes.put(ManifestOrderDetector.ALLOW_BACKUP.getId(), SetAttributeFix.class);
+ sFixes.put(MissingIdDetector.ISSUE.getId(), SetAttributeFix.class);
sFixes.put(HardcodedValuesDetector.ISSUE.getId(), ExtractStringFix.class);
sFixes.put(UselessViewDetector.USELESS_LEAF.getId(), RemoveUselessViewFix.class);
sFixes.put(UselessViewDetector.USELESS_PARENT.getId(), RemoveUselessViewFix.class);
@@ -168,6 +172,7 @@ abstract class LintFix implements ICompletionProposal {
sFixes.put(UseCompoundDrawableDetector.ISSUE.getId(),
UseCompoundDrawableDetectorFix.class);
sFixes.put(TypoDetector.ISSUE.getId(), TypoFix.class);
+ sFixes.put(DosLineEndingDetector.ISSUE.getId(), DosLineEndingsFix.class);
// ApiDetector.UNSUPPORTED is provided as a marker resolution rather than
// a quick assistant (the marker resolution adds a suitable @TargetApi annotation)
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
index 5d2a2bb..ce5fd55 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintFixGenerator.java
@@ -27,6 +27,7 @@ import com.android.tools.lint.client.api.Configuration;
import com.android.tools.lint.client.api.DefaultConfiguration;
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Issue.OutputFormat;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
import com.android.utils.SdkUtils;
@@ -189,11 +190,7 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
String message = marker.getAttribute(IMarker.MESSAGE, null);
proposals.add(new MoreInfoProposal(id, message));
- ICompletionProposal fix = AddSuppressAttribute.createFix(editor, marker, id);
- if (fix != null) {
- proposals.add(fix);
- }
-
+ proposals.addAll(AddSuppressAttribute.createFixes(editor, marker, id));
proposals.add(new SuppressProposal(file, id, false));
proposals.add(new SuppressProposal(file.getProject(), id, true /* all */));
proposals.add(new SuppressProposal(file, id, true /* all */));
@@ -296,11 +293,11 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
assert isXml;
if (part instanceof AndroidXmlEditor) {
AndroidXmlEditor editor = (AndroidXmlEditor) part;
- AddSuppressAttribute fix = AddSuppressAttribute.createFix(editor,
+ List<AddSuppressAttribute> fixes = AddSuppressAttribute.createFixes(editor,
marker, id);
- if (fix != null) {
+ if (fixes.size() > 0) {
IStructuredDocument document = editor.getStructuredDocument();
- fix.apply(document);
+ fixes.get(0).apply(document);
}
}
}
@@ -492,11 +489,12 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
sb.append('\n').append('\n');
sb.append("Issue Explanation:");
sb.append('\n');
- if (issue.getExplanation() != null) {
+ String explanation = issue.getExplanation(Issue.OutputFormat.TEXT);
+ if (explanation != null && !explanation.isEmpty()) {
sb.append('\n');
- sb.append(issue.getExplanationAsSimpleText());
+ sb.append(explanation);
} else {
- sb.append(issue.getDescription());
+ sb.append(issue.getDescription(Issue.OutputFormat.TEXT));
}
if (issue.getMoreInfo() != null) {
@@ -505,13 +503,13 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
sb.append(issue.getMoreInfo());
}
- MessageDialog.openInformation(AdtPlugin.getDisplay().getActiveShell(), "More Info",
+ MessageDialog.openInformation(AdtPlugin.getShell(), "More Info",
sb.toString());
}
@Override
public String getDisplayString() {
- return "Explain Issue";
+ return String.format("Explain Issue (%1$s)", mId);
}
// ---- Implements MarkerResolution2 ----
@@ -547,7 +545,8 @@ public class LintFixGenerator implements IMarkerResolutionGenerator2, IQuickAssi
public String getAdditionalProposalInfo() {
return "Provides more information about this issue."
+ "<br><br>" //$NON-NLS-1$
- + EclipseLintClient.getRegistry().getIssue(mId).getExplanationAsHtml();
+ + EclipseLintClient.getRegistry().getIssue(mId).getExplanation(
+ OutputFormat.HTML);
}
@Override
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
index 2851c47..bd1eb72 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
@@ -140,10 +140,11 @@ final class LintJob extends Job {
if (issue == null) {
continue;
}
- if (issue.isAdequate(scope)) {
+ if (issue.getImplementation().isAdequate(scope)) {
marker.delete();
}
}
+ mClient.setSearchForSuperClasses(true);
} else {
EclipseLintClient.clearMarkers(mResources);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
index a0d414e..1de903e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -21,6 +21,7 @@ import static com.android.SdkConstants.DOT_XML;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.tools.lint.detector.api.LintUtils;
import org.eclipse.core.resources.IFile;
@@ -80,7 +81,7 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
List<IProject> projects = AdtUtils.getSelectedProjects(selection);
if (projects.isEmpty() && warn) {
- MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(), "Lint",
+ MessageDialog.openWarning(AdtPlugin.getShell(), "Lint",
"Could not run Lint: Select an Android project first.");
}
@@ -110,7 +111,8 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
IconFactory iconFactory = IconFactory.getInstance();
ImageDescriptor allIcon = iconFactory.getImageDescriptor("lintrun"); //$NON-NLS-1$
- LintMenuAction allAction = new LintMenuAction("Check All Projects", allIcon, false, null);
+ LintMenuAction allAction = new LintMenuAction("Check All Projects", allIcon,
+ ACTION_RUN, null);
addAction(allAction);
addSeparator();
@@ -121,7 +123,7 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
IProject p = project.getProject();
ImageDescriptor icon = ImageDescriptor.createFromImage(provider.getImage(p));
String label = String.format("Check %1$s", p.getName());
- LintMenuAction projectAction = new LintMenuAction(label, icon, false, p);
+ LintMenuAction projectAction = new LintMenuAction(label, icon, ACTION_RUN, p);
addAction(projectAction);
}
@@ -131,7 +133,8 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
// Currently only supported for XML files
if (file != null && LintUtils.endsWith(file.getName(), DOT_XML)) {
ImageDescriptor icon = ImageDescriptor.createFromImage(provider.getImage(file));
- IAction fileAction = new LintMenuAction("Check Current File", icon, false, file);
+ IAction fileAction = new LintMenuAction("Check Current File", icon, ACTION_RUN,
+ file);
addSeparator();
addAction(fileAction);
@@ -140,10 +143,17 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
ImageDescriptor clear = images.getImageDescriptor(ISharedImages.IMG_ELCL_REMOVEALL);
- LintMenuAction clearAction = new LintMenuAction("Clear Lint Warnings", clear, true, null);
+ LintMenuAction clearAction = new LintMenuAction("Clear Lint Warnings", clear, ACTION_CLEAR,
+ null);
addSeparator();
addAction(clearAction);
+ LintMenuAction excludeAction = new LintMenuAction("Skip Library Project Dependencies",
+ allIcon, ACTION_TOGGLE_EXCLUDE, null);
+ addSeparator();
+ addAction(excludeAction);
+ excludeAction.setChecked(AdtPrefs.getPrefs().getSkipLibrariesFromLint());
+
return mMenu;
}
@@ -161,32 +171,44 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
return null;
}
+ private static final int ACTION_RUN = 1;
+ private static final int ACTION_CLEAR = 2;
+ private static final int ACTION_TOGGLE_EXCLUDE = 3;
+
/**
* Actions in the pulldown context menu: run lint or clear lint markers on
* the given resource
*/
private static class LintMenuAction extends Action {
- private final boolean mClear;
private final IResource mResource;
+ private final int mAction;
/**
* Creates a new context menu action
*
* @param text the label
* @param descriptor the icon
- * @param clear if true, clear lint markers otherwise check the resource
+ * @param action the action to run: run lint, clear, or toggle exclude libraries
* @param resource the resource to check or clear markers for, where
* null means all projects
*/
- private LintMenuAction(String text, ImageDescriptor descriptor, boolean clear,
+ private LintMenuAction(String text, ImageDescriptor descriptor, int action,
IResource resource) {
- super(text, descriptor);
- mClear = clear;
+ super(text, action == ACTION_TOGGLE_EXCLUDE ? AS_CHECK_BOX : AS_PUSH_BUTTON);
+ if (descriptor != null) {
+ setImageDescriptor(descriptor);
+ }
+ mAction = action;
mResource = resource;
}
@Override
public void run() {
+ if (mAction == ACTION_TOGGLE_EXCLUDE) {
+ AdtPrefs prefs = AdtPrefs.getPrefs();
+ prefs.setSkipLibrariesFromLint(!prefs.getSkipLibrariesFromLint());
+ return;
+ }
List<IResource> resources = new ArrayList<IResource>();
if (mResource == null) {
// All projects
@@ -198,9 +220,10 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
resources.add(mResource);
}
EclipseLintRunner.cancelCurrentJobs(false);
- if (mClear) {
+ if (mAction == ACTION_CLEAR) {
EclipseLintClient.clearMarkers(resources);
} else {
+ assert mAction == ACTION_RUN;
EclipseLintRunner.startLint(resources, null, null, false /*fatalOnly*/,
true /*show*/);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
index 1d743b8..627eeb8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetAttributeFix.java
@@ -15,22 +15,31 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_ALLOW_BACKUP;
import static com.android.SdkConstants.ATTR_BASELINE_ALIGNED;
import static com.android.SdkConstants.ATTR_CONTENT_DESCRIPTION;
+import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_INPUT_TYPE;
import static com.android.SdkConstants.ATTR_PERMISSION;
import static com.android.SdkConstants.ATTR_TRANSLATABLE;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
import static com.android.SdkConstants.VALUE_FALSE;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
import com.android.tools.lint.checks.AccessibilityDetector;
import com.android.tools.lint.checks.InefficientWeightDetector;
import com.android.tools.lint.checks.ManifestOrderDetector;
+import com.android.tools.lint.checks.MissingIdDetector;
import com.android.tools.lint.checks.SecurityDetector;
import com.android.tools.lint.checks.TextFieldDetector;
import com.android.tools.lint.checks.TranslationDetector;
import org.eclipse.core.resources.IMarker;
+import org.eclipse.ui.IEditorPart;
+import org.w3c.dom.Element;
/** Shared fix class for various builtin attributes */
final class SetAttributeFix extends SetPropertyFix {
@@ -52,6 +61,8 @@ final class SetAttributeFix extends SetPropertyFix {
return ATTR_TRANSLATABLE;
} else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) {
return ATTR_ALLOW_BACKUP;
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ return ATTR_ID;
} else {
assert false : mId;
return "";
@@ -81,6 +92,8 @@ final class SetAttributeFix extends SetPropertyFix {
return "Mark this as a non-translatable resource";
} else if (mId.equals(ManifestOrderDetector.ALLOW_BACKUP.getId())) {
return "Set the allowBackup attribute to true or false";
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ return "Set the ID attribute";
} else {
assert false : mId;
return "";
@@ -115,13 +128,24 @@ final class SetAttributeFix extends SetPropertyFix {
}
@Override
- protected String getProposal() {
+ protected String getProposal(Element element) {
if (mId.equals(InefficientWeightDetector.BASELINE_WEIGHTS.getId())) {
return VALUE_FALSE;
} else if (mId.equals(TranslationDetector.MISSING.getId())) {
return VALUE_FALSE;
+ } else if (mId.equals(TextFieldDetector.ISSUE.getId())) {
+ return element.getAttributeNS(ANDROID_URI, ATTR_INPUT_TYPE);
+ } else if (mId.equals(MissingIdDetector.ISSUE.getId())) {
+ IEditorPart editor = AdtUtils.getActiveEditor();
+ if (editor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor xmlEditor = (AndroidXmlEditor) editor;
+ return DescriptorsUtils.getFreeWidgetId(xmlEditor.getUiRootNode(),
+ "fragment"); //$NON-NLS-1$
+ } else {
+ return NEW_ID_PREFIX;
+ }
}
- return super.getProposal();
+ return super.getProposal(element);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
index ee049ca..a2b79c3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/SetPropertyFix.java
@@ -51,7 +51,7 @@ abstract class SetPropertyFix extends DocumentFix {
/** Whether it's in the android: namespace */
protected abstract boolean isAndroidAttribute();
- protected String getProposal() {
+ protected String getProposal(Element element) {
return invokeCodeCompletion() ? "" : "TODO"; //$NON-NLS-1$
}
@@ -71,7 +71,7 @@ abstract class SetPropertyFix extends DocumentFix {
if (node instanceof Element) {
Element element = (Element) node;
- String proposal = getProposal();
+ String proposal = getProposal(element);
String localAttribute = getAttribute();
String prefix = null;
if (isAndroidAttribute()) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/TypoFix.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/TypoFix.java
index 4358410..8a83364 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/TypoFix.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/TypoFix.java
@@ -105,7 +105,7 @@ final class TypoFix extends DocumentFix {
String message = mMarker.getAttribute(IMarker.MESSAGE, "");
String typo = TypoDetector.getTypo(message);
List<String> replacements = TypoDetector.getSuggestions(message);
- if (!replacements.isEmpty() && typo != null) {
+ if (replacements != null && !replacements.isEmpty() && typo != null) {
List<LintFix> allFixes = new ArrayList<LintFix>(replacements.size());
for (String replacement : replacements) {
TypoFix fix = new TypoFix(mId, mMarker);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
index 8526ad9..aed4bd4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
@@ -17,9 +17,11 @@
package com.android.ide.eclipse.adt.internal.preferences;
+import com.android.annotations.NonNull;
+import com.android.ide.common.xml.XmlAttributeSortOrder;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewMode;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
@@ -30,6 +32,7 @@ import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.PropertyChangeEvent;
import java.io.File;
+import java.util.Locale;
public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_SDK_DIR = AdtPlugin.PLUGIN_ID + ".sdk"; //$NON-NLS-1$
@@ -69,6 +72,10 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
public final static String PREFS_LINT_SEVERITIES = AdtPlugin.PLUGIN_ID + ".lintSeverities"; //$NON-NLS-1$
public final static String PREFS_FIX_LEGACY_EDITORS = AdtPlugin.PLUGIN_ID + ".fixLegacyEditors"; //$NON-NLS-1$
public final static String PREFS_SHARED_LAYOUT_EDITOR = AdtPlugin.PLUGIN_ID + ".sharedLayoutEditor"; //$NON-NLS-1$
+ public final static String PREFS_PREVIEWS = AdtPlugin.PLUGIN_ID + ".previews"; //$NON-NLS-1$
+ public final static String PREFS_SKIP_LINT_LIBS = AdtPlugin.PLUGIN_ID + ".skipLintLibs"; //$NON-NLS-1$
+ public final static String PREFS_AUTO_PICK_TARGET = AdtPlugin.PLUGIN_ID + ".autoPickTarget"; //$NON-NLS-1$
+ public final static String PREFS_REFACTOR_IDS = AdtPlugin.PLUGIN_ID + ".refactorIds"; //$NON-NLS-1$
/** singleton instance */
private final static AdtPrefs sThis = new AdtPrefs();
@@ -97,9 +104,12 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
private boolean mFormatOnSave;
private boolean mLintOnSave;
private boolean mLintOnExport;
- private AttributeSortOrder mAttributeSort;
+ private XmlAttributeSortOrder mAttributeSort;
private boolean mSharedLayoutEditor;
+ private boolean mAutoPickTarget;
+ private RenderPreviewMode mPreviewMode = RenderPreviewMode.NONE;
private int mPreferXmlEditor;
+ private boolean mSkipLibrariesFromLint;
public static enum BuildVerbosity {
/** Build verbosity "Always". Those messages are always displayed, even in silent mode */
@@ -226,11 +236,11 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
if (property == null || PREFS_ATTRIBUTE_SORT.equals(property)) {
String order = mStore.getString(PREFS_ATTRIBUTE_SORT);
- mAttributeSort = AttributeSortOrder.LOGICAL;
- if (AttributeSortOrder.ALPHABETICAL.key.equals(order)) {
- mAttributeSort = AttributeSortOrder.ALPHABETICAL;
- } else if (AttributeSortOrder.NO_SORTING.key.equals(order)) {
- mAttributeSort = AttributeSortOrder.NO_SORTING;
+ mAttributeSort = XmlAttributeSortOrder.LOGICAL;
+ if (XmlAttributeSortOrder.ALPHABETICAL.key.equals(order)) {
+ mAttributeSort = XmlAttributeSortOrder.ALPHABETICAL;
+ } else if (XmlAttributeSortOrder.NO_SORTING.key.equals(order)) {
+ mAttributeSort = XmlAttributeSortOrder.NO_SORTING;
}
}
@@ -254,6 +264,25 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
mSharedLayoutEditor = mStore.getBoolean(PREFS_SHARED_LAYOUT_EDITOR);
}
+ if (property == null || PREFS_AUTO_PICK_TARGET.equals(property)) {
+ mAutoPickTarget = mStore.getBoolean(PREFS_AUTO_PICK_TARGET);
+ }
+
+ if (property == null || PREFS_PREVIEWS.equals(property)) {
+ mPreviewMode = RenderPreviewMode.NONE;
+ String previewMode = mStore.getString(PREFS_PREVIEWS);
+ if (previewMode != null && !previewMode.isEmpty()) {
+ try {
+ mPreviewMode = RenderPreviewMode.valueOf(previewMode.toUpperCase(Locale.US));
+ } catch (IllegalArgumentException iae) {
+ // Ignore: Leave it as RenderPreviewMode.NONE
+ }
+ }
+ }
+
+ if (property == null || PREFS_SKIP_LINT_LIBS.equals(property)) {
+ mSkipLibrariesFromLint = mStore.getBoolean(PREFS_SKIP_LINT_LIBS);
+ }
}
/**
@@ -330,13 +359,13 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
/**
* Returns the sort order to be applied to the attributes (one of which can
- * be {@link AttributeSortOrder#NO_SORTING}).
+ * be {@link com.android.ide.common.xml.XmlAttributeSortOrder#NO_SORTING}).
*
* @return the sort order to apply to the attributes
*/
- public AttributeSortOrder getAttributeSort() {
+ public XmlAttributeSortOrder getAttributeSort() {
if (mAttributeSort == null) {
- return AttributeSortOrder.LOGICAL;
+ return XmlAttributeSortOrder.LOGICAL;
}
return mAttributeSort;
}
@@ -344,7 +373,7 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
/**
* Returns whether a space should be inserted before the closing {@code >}
* character in open tags and before the closing {@code />} characters in
- * empty tag. Note that the {@link XmlFormatStyle#RESOURCE} style overrides
+ * empty tag. Note that the {@link com.android.ide.common.xml.XmlFormatStyle#RESOURCE} style overrides
* this setting to make it more compact for the {@code <item>} elements.
*
* @return true if an empty space should be inserted before {@code >} or
@@ -485,13 +514,15 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
store.setDefault(PREFS_SPACE_BEFORE_CLOSE, true);
store.setDefault(PREFS_LINT_ON_SAVE, true);
store.setDefault(PREFS_LINT_ON_EXPORT, true);
+ store.setDefault(PREFS_AUTO_PICK_TARGET, true);
// Defaults already handled; no need to write into map:
- //store.setDefault(PREFS_ATTRIBUTE_SORT, AttributeSortOrder.LOGICAL.key);
+ //store.setDefault(PREFS_ATTRIBUTE_SORT, XmlAttributeSortOrder.LOGICAL.key);
//store.setDefault(PREFS_USE_ECLIPSE_INDENT, false);
//store.setDefault(PREVS_REMOVE_EMPTY_LINES, false);
//store.setDefault(PREFS_FORMAT_ON_SAVE, false);
//store.setDefault(PREFS_SHARED_LAYOUT_EDITOR, false);
+ //store.setDefault(PREFS_SKIP_LINT_LIBS, false);
try {
store.setDefault(PREFS_DEFAULT_DEBUG_KEYSTORE,
@@ -538,4 +569,79 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
store.setValue(PREFS_PREFER_XML, xml);
}
}
+
+ /**
+ * Gets the {@link RenderPreviewMode}
+ *
+ * @return the preview mode
+ */
+ @NonNull
+ public RenderPreviewMode getRenderPreviewMode() {
+ return mPreviewMode;
+ }
+
+ /**
+ * Sets the {@link RenderPreviewMode}
+ *
+ * @param previewMode the preview mode
+ */
+ public void setPreviewMode(@NonNull RenderPreviewMode previewMode) {
+ mPreviewMode = previewMode;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ if (previewMode != RenderPreviewMode.NONE) {
+ store.setValue(PREFS_PREVIEWS, previewMode.name().toLowerCase(Locale.US));
+ } else {
+ store.setToDefault(PREFS_PREVIEWS);
+ }
+ }
+
+ /**
+ * Sets whether auto-pick render target mode is enabled.
+ *
+ * @return whether the layout editor should automatically pick the best render target
+ */
+ public boolean isAutoPickRenderTarget() {
+ return mAutoPickTarget;
+ }
+
+ /**
+ * Sets whether auto-pick render target mode is enabled.
+ *
+ * @param autoPick if true, auto pick the best render target in the layout editor
+ */
+ public void setAutoPickRenderTarget(boolean autoPick) {
+ mAutoPickTarget = autoPick;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ if (autoPick) {
+ store.setToDefault(PREFS_AUTO_PICK_TARGET);
+ } else {
+ store.setValue(PREFS_AUTO_PICK_TARGET, autoPick);
+ }
+ }
+
+ /**
+ * Sets whether libraries should be excluded when running lint on a project
+ *
+ * @param exclude if true, exclude library projects
+ */
+ public void setSkipLibrariesFromLint(boolean exclude) {
+ if (exclude != mSkipLibrariesFromLint) {
+ mSkipLibrariesFromLint = exclude;
+ IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore();
+ if (exclude) {
+ store.setValue(PREFS_SKIP_LINT_LIBS, true);
+ } else {
+ store.setToDefault(PREFS_SKIP_LINT_LIBS);
+ }
+ }
+ }
+
+ /**
+ * Returns whether libraries should be excluded when running lint on a project
+ *
+ * @return if true, exclude library projects
+ */
+ public boolean getSkipLibrariesFromLint() {
+ return mSkipLibrariesFromLint;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java
deleted file mode 100644
index b743014..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AttributeSortOrder.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.ide.eclipse.adt.internal.preferences;
-
-import static com.android.SdkConstants.XMLNS;
-
-import com.android.ide.eclipse.adt.internal.editors.uimodel.UiAttributeNode;
-
-import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
-import org.w3c.dom.Attr;
-
-import java.util.Comparator;
-
-/** Order to use when sorting attributes */
-@SuppressWarnings("restriction") // IndexedRegion
-public enum AttributeSortOrder {
- NO_SORTING("none"), //$NON-NLS-1$
- ALPHABETICAL("alpha"), //$NON-NLS-1$
- LOGICAL("logical"); //$NON-NLS-1$
-
- AttributeSortOrder(String key) {
- this.key = key;
- }
-
- public final String key;
-
- /**
- * @return a comparator for use by this attribute sort order
- */
- public Comparator<Attr> getAttributeComparator() {
- switch (this) {
- case ALPHABETICAL:
- return ALPHABETICAL_COMPARATOR;
- case NO_SORTING:
- return EXISTING_ORDER_COMPARATOR;
- case LOGICAL:
- default:
- return SORTED_ORDER_COMPARATOR;
- }
- }
-
- /** Comparator which can be used to sort attributes in the coding style priority order */
- private static final Comparator<Attr> SORTED_ORDER_COMPARATOR = new Comparator<Attr>() {
- @Override
- public int compare(Attr attr1, Attr attr2) {
- // Namespace declarations should always go first
- if (XMLNS.equals(attr1.getPrefix())) {
- if (XMLNS.equals(attr2.getPrefix())) {
- return 0;
- }
- return -1;
- } else if (XMLNS.equals(attr2.getPrefix())) {
- return 1;
- }
-
- // Sort by preferred attribute order
- return UiAttributeNode.compareAttributes(
- attr1.getPrefix(), attr1.getLocalName(),
- attr2.getPrefix(), attr2.getLocalName());
- }
- };
-
- /**
- * Comparator which can be used to "sort" attributes into their existing source order
- * (which is not the same as the node map iteration order in the DOM model)
- */
- private static final Comparator<Attr> EXISTING_ORDER_COMPARATOR = new Comparator<Attr>() {
- @Override
- public int compare(Attr attr1, Attr attr2) {
- IndexedRegion region1 = (IndexedRegion) attr1;
- IndexedRegion region2 = (IndexedRegion) attr2;
-
- return region1.getStartOffset() - region2.getStartOffset();
- }
- };
-
- /**
- * Comparator which can be used to sort attributes into alphabetical order (but xmlns
- * is always first)
- */
- private static final Comparator<Attr> ALPHABETICAL_COMPARATOR = new Comparator<Attr>() {
- @Override
- public int compare(Attr attr1, Attr attr2) {
- // Namespace declarations should always go first
- if (XMLNS.equals(attr1.getPrefix())) {
- if (XMLNS.equals(attr2.getPrefix())) {
- return 0;
- }
- return -1;
- } else if (XMLNS.equals(attr2.getPrefix())) {
- return 1;
- }
-
- // Sort by name rather than localname to ensure we sort by namespaces first,
- // then by names.
- return attr1.getName().compareTo(attr2.getName());
- }
- };
-} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/BuildPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/BuildPreferencePage.java
index d5aa30a..f5f4633 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/BuildPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/BuildPreferencePage.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.preferences;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
+import com.android.ide.eclipse.adt.internal.utils.FingerprintUtils;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.internal.build.DebugKeyProvider;
import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
@@ -25,6 +26,7 @@ import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FileFieldEditor;
+import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.RadioGroupFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.swt.widgets.Composite;
@@ -36,6 +38,7 @@ import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
+import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Date;
@@ -46,9 +49,22 @@ import java.util.Date;
public class BuildPreferencePage extends FieldEditorPreferencePage implements
IWorkbenchPreferencePage {
+ private IPreferenceStore mPrefStore = null;
+
+ // default key store
+ private ReadOnlyFieldEditor mDefaultKeyStore = null;
+ private LabelField mDefaultFingerprintMd5 = null;
+ private LabelField mDefaultFingerprintSha1 = null;
+
+ // custom key store
+ private KeystoreFieldEditor mCustomKeyStore = null;
+ private LabelField mCustomFingerprintMd5 = null;
+ private LabelField mCustomFingerprintSha1 = null;
+
public BuildPreferencePage() {
super(GRID);
- setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
+ mPrefStore = AdtPlugin.getDefault().getPreferenceStore();
+ setPreferenceStore(mPrefStore);
setDescription(Messages.BuildPreferencePage_Title);
}
@@ -76,12 +92,90 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
getFieldEditorParent(), true);
addField(rgfe);
- addField(new ReadOnlyFieldEditor(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE,
- Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent()));
+ // default debug keystore fingerprints
+ Fingerprints defaultFingerprints = getFingerprints(
+ mPrefStore.getString(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE));
+
+ // default debug key store fields
+ mDefaultKeyStore = new ReadOnlyFieldEditor(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE,
+ Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent());
+ mDefaultFingerprintMd5 = new LabelField(
+ Messages.BuildPreferencePage_Default_Certificate_Fingerprint_MD5,
+ defaultFingerprints != null ? defaultFingerprints.md5 : "",
+ getFieldEditorParent());
+ mDefaultFingerprintSha1 = new LabelField(
+ Messages.BuildPreferencePage_Default_Certificate_Fingerprint_SHA1,
+ defaultFingerprints != null ? defaultFingerprints.sha1 : "",
+ getFieldEditorParent());
+
+ addField(mDefaultKeyStore);
+ addField(mDefaultFingerprintMd5);
+ addField(mDefaultFingerprintSha1);
+
+ // custom debug keystore fingerprints
+ Fingerprints customFingerprints = null;
+
+ String customDebugKeystorePath = mPrefStore.getString(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE);
+ if (new File(customDebugKeystorePath).isFile()) {
+ customFingerprints = getFingerprints(customDebugKeystorePath);
+ } else {
+ // file does not exist.
+ setErrorMessage("Not a valid keystore path.");
+ }
- addField(new KeystoreFieldEditor(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE,
- Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent()));
+ // custom debug key store fields
+ mCustomKeyStore = new KeystoreFieldEditor(AdtPrefs.PREFS_CUSTOM_DEBUG_KEYSTORE,
+ Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent());
+ mCustomFingerprintMd5 = new LabelField(
+ Messages.BuildPreferencePage_Default_Certificate_Fingerprint_MD5,
+ customFingerprints != null ? customFingerprints.md5 : "",
+ getFieldEditorParent());
+ mCustomFingerprintSha1 = new LabelField(
+ Messages.BuildPreferencePage_Default_Certificate_Fingerprint_SHA1,
+ customFingerprints != null ? customFingerprints.sha1 : "",
+ getFieldEditorParent());
+
+ // set fingerprint fields
+ mCustomKeyStore.setFingerprintMd5Field(mCustomFingerprintMd5);
+ mCustomKeyStore.setFingerprintSha1Field(mCustomFingerprintSha1);
+
+ addField(mCustomKeyStore);
+ addField(mCustomFingerprintMd5);
+ addField(mCustomFingerprintSha1);
+ }
+ /**
+ * MD5 & SHA1 fingerprints.
+ */
+ private static class Fingerprints {
+ final String md5;
+ final String sha1;
+
+ Fingerprints(String md5Val, String sha1Val) {
+ md5 = md5Val;
+ sha1 = sha1Val;
+ }
+ }
+
+ private Fingerprints getFingerprints(String keystorePath) {
+ // attempt to load the debug key.
+ try {
+ DebugKeyProvider keyProvider = new DebugKeyProvider(keystorePath,
+ null /* storeType */, null /* key gen output */);
+
+ return new Fingerprints(
+ FingerprintUtils.getFingerprint(keyProvider.getCertificate(), "MD5"),
+ FingerprintUtils.getFingerprint(keyProvider.getCertificate(), "SHA1"));
+ } catch (GeneralSecurityException e) {
+ setErrorMessage(e.getMessage());
+ } catch (IOException e) {
+ setErrorMessage(e.getMessage());
+ } catch (KeytoolException e) {
+ setErrorMessage(e.getMessage());
+ } catch (AndroidLocationException e) {
+ setErrorMessage(e.getMessage());
+ }
+ return null;
}
/*
@@ -93,6 +187,27 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
public void init(IWorkbench workbench) {
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.jface.preference.FieldEditorPreferencePage#performDefaults
+ * (org.eclipse.jface.preference.PreferencePage#performDefaults)
+ */
+ @Override
+ protected void performDefaults() {
+ super.performDefaults();
+
+ // restore the default key store fingerprints
+ Fingerprints defaultFingerprints = getFingerprints(mPrefStore
+ .getString(AdtPrefs.PREFS_DEFAULT_DEBUG_KEYSTORE));
+ mDefaultFingerprintMd5.setStringValue(defaultFingerprints.md5);
+ mDefaultFingerprintSha1.setStringValue(defaultFingerprints.sha1);
+
+ // set custom fingerprint fields to blank
+ mCustomFingerprintMd5.setStringValue("");
+ mCustomFingerprintSha1.setStringValue("");
+ }
+
/**
* A read-only string field editor.
*/
@@ -112,9 +227,50 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
}
/**
+ * A read-only string field.
+ */
+ private static class LabelField extends StringFieldEditor {
+ private String text;
+
+ public LabelField(String labelText, String value, Composite parent) {
+ super("", labelText, parent);
+ text = value;
+ }
+
+ @Override
+ protected void createControl(Composite parent) {
+ super.createControl(parent);
+
+ Text control = getTextControl();
+ control.setEditable(false);
+ }
+
+ @Override
+ protected void doLoad() {
+ setStringValue(text);
+ }
+
+ @Override
+ protected void doStore() {
+ // Do nothing
+ }
+ }
+
+ /**
* Custom {@link FileFieldEditor} that checks that the keystore is valid.
*/
private static class KeystoreFieldEditor extends FileFieldEditor {
+ private StringFieldEditor fingerprintMd5 = null;
+ private StringFieldEditor fingerprintSha1 = null;
+
+ public void setFingerprintMd5Field(StringFieldEditor field) {
+ fingerprintMd5 = field;
+ }
+
+ public void setFingerprintSha1Field(StringFieldEditor field) {
+ fingerprintSha1 = field;
+ }
+
public KeystoreFieldEditor(String name, String label, Composite parent) {
super(name, label, parent);
setValidateStrategy(VALIDATE_ON_KEY_STROKE);
@@ -125,6 +281,14 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
String fileName = getTextControl().getText();
fileName = fileName.trim();
+ if (fingerprintMd5 != null) {
+ fingerprintMd5.setStringValue("");
+ }
+
+ if (fingerprintSha1 != null) {
+ fingerprintSha1.setStringValue("");
+ }
+
// empty values are considered ok.
if (fileName.length() > 0) {
File file = new File(fileName);
@@ -141,6 +305,16 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements
return false;
}
+ if (fingerprintMd5 != null) {
+ fingerprintMd5.setStringValue(
+ FingerprintUtils.getFingerprint(certificate, "MD5"));
+ }
+
+ if (fingerprintSha1 != null) {
+ fingerprintSha1.setStringValue(
+ FingerprintUtils.getFingerprint(certificate, "SHA1"));
+ }
+
Date today = new Date();
if (certificate.getNotAfter().compareTo(today) < 0) {
showErrorMessage("Certificate is expired!");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
index 0fcbaa0..d33b49f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
@@ -16,9 +16,9 @@
package com.android.ide.eclipse.adt.internal.preferences;
-import static com.android.ide.eclipse.adt.internal.preferences.AttributeSortOrder.ALPHABETICAL;
-import static com.android.ide.eclipse.adt.internal.preferences.AttributeSortOrder.LOGICAL;
-import static com.android.ide.eclipse.adt.internal.preferences.AttributeSortOrder.NO_SORTING;
+import static com.android.ide.common.xml.XmlAttributeSortOrder.ALPHABETICAL;
+import static com.android.ide.common.xml.XmlAttributeSortOrder.LOGICAL;
+import static com.android.ide.common.xml.XmlAttributeSortOrder.NO_SORTING;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
index 0bc2bfd..02af2fd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -15,6 +15,9 @@
*/
package com.android.ide.eclipse.adt.internal.preferences;
+import static com.android.tools.lint.detector.api.Issue.OutputFormat.RAW;
+import static com.android.tools.lint.detector.api.Issue.OutputFormat.TEXT;
+
import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
@@ -448,8 +451,8 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
Object data = item != null ? item.getData() : null;
if (data instanceof Issue) {
Issue issue = (Issue) data;
- String summary = issue.getDescription();
- String explanation = issue.getExplanation();
+ String summary = issue.getDescription(Issue.OutputFormat.TEXT);
+ String explanation = issue.getExplanation(Issue.OutputFormat.TEXT);
StringBuilder sb = new StringBuilder(summary.length() + explanation.length() + 20);
sb.append(summary);
@@ -568,7 +571,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
|| issue.getCategory().getName().toLowerCase(Locale.US).startsWith(filter)
|| issue.getCategory().getFullName().toLowerCase(Locale.US).startsWith(filter)
|| issue.getId().toLowerCase(Locale.US).contains(filter)
- || issue.getDescription().toLowerCase(Locale.US).contains(filter);
+ || issue.getDescription(RAW).toLowerCase(Locale.US).contains(filter);
}
private class ContentProvider extends TreeNodeContentProvider {
@@ -703,7 +706,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
if (columnIndex == 0) {
return ((Category) element).getFullName();
} else {
- return ((Category) element).getExplanation();
+ return null;
}
}
@@ -712,7 +715,7 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
case 0:
return issue.getId();
case 1:
- return issue.getDescription();
+ return issue.getDescription(TEXT);
}
return null;
@@ -745,4 +748,4 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
return null;
}
}
-} \ No newline at end of file
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/Messages.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/Messages.java
index 1474102..70b9751 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/Messages.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/Messages.java
@@ -18,8 +18,16 @@ public class Messages extends NLS {
public static String BuildPreferencePage_Custom_Keystore;
+ public static String BuildPreferencePage_Custom_Certificate_Fingerprint_MD5;
+
+ public static String BuildPreferencePage_Custom_Certificate_Fingerprint_SHA1;
+
public static String BuildPreferencePage_Default_KeyStore;
+ public static String BuildPreferencePage_Default_Certificate_Fingerprint_MD5;
+
+ public static String BuildPreferencePage_Default_Certificate_Fingerprint_SHA1;
+
public static String BuildPreferencePage_Normal;
public static String BuildPreferencePage_Silent;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/messages.properties
index 865ac19..61894fd 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/messages.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/messages.properties
@@ -5,7 +5,11 @@ BuildPreferencePage_Silent=Silent
BuildPreferencePage_Normal=Normal
BuildPreferencePage_Verbose=Verbose
BuildPreferencePage_Default_KeyStore=Default debug keystore:
+BuildPreferencePage_Default_Certificate_Fingerprint_MD5=MD5 fingerprint:
+BuildPreferencePage_Default_Certificate_Fingerprint_SHA1=SHA1 fingerprint:
BuildPreferencePage_Custom_Keystore=Custom debug keystore:
+BuildPreferencePage_Custom_Certificate_Fingerprint_MD5=MD5 fingerprint:
+BuildPreferencePage_Custom_Certificate_Fingerprint_SHA1=SHA1 fingerprint:
LaunchPreferencePage_Title=Launch Settings:
LaunchPreferencePage_Default_Emu_Options=Default emulator options:
LaunchPreferencePage_Default_HOME_Package=Default HOME package:
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java
index bb1e40a..c526edf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/AndroidClasspathContainerInitializer.java
@@ -25,6 +25,8 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.IAndroidTarget.IOptionalLibrary;
+import com.google.common.collect.Maps;
+import com.google.common.io.Closeables;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
@@ -51,8 +53,10 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Map;
import java.util.regex.Pattern;
/**
@@ -526,22 +530,36 @@ public class AndroidClasspathContainerInitializer extends BaseClasspathContainer
return androidSourceProperty;
}
+ /**
+ * Cache results for testURL: Some are expensive to compute, and this is
+ * called repeatedly (perhaps for each open project)
+ */
+ private static final Map<String, Boolean> sRecentUrlValidCache =
+ Maps.newHashMapWithExpectedSize(4);
+
+ @SuppressWarnings("resource") // Eclipse does not handle Closeables#closeQuietly
private static boolean testURL(String androidApiURL) {
+ Boolean cached = sRecentUrlValidCache.get(androidApiURL);
+ if (cached != null) {
+ return cached.booleanValue();
+ }
boolean valid = false;
InputStream is = null;
try {
URL testURL = new URL(androidApiURL);
- is = testURL.openStream();
+ URLConnection connection = testURL.openConnection();
+ // Only try for 5 seconds (though some implementations ignore this flag)
+ connection.setConnectTimeout(5000);
+ connection.setReadTimeout(5000);
+ is = connection.getInputStream();
valid = true;
} catch (Exception ignore) {
} finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException ignore) {
- }
- }
+ Closeables.closeQuietly(is);
}
+
+ sRecentUrlValidCache.put(androidApiURL, valid);
+
return valid;
}
@@ -568,7 +586,8 @@ public class AndroidClasspathContainerInitializer extends BaseClasspathContainer
// project that have been resolved before the sdk was loaded
// will have a ProjectState where the IAndroidTarget is null
// so we load the target now that the SDK is loaded.
- IAndroidTarget target = currentSdk.loadTarget(Sdk.getProjectState(iProject));
+ IAndroidTarget target = currentSdk.loadTargetAndBuildTools(
+ Sdk.getProjectState(iProject));
if (target == null) {
// this is really not supposed to happen. This would mean there are cached paths,
// but project.properties was deleted. Keep the project in the list to force
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
index fb8742d..52870a4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/ExportHelper.java
@@ -31,6 +31,7 @@ import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFileWrapper;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.build.ApkCreationException;
import com.android.sdklib.build.DuplicateFileException;
import com.android.sdklib.internal.project.ProjectProperties;
@@ -123,13 +124,33 @@ public final class ExportHelper {
}
});
- BuildHelper helper = new BuildHelper(project,
+ ProjectState projectState = Sdk.getProjectState(project);
+
+ // get the jumbo mode option
+ String forceJumboStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_FORCEJUMBO);
+ Boolean jumbo = Boolean.valueOf(forceJumboStr);
+
+ String dexMergerStr = projectState.getProperty(AdtConstants.DEX_OPTIONS_DISABLE_MERGER);
+ Boolean dexMerger = Boolean.valueOf(dexMergerStr);
+
+ BuildToolInfo buildToolInfo = projectState.getBuildToolInfo();
+ if (buildToolInfo == null) {
+ buildToolInfo = Sdk.getCurrent().getLatestBuildTool();
+ }
+
+ if (buildToolInfo == null) {
+ throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "No Build Tools installed in the SDK."));
+ }
+
+ BuildHelper helper = new BuildHelper(project, buildToolInfo,
fakeStream, fakeStream,
+ jumbo.booleanValue(),
+ dexMerger.booleanValue(),
debugMode, false /*verbose*/,
null /*resourceMarker*/);
// get the list of library projects
- ProjectState projectState = Sdk.getProjectState(project);
List<IProject> libProjects = projectState.getFullLibraryProjects();
// Step 1. Package the resources.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
index aa0e736..5b0d185 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/LibraryClasspathContainerInitializer.java
@@ -384,7 +384,10 @@ public class LibraryClasspathContainerInitializer extends BaseClasspathContainer
for (IResource member : members) {
if (member.getType() == IResource.FILE &&
SdkConstants.EXT_JAR.equalsIgnoreCase(member.getFileExtension())) {
- jarFiles.add(member.getLocation().toFile());
+ IPath location = member.getLocation();
+ if (location != null) {
+ jarFiles.add(location.toFile());
+ }
}
}
} catch (CoreException e) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java
deleted file mode 100644
index 3949789..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidDocumentChange.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import com.android.SdkConstants;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.project.XmlErrorHandler.BasicXmlErrorListener;
-import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.DocumentChange;
-import org.eclipse.ltk.core.refactoring.RefactoringStatus;
-import org.eclipse.text.edits.ReplaceEdit;
-import org.eclipse.text.edits.TextEdit;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.Map;
-
-/**
- * A text change that operates on android manifest using WTP SSE model.
- * It is base class for Rename Package and Rename Type changes
-*/
-@SuppressWarnings("restriction")
-public class AndroidDocumentChange extends DocumentChange {
-
- protected IFile mAndroidManifest;
-
- protected String mAppPackage;
-
- protected IStructuredModel mModel;
-
- protected IDocument mDocument;
-
- protected Map<String, String> mElements;
-
- protected String mNewName;
-
- protected String mOldName;
-
- protected ITextFileBufferManager mManager;
-
- /**
- * Creates a new <code>AndroidDocumentChange</code> for the given
- * {@link IDocument}.
- *
- * @param document the document this change is working on
- */
- public AndroidDocumentChange(IDocument document) {
- super(SdkConstants.FN_ANDROID_MANIFEST_XML, document);
- }
-
- @Override
- public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- RefactoringStatus status = super.isValid(pm);
- if (mModel == null) {
- status.addFatalError("File " + SdkConstants.FN_ANDROID_MANIFEST_XML + " is invalid.");
- } else {
- mAppPackage = getAppPackage();
- if (mAppPackage == null) {
- status.addFatalError("Invalid package in the "
- + SdkConstants.FN_ANDROID_MANIFEST_XML + " file.");
- }
- }
- BasicXmlErrorListener errorListener = new BasicXmlErrorListener();
- AndroidManifestHelper.parseForError(mAndroidManifest, errorListener);
-
- if (errorListener.mHasXmlError == true) {
- status.addFatalError("File " + SdkConstants.FN_ANDROID_MANIFEST_XML + " is invalid.");
- }
- return status;
- }
-
- /**
- * Finds the attribute with values oldName
- *
- * @param xmlDoc the document
- * @param element the element
- * @param attributeName the attribute
- * @param oldName the value
- *
- * @return the attribute
- */
- private Attr findAttribute(IDOMDocument xmlDoc, String element, String attributeName,
- String oldName) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName);
- if (attribute != null) {
- String value = attribute.getValue();
- if (value != null) {
- String fullName = AndroidManifest.combinePackageAndClassName(
- getAppPackage(), value);
- if (fullName != null && fullName.equals(oldName)) {
- return attribute;
- }
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the attribute value
- *
- * @param xmlDoc the document
- * @param element the element
- * @param attributeName the attribute
- *
- * @return the attribute value
- */
- protected String getElementAttribute(IDOMDocument xmlDoc, String element,
- String attributeName, boolean useNamespace) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute;
- if (useNamespace) {
- attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName);
- } else {
- attribute = (Attr) attributes.getNamedItem(attributeName);
- }
- if (attribute != null) {
- return attribute.getValue();
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the SSE model for a document
- *
- * @param document the document
- * @return the model
- *
- */
- protected IStructuredModel getModel(IDocument document) {
- if (mModel != null) {
- return mModel;
- }
- IStructuredModel model;
- model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
- if (model == null) {
- if (document instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) document;
- model = StructuredModelManager.getModelManager()
- .getModelForRead(structuredDocument);
- }
- }
- return model;
- }
-
- @Override
- public void setTextType(String type) {
- super.setTextType(mAndroidManifest.getFileExtension());
- }
-
- /**
- * Returns the SSE DOM document
- *
- * @return the attribute value
- */
- protected IDOMDocument getDOMDocument() {
- IDOMModel xmlModel = (IDOMModel) mModel;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- return xmlDoc;
- }
-
- /**
- * Returns the application package
- *
- * @return the package name
- */
- protected String getAppPackage() {
- if (mAppPackage == null) {
- IDOMDocument xmlDoc = getDOMDocument();
- mAppPackage = getElementAttribute(xmlDoc, AndroidManifest.NODE_MANIFEST,
- AndroidManifest.ATTRIBUTE_PACKAGE, false);
- }
- return mAppPackage;
- }
-
- /**
- * Returns the text change that set new value of attribute
- *
- * @param attribute the attribute
- * @param newValue the new value
- *
- * @return the text change
- */
- protected TextEdit createTextEdit(Attr attribute, String newValue) {
- if (attribute == null)
- return null;
-
- if (attribute instanceof IDOMAttr) {
- IDOMAttr domAttr = (IDOMAttr) attribute;
- String region = domAttr.getValueRegionText();
- int offset = domAttr.getValueRegionStartOffset();
- if (region != null && region.length() >= 2) {
- return new ReplaceEdit(offset + 1, region.length() - 2, newValue);
- }
- }
- return null;
- }
-
- /**
- * Returns the text change that change the value of attribute from oldValue to newValue
- * and combine package
- *
- * @param elementName the element name
- * @param attributeName the attribute name
- * @param oldValue the old value
- * @param newValue the new value
- *
- * @return the text change
- */
- protected TextEdit createTextEdit(String elementName, String attributeName, String oldValue,
- String newValue) {
- return createTextEdit(elementName, attributeName, oldValue, newValue, true);
- }
-
- /**
- * Returns the text change that change the value of attribute from oldValue to newValue
- *
- * @param elementName the element name
- * @param attributeName the attribute name
- * @param oldName the old value
- * @param newName the new value
- * @param combinePackage combine package ?
- *
- * @return the text change
- */
- protected TextEdit createTextEdit(String elementName, String attributeName, String oldName,
- String newName, boolean combinePackage) {
- IDOMDocument xmlDoc = getDOMDocument();
- String name = null;
- Attr attr = findAttribute(xmlDoc, elementName, attributeName, oldName);
- if (attr != null) {
- name = attr.getValue();
- }
- if (name != null) {
- String newValue;
- if (combinePackage) {
- newValue = AndroidManifest.extractActivityName(newName, getAppPackage());
- } else {
- newValue = newName;
- }
- if (newValue != null) {
- TextEdit edit = createTextEdit(attr, newValue);
- return edit;
- }
- }
- return null;
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChange.java
deleted file mode 100644
index 834a57c..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChange.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import com.android.SdkConstants;
-import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;
-
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.DocumentChange;
-import org.eclipse.ltk.core.refactoring.RefactoringStatus;
-import org.eclipse.text.edits.MultiTextEdit;
-import org.eclipse.text.edits.ReplaceEdit;
-import org.eclipse.text.edits.TextEdit;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMAttr;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-/**
- * A text change that operates on android layout using WTP SSE model.
- * It is base class for Rename Package and Rename Type changes
-*/
-@SuppressWarnings("restriction")
-public class AndroidLayoutChange extends DocumentChange {
-
- private IDocument mDocument;
-
- private ITextFileBufferManager mManager;
-
- private IFile mFile;
-
- private IStructuredModel mModel;
-
- private Set<AndroidLayoutChangeDescription> mChanges;
-
- /**
- * Creates a new <code>AndroidLayoutChange</code>
- *
- * @param file the layout file
- * @param document the document
- * @param manager the buffer manager
- * @param changes the list of changes
- */
- public AndroidLayoutChange(IFile file, IDocument document, ITextFileBufferManager manager,
- Set<AndroidLayoutChangeDescription> changes) {
- super("", document); //$NON-NLS-1$
- mFile = file;
- mDocument = document;
- mManager = manager;
- mChanges = changes;
- try {
- this.mModel = getModel(document);
- } catch (Exception ignore) {
- }
- if (mModel != null) {
- addEdits();
- }
- }
-
- @Override
- public String getName() {
- return mFile.getName();
- }
-
- @Override
- public RefactoringStatus isValid(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- RefactoringStatus status = super.isValid(pm);
- if (mModel == null) {
- status.addFatalError("Invalid the " + getName() + " file.");
- }
- return status;
- }
-
- @Override
- public void setTextType(String type) {
- super.setTextType(mFile.getFileExtension());
- }
-
- @Override
- public void dispose() {
- super.dispose();
- RefactoringUtil.fixModel(mModel, mDocument);
-
- if (mManager != null) {
- try {
- mManager.disconnect(mFile.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
- }
-
- // ----
-
- /**
- * Adds text edits for this change
- */
- private void addEdits() {
- MultiTextEdit multiEdit = new MultiTextEdit();
- for (AndroidLayoutChangeDescription change : mChanges) {
- if (!change.isStandalone()) {
- TextEdit edit = createTextEdit(SdkConstants.VIEW,
- SdkConstants.ATTR_CLASS,
- change.getClassName(),
- change.getNewName());
- if (edit != null) {
- multiEdit.addChild(edit);
- }
- } else {
- List<TextEdit> edits = createElementTextEdit(change.getClassName(),
- change.getNewName());
- for (TextEdit edit : edits) {
- multiEdit.addChild(edit);
- }
- }
- }
- setEdit(multiEdit);
- }
-
- /**
- * Returns the text changes which change class (custom layout viewer) in layout file
- *
- * @param className the class name
- * @param newName the new class name
- *
- * @return list of text changes
- */
- private List<TextEdit> createElementTextEdit(String className, String newName) {
- IDOMDocument xmlDoc = getDOMDocument();
- List<TextEdit> edits = new ArrayList<TextEdit>();
- NodeList nodes = xmlDoc.getElementsByTagName(className);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- if (node instanceof IDOMElement) {
- IDOMElement domNode = (IDOMElement) node;
- IStructuredDocumentRegion firstRegion = domNode.getFirstStructuredDocumentRegion();
- if (firstRegion != null) {
- int offset = firstRegion.getStartOffset();
- edits.add(new ReplaceEdit(offset + 1, className.length(), newName));
- }
- IStructuredDocumentRegion endRegion = domNode.getEndStructuredDocumentRegion();
- if (endRegion != null) {
- int offset = endRegion.getStartOffset();
- edits.add(new ReplaceEdit(offset + 2, className.length(), newName));
- }
- }
-
- }
- return edits;
- }
-
- /**
- * Returns the SSE DOM document
- *
- * @return the attribute value
- */
- private IDOMDocument getDOMDocument() {
- IDOMModel xmlModel = (IDOMModel) mModel;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- return xmlDoc;
- }
-
- /**
- * Returns the text change that set new value of attribute
- *
- * @param attribute the attribute
- * @param newValue the new value
- *
- * @return the text change
- */
- private TextEdit createTextEdit(Attr attribute, String newValue) {
- if (attribute == null)
- return null;
-
- if (attribute instanceof IDOMAttr) {
- IDOMAttr domAttr = (IDOMAttr) attribute;
- String region = domAttr.getValueRegionText();
- int offset = domAttr.getValueRegionStartOffset();
- if (region != null && region.length() >= 2) {
- return new ReplaceEdit(offset + 1, region.length() - 2, newValue);
- }
- }
- return null;
- }
-
-
- /**
- * Returns the text change that change the value of attribute from oldValue to newValue
- *
- * @param elementName the element name
- * @param argumentName the attribute name
- * @param oldName the old value
- * @param newName the new value
- *
- * @return the text change
- */
- private TextEdit createTextEdit(String elementName, String argumentName, String oldName,
- String newName) {
- IDOMDocument xmlDoc = getDOMDocument();
- String name = null;
- Attr attr = findAttribute(xmlDoc, elementName, argumentName, oldName);
- if (attr != null) {
- name = attr.getValue();
- }
- if (name != null && newName != null) {
- TextEdit edit = createTextEdit(attr, newName);
- return edit;
- }
- return null;
- }
-
- /**
- * Finds the attribute with values oldName
- *
- * @param xmlDoc the document
- * @param element the element
- * @param attributeName the attribute
- * @param oldValue the value
- *
- * @return the attribute
- */
- private Attr findAttribute(IDOMDocument xmlDoc, String element, String attributeName,
- String oldValue) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, attributeName);
- if (attribute != null) {
- String value = attribute.getValue();
- if (value != null && value.equals(oldValue)) {
- return attribute;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the SSE model for a document
- *
- * @param document the document
- * @return the model
- */
- private IStructuredModel getModel(IDocument document) {
-
- IModelManager manager = StructuredModelManager.getModelManager();
- IStructuredModel model = manager.getExistingModelForRead(document);
- if (model == null && document instanceof IStructuredDocument) {
- model = manager.getModelForRead((IStructuredDocument) document);
- }
-
- return model;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChangeDescription.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChangeDescription.java
deleted file mode 100644
index a12885d..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutChangeDescription.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-/**
- * This class describes the text changes of android layout files
- *
- */
-public class AndroidLayoutChangeDescription {
-
- private String mClassName;
-
- private String mNewName;
-
- private int mType;
-
- /**
- * the view layout
- */
- public static final int VIEW_TYPE = 0;
-
- /**
- * the standalone layout
- */
- public static final int STANDALONE_TYPE = 1;
-
- /**
- * Creates a new <code>AndroidDocumentChange</code>
- *
- * @param className the old layout class name
- * @param newName the new layout class name
- * @param type the layout type; valid value are VIEW_TYPE and STANDALONE_TYPE
- */
- public AndroidLayoutChangeDescription(String className, String newName, int type) {
- this.mClassName = className;
- this.mNewName = newName;
- this.mType = type;
- }
-
- /**
- * @return the old class name
- */
- public String getClassName() {
- return mClassName;
- }
-
- /**
- * @return the new class name
- */
- public String getNewName() {
- return mNewName;
- }
-
- /**
- * @return the layout type
- */
- public int getType() {
- return mType;
- }
-
- /**
- * @return true if the layout is standalone
- */
- public boolean isStandalone() {
- return mType == STANDALONE_TYPE;
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((mClassName == null) ? 0 : mClassName.hashCode());
- result = prime * result + ((mNewName == null) ? 0 : mNewName.hashCode());
- result = prime * result + mType;
- return result;
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- AndroidLayoutChangeDescription other = (AndroidLayoutChangeDescription) obj;
- if (mClassName == null) {
- if (other.mClassName != null)
- return false;
- } else if (!mClassName.equals(other.mClassName))
- return false;
- if (mNewName == null) {
- if (other.mNewName != null)
- return false;
- } else if (!mNewName.equals(other.mNewName))
- return false;
- if (mType != other.mType)
- return false;
- return true;
- }
-
- @Override
- public String toString() {
- return "AndroidLayoutChangeDescription [className=" + mClassName + ", newName=" + mNewName
- + ", type=" + mType + "]";
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutFileChanges.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutFileChanges.java
deleted file mode 100644
index 13f7d6e..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidLayoutFileChanges.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import org.eclipse.core.resources.IFile;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Set of layout files with required text changes
- *
- */
-public class AndroidLayoutFileChanges {
- private IFile mFile;
-
- private Set<AndroidLayoutChangeDescription> mChanges =
- new HashSet<AndroidLayoutChangeDescription>();
-
- /**
- * Creates a new <code>AndroidLayoutFileChanges</code>
- *
- * @param file the layout file
- */
- public AndroidLayoutFileChanges(IFile file) {
- this.mFile = file;
- }
-
- /**
- * Return the layout file
- *
- * @return the file
- */
- public IFile getFile() {
- return mFile;
- }
-
- /**
- * Return the text changes
- *
- * @return the set of changes
- */
- public Set<AndroidLayoutChangeDescription> getChanges() {
- return mChanges;
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidPackageRenameChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidPackageRenameChange.java
deleted file mode 100644
index 590629a..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidPackageRenameChange.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import com.android.ide.eclipse.adt.internal.refactoring.core.FixImportsJob;
-import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.Change;
-import org.eclipse.text.edits.MultiTextEdit;
-import org.eclipse.text.edits.TextEdit;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A text change that operates on android manifest when execute Java Rename package refactoring
-*/
-public class AndroidPackageRenameChange extends AndroidDocumentChange {
-
- private boolean mIsPackage;
-
- /**
- * Creates a new <code>AndroidPackageRenameChange</code>
- *
- * @param androidManifest the android manifest file
- * @param manager the text buffer manager
- * @param document the document
- * @param elements the elements
- * @param newName the new name
- * @param oldName the old name
- * @param isPackage is the application package
- */
- public AndroidPackageRenameChange(IFile androidManifest, ITextFileBufferManager manager,
- IDocument document, Map<String, String> elements, String newName, String oldName,
- boolean isPackage) {
- super(document);
- this.mDocument = document;
- this.mIsPackage = isPackage;
- this.mElements = elements;
- this.mNewName = newName;
- this.mOldName = oldName;
- this.mManager = manager;
- this.mAndroidManifest = androidManifest;
- try {
- this.mModel = getModel(document);
- } catch (Exception ignore) {
- }
- if (mModel != null) {
- this.mAppPackage = getAppPackage();
- addEdits();
- }
- }
-
- /**
- * Adds text edits for this change
- */
- private void addEdits() {
- MultiTextEdit multiEdit = new MultiTextEdit();
-
- if (mIsPackage) {
- TextEdit edit = createTextEdit(AndroidManifest.NODE_MANIFEST,
- AndroidManifest.ATTRIBUTE_PACKAGE, mOldName, mNewName, false);
- if (edit != null) {
- multiEdit.addChild(edit);
- }
- }
- Set<String> keys = mElements.keySet();
- for (String key : keys) {
- String value = mElements.get(key);
- String oldValue = AndroidManifest.combinePackageAndClassName(mAppPackage, value);
- String newValue = oldValue.replaceFirst(mOldName, mNewName);
- TextEdit edit = createTextEdit(key, AndroidManifest.ATTRIBUTE_NAME, oldValue,
- newValue);
- if (edit != null) {
- multiEdit.addChild(edit);
- }
- if (AndroidManifest.NODE_ACTIVITY.equals(key)) {
- TextEdit alias = createTextEdit(AndroidManifest.NODE_ACTIVITY_ALIAS,
- AndroidManifest.ATTRIBUTE_TARGET_ACTIVITY, oldValue, newValue);
- if (alias != null) {
- multiEdit.addChild(alias);
- }
- }
- }
- setEdit(multiEdit);
- }
-
- @Override
- public Change perform(IProgressMonitor pm) throws CoreException {
- super.perform(pm);
- return new AndroidPackageRenameChange(mAndroidManifest, mManager, mDocument, mElements,
- mOldName, mNewName, mIsPackage);
- }
-
- @Override
- public void dispose() {
- super.dispose();
- RefactoringUtil.fixModel(mModel, mDocument);
-
- if (mManager != null) {
- try {
- mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
- if (mIsPackage) {
- new FixImportsJob("Fix Rename Package", mAndroidManifest, mNewName).schedule(500);
- }
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeMoveChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeMoveChange.java
deleted file mode 100644
index 7dcc505..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeMoveChange.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.jface.text.IDocument;
-
-import java.util.Map;
-
-/**
- * A text change that operates on android manifest when execute Java Move type refactoring
-*/
-public class AndroidTypeMoveChange extends AndroidTypeRenameChange {
-
- /**
- * Creates a new <code>AndroidTypeMoveChange</code>
- *
- * @param androidManifest the android manifest file
- * @param manager the text buffer manager
- * @param document the document
- * @param elements the elements
- * @param newName the new name
- * @param oldName the old name
- */
- public AndroidTypeMoveChange(IFile androidManifest, ITextFileBufferManager manager,
- IDocument document, Map<String, String> elements, String newName, String oldName) {
- super(androidManifest, manager, document, elements, newName, oldName);
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeRenameChange.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeRenameChange.java
deleted file mode 100644
index 798c0d2..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/changes/AndroidTypeRenameChange.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.changes;
-
-import com.android.ide.eclipse.adt.internal.refactoring.core.RefactoringUtil;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.Change;
-import org.eclipse.text.edits.MultiTextEdit;
-import org.eclipse.text.edits.TextEdit;
-
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A text change that operates on android manifest when execute Java Rename type refactoring
-*/
-public class AndroidTypeRenameChange extends AndroidDocumentChange {
-
- /**
- * * Creates a new <code>AndroidTypeRenameChange</code>
- *
- * @param androidManifest the android manifest file
- * @param manager the text buffer manager
- * @param document the document
- * @param elements the elements
- * @param newName the new name
- * @param oldName the old name
- */
- public AndroidTypeRenameChange(IFile androidManifest, ITextFileBufferManager manager,
- IDocument document, Map<String, String> elements, String newName, String oldName) {
- super(document);
- this.mDocument = document;
- this.mElements = elements;
- this.mNewName = newName;
- this.mOldName = oldName;
- this.mManager = manager;
- this.mAndroidManifest = androidManifest;
- try {
- this.mModel = getModel(document);
- } catch (Exception ignore) {
- }
- if (mModel != null) {
- addEdits();
- }
- }
-
- /**
- * Adds text edits for this change
- */
- private void addEdits() {
- MultiTextEdit multiEdit = new MultiTextEdit();
- Set<String> keys = mElements.keySet();
- for (String key : keys) {
- TextEdit edit = createTextEdit(key, AndroidManifest.ATTRIBUTE_NAME, mOldName,
- mNewName);
- if (edit != null) {
- multiEdit.addChild(edit);
- }
- if (AndroidManifest.NODE_ACTIVITY.equals(key)) {
- TextEdit alias = createTextEdit(AndroidManifest.NODE_ACTIVITY_ALIAS,
- AndroidManifest.ATTRIBUTE_TARGET_ACTIVITY, mOldName, mNewName);
- if (alias != null) {
- multiEdit.addChild(alias);
- }
- TextEdit manageSpaceActivity = createTextEdit(
- AndroidManifest.NODE_APPLICATION,
- AndroidManifest.ATTRIBUTE_MANAGE_SPACE_ACTIVITY, mOldName, mNewName);
- if (manageSpaceActivity != null) {
- multiEdit.addChild(manageSpaceActivity);
- }
- }
- }
- setEdit(multiEdit);
- }
-
- @Override
- public Change perform(IProgressMonitor pm) throws CoreException {
- super.perform(pm);
- return new AndroidTypeRenameChange(mAndroidManifest, mManager, mDocument, mElements,
- mOldName, mNewName);
- }
-
- @Override
- public void dispose() {
- super.dispose();
- RefactoringUtil.fixModel(mModel, mDocument);
-
- if (mManager != null) {
- try {
- mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java
deleted file mode 100644
index 52b49a7..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidPackageRenameParticipant.java
+++ /dev/null
@@ -1,528 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.core;
-
-import com.android.SdkConstants;
-import com.android.ide.common.xml.ManifestData;
-import com.android.ide.eclipse.adt.AdtConstants;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutFileChanges;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidPackageRenameChange;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.FileBuffers;
-import org.eclipse.core.filebuffers.ITextFileBuffer;
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IPath;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jdt.core.IJavaElement;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IPackageFragment;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jdt.core.search.IJavaSearchConstants;
-import org.eclipse.jdt.core.search.IJavaSearchScope;
-import org.eclipse.jdt.core.search.SearchEngine;
-import org.eclipse.jdt.core.search.SearchMatch;
-import org.eclipse.jdt.core.search.SearchParticipant;
-import org.eclipse.jdt.core.search.SearchPattern;
-import org.eclipse.jdt.core.search.SearchRequestor;
-import org.eclipse.jdt.internal.corext.refactoring.changes.RenamePackageChange;
-import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.Change;
-import org.eclipse.ltk.core.refactoring.CompositeChange;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A participant to participate in refactorings that rename a package in an Android project.
- * The class updates android manifest and the layout file
- * The user can suppress refactoring by disabling the "Update references" checkbox
- * <p>
- * Rename participants are registered via the extension point <code>
- * org.eclipse.ltk.core.refactoring.renameParticipants</code>.
- * Extensions to this extension point must therefore extend
- * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
- * </p>
- */
-@SuppressWarnings("restriction")
-public class AndroidPackageRenameParticipant extends AndroidRenameParticipant {
-
- private IPackageFragment mPackageFragment;
-
- private boolean mIsPackage;
-
- private Set<AndroidLayoutFileChanges> mFileChanges = new HashSet<AndroidLayoutFileChanges>();
-
- @Override
- public Change createChange(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- if (pm.isCanceled()) {
- return null;
- }
- if (!getArguments().getUpdateReferences())
- return null;
- IPath pkgPath = mPackageFragment.getPath();
- IJavaProject javaProject = (IJavaProject) mPackageFragment
- .getAncestor(IJavaElement.JAVA_PROJECT);
- IProject project = javaProject.getProject();
- IPath genPath = project.getFullPath().append(SdkConstants.FD_GEN_SOURCES);
- if (genPath.isPrefixOf(pkgPath)) {
- RefactoringUtil.logInfo(getName() + ": Cannot rename generated package.");
- return null;
- }
- CompositeChange result = new CompositeChange(getName());
- if (mAndroidManifest.exists()) {
- if (mAndroidElements.size() > 0 || mIsPackage) {
- getDocument();
- Change change = new AndroidPackageRenameChange(mAndroidManifest, mManager,
- mDocument, mAndroidElements, mNewName, mOldName, mIsPackage);
- if (change != null) {
- result.add(change);
- }
- }
- if (mIsPackage) {
- Change genChange = getGenPackageChange(pm);
- if (genChange != null) {
- result.add(genChange);
- }
- }
- // add layoutChange
- for (AndroidLayoutFileChanges fileChange : mFileChanges) {
- IFile file = fileChange.getFile();
- ITextFileBufferManager lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- Change layoutChange = new AndroidLayoutChange(file, lDocument, lManager,
- fileChange.getChanges());
- if (layoutChange != null) {
- result.add(layoutChange);
- }
- }
- }
- return (result.getChildren().length == 0) ? null : result;
- }
-
- /**
- * Returns Android gen package text change
- *
- * @param pm the progress monitor
- *
- * @return Android gen package text change
- * @throws CoreException
- * @throws OperationCanceledException
- */
- public Change getGenPackageChange(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- if (mIsPackage) {
- IPackageFragment genJavaPackageFragment = getGenPackageFragment();
- if (genJavaPackageFragment != null && genJavaPackageFragment.exists()) {
- return new RenamePackageChange(genJavaPackageFragment, mNewName, true);
- }
- }
- return null;
- }
-
- /**
- * Return the gen package fragment
- *
- */
- private IPackageFragment getGenPackageFragment() throws JavaModelException {
- IJavaProject javaProject = (IJavaProject) mPackageFragment
- .getAncestor(IJavaElement.JAVA_PROJECT);
- if (javaProject != null && javaProject.isOpen()) {
- IProject project = javaProject.getProject();
- IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
- if (genFolder.exists()) {
- String javaPackagePath = mAppPackage.replace(".", "/");
- IPath genJavaPackagePath = genFolder.getFullPath().append(javaPackagePath);
- IPackageFragment genPackageFragment = javaProject
- .findPackageFragment(genJavaPackagePath);
- return genPackageFragment;
- }
- }
- return null;
- }
-
- @Override
- public String getName() {
- return "Android Package Rename";
- }
-
- @Override
- protected boolean initialize(final Object element) {
- mIsPackage = false;
- try {
- if (element instanceof IPackageFragment) {
- mPackageFragment = (IPackageFragment) element;
- if (!mPackageFragment.containsJavaResources())
- return false;
- IJavaProject javaProject = (IJavaProject) mPackageFragment
- .getAncestor(IJavaElement.JAVA_PROJECT);
- IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AdtConstants.WS_SEP
- + SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- if (manifestResource == null || !manifestResource.exists()
- || !(manifestResource instanceof IFile)) {
- RefactoringUtil.logInfo("Invalid or missing the "
- + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + project.getName()
- + " project.");
- return false;
- }
- mAndroidManifest = (IFile) manifestResource;
- String packageName = mPackageFragment.getElementName();
- ManifestData manifestData;
- manifestData = AndroidManifestHelper.parseForData(mAndroidManifest);
- if (manifestData == null) {
- return false;
- }
- mAppPackage = manifestData.getPackage();
- mOldName = packageName;
- mNewName = getArguments().getNewName();
- if (mOldName == null || mNewName == null) {
- return false;
- }
-
- if (RefactoringUtil.isRefactorAppPackage()
- && mAppPackage != null
- && mAppPackage.equals(packageName)) {
- mIsPackage = true;
- }
- mAndroidElements = addAndroidElements();
- try {
- final IType type = javaProject.findType(SdkConstants.CLASS_VIEW);
- SearchPattern pattern = SearchPattern.createPattern("*",
- IJavaSearchConstants.TYPE, IJavaSearchConstants.DECLARATIONS,
- SearchPattern.R_REGEXP_MATCH);
- IJavaSearchScope scope =SearchEngine.createJavaSearchScope(
- new IJavaElement[] { mPackageFragment });
- final HashSet<IType> elements = new HashSet<IType>();
- SearchRequestor requestor = new SearchRequestor() {
-
- @Override
- public void acceptSearchMatch(SearchMatch match) throws CoreException {
- Object elem = match.getElement();
- if (elem instanceof IType) {
- IType eType = (IType) elem;
- IType[] superTypes = JavaModelUtil.getAllSuperTypes(eType,
- new NullProgressMonitor());
- for (int i = 0; i < superTypes.length; i++) {
- if (superTypes[i].equals(type)) {
- elements.add(eType);
- break;
- }
- }
- }
-
- }
- };
- SearchEngine searchEngine = new SearchEngine();
- searchEngine.search(pattern, new SearchParticipant[] {
- SearchEngine.getDefaultSearchParticipant()
- }, scope, requestor, null);
- List<String> views = new ArrayList<String>();
- for (IType elem : elements) {
- views.add(elem.getFullyQualifiedName());
- }
- if (views.size() > 0) {
- String[] classNames = views.toArray(new String[0]);
- addLayoutChanges(project, classNames);
- }
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
-
- return mIsPackage || mAndroidElements.size() > 0 || mFileChanges.size() > 0;
- }
- } catch (JavaModelException ignore) {
- }
- return false;
- }
-
- /**
- * Adds layout changes for project
- *
- * @param project the Android project
- * @param classNames the layout classes
- */
- private void addLayoutChanges(IProject project, String[] classNames) {
- try {
- IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
- IResource[] layoutMembers = resFolder.members();
- for (int j = 0; j < layoutMembers.length; j++) {
- IResource resource = layoutMembers[j];
- if (resource instanceof IFolder
- && resource.exists()
- && resource.getName().startsWith(SdkConstants.FD_RES_LAYOUT)) {
- IFolder layoutFolder = (IFolder) resource;
- IResource[] members = layoutFolder.members();
- for (int i = 0; i < members.length; i++) {
- IResource member = members[i];
- if ((member instanceof IFile)
- && member.exists()
- && member.getName().endsWith(".xml")) { //$NON-NLS-1$
- IFile file = (IFile) member;
- Set<AndroidLayoutChangeDescription> changes =
- parse(file, classNames);
- if (changes.size() > 0) {
- AndroidLayoutFileChanges fileChange =
- new AndroidLayoutFileChanges(file);
- fileChange.getChanges().addAll(changes);
- mFileChanges.add(fileChange);
- }
- }
- }
- }
- }
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
-
- /**
- * Searches the layout file for classes
- *
- * @param file the Android layout file
- * @param classNames the layout classes
- */
- private Set<AndroidLayoutChangeDescription> parse(IFile file, String[] classNames) {
- Set<AndroidLayoutChangeDescription> changes =
- new HashSet<AndroidLayoutChangeDescription>();
- ITextFileBufferManager lManager = null;
- try {
- lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(),
- LocationKind.NORMALIZE, new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().
- getExistingModelForRead(lDocument);
- if (model == null) {
- if (lDocument instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) lDocument;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- NodeList nodes = xmlDoc.getElementsByTagName(SdkConstants.VIEW);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Node attributeNode = attributes
- .getNamedItem(SdkConstants.ATTR_CLASS);
- if (attributeNode instanceof Attr) {
- Attr attribute = (Attr) attributeNode;
- String value = attribute.getValue();
- if (value != null) {
- for (int j = 0; j < classNames.length; j++) {
- String className = classNames[j];
- if (value.equals(className)) {
- String newClassName = getNewClassName(className);
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(
- className, newClassName,
- AndroidLayoutChangeDescription.VIEW_TYPE);
- changes.add(layoutChange);
- }
- }
- }
- }
- }
- }
- for (int j = 0; j < classNames.length; j++) {
- String className = classNames[j];
- nodes = xmlDoc.getElementsByTagName(className);
- for (int i = 0; i < nodes.getLength(); i++) {
- String newClassName = getNewClassName(className);
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(
- className, newClassName,
- AndroidLayoutChangeDescription.STANDALONE_TYPE);
- changes.add(layoutChange);
- }
- }
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- } catch (CoreException ignore) {
- } finally {
- if (lManager != null) {
- try {
- lManager.disconnect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException ignore) {
- }
- }
- }
- return changes;
- }
-
- /**
- * Returns the new class name
- *
- * @param className the class name
- * @return the new class name
- */
- private String getNewClassName(String className) {
- int lastDot = className.lastIndexOf("."); //$NON-NLS-1$
- if (lastDot < 0) {
- return mNewName;
- }
- String name = className.substring(lastDot, className.length());
- String newClassName = mNewName + name;
- return newClassName;
- }
-
- /**
- * Returns the elements (activity, receiver, service ...)
- * which have to be renamed
- *
- * @return the android elements
- */
- private Map<String, String> addAndroidElements() {
- Map<String, String> androidElements = new HashMap<String, String>();
-
- IDocument document;
- try {
- document = getDocument();
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- if (mManager != null) {
- try {
- mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e1) {
- RefactoringUtil.log(e1);
- }
- }
- document = null;
- return androidElements;
- }
-
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
- if (model == null) {
- if (document instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) document;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- add(xmlDoc, androidElements, AndroidManifest.NODE_ACTIVITY,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_APPLICATION,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_PROVIDER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_RECEIVER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_SERVICE,
- AndroidManifest.ATTRIBUTE_NAME);
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- return androidElements;
- }
-
- /**
- * Adds the element (activity, receiver, service ...) to the map
- *
- * @param xmlDoc the document
- * @param androidElements the map
- * @param element the element
- */
- private void add(IDOMDocument xmlDoc, Map<String, String> androidElements, String element,
- String argument) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, argument);
- if (attribute != null) {
- String value = attribute.getValue();
- if (value != null) {
- String fullName = AndroidManifest.combinePackageAndClassName(mAppPackage,
- value);
- if (RefactoringUtil.isRefactorAppPackage()) {
- if (fullName != null && fullName.startsWith(mAppPackage)) {
- boolean startWithDot = (value.charAt(0) == '.');
- boolean hasDot = (value.indexOf('.') != -1);
- if (!startWithDot && hasDot) {
- androidElements.put(element, value);
- }
- }
- } else {
- if (fullName != null) {
- androidElements.put(element, value);
- }
- }
- }
- }
- }
- }
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidRenameParticipant.java
deleted file mode 100644
index 5ddca7f..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidRenameParticipant.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.core;
-
-import org.eclipse.core.filebuffers.FileBuffers;
-import org.eclipse.core.filebuffers.ITextFileBuffer;
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.RefactoringStatus;
-import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
-import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
-
-import java.util.Map;
-
-/**
- * The abstract class for Rename Package and Rename type participants
- *
- */
-public abstract class AndroidRenameParticipant extends RenameParticipant {
-
- protected IFile mAndroidManifest;
-
- protected ITextFileBufferManager mManager;
-
- protected String mOldName;
-
- protected String mNewName;
-
- protected IDocument mDocument;
-
- protected String mAppPackage;
-
- protected Map<String, String> mAndroidElements;
-
- @Override
- public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
- throws OperationCanceledException {
- return new RefactoringStatus();
- }
-
- /**
- * @return the document
- * @throws CoreException
- */
- public IDocument getDocument() throws CoreException {
- if (mDocument == null) {
- mManager = FileBuffers.getTextFileBufferManager();
- mManager.connect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- ITextFileBuffer buffer = mManager.getTextFileBuffer(mAndroidManifest.getFullPath(),
- LocationKind.NORMALIZE);
- mDocument = buffer.getDocument();
- }
- return mDocument;
- }
-
- /**
- * @return the android manifest file
- */
- public IFile getAndroidManifest() {
- return mAndroidManifest;
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java
deleted file mode 100644
index 25ca533..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeMoveParticipant.java
+++ /dev/null
@@ -1,416 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.core;
-
-import com.android.SdkConstants;
-import com.android.ide.common.xml.ManifestData;
-import com.android.ide.eclipse.adt.AdtConstants;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutFileChanges;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidTypeMoveChange;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.FileBuffers;
-import org.eclipse.core.filebuffers.ITextFileBuffer;
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jdt.core.IJavaElement;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IPackageFragment;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.ITypeHierarchy;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.Change;
-import org.eclipse.ltk.core.refactoring.CompositeChange;
-import org.eclipse.ltk.core.refactoring.RefactoringStatus;
-import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
-import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A participant to participate in refactorings that move a type in an Android project.
- * The class updates android manifest and the layout file
- * The user can suppress refactoring by disabling the "Update references" checkbox
- * <p>
- * Rename participants are registered via the extension point <code>
- * org.eclipse.ltk.core.refactoring.moveParticipants</code>.
- * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.MoveParticipant</code>.
- * </p>
- */
-@SuppressWarnings("restriction")
-public class AndroidTypeMoveParticipant extends MoveParticipant {
-
- protected IFile mAndroidManifest;
-
- protected ITextFileBufferManager mManager;
-
- protected String mOldName;
-
- protected String mNewName;
-
- protected IDocument mDocument;
-
- protected String mJavaPackage;
-
- protected Map<String, String> mAndroidElements;
-
- private Set<AndroidLayoutFileChanges> mFileChanges = new HashSet<AndroidLayoutFileChanges>();
-
- @Override
- public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
- throws OperationCanceledException {
- return new RefactoringStatus();
- }
-
- @Override
- public Change createChange(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- if (pm.isCanceled()) {
- return null;
- }
- if (!getArguments().getUpdateReferences())
- return null;
- CompositeChange result = new CompositeChange(getName());
- if (mAndroidManifest.exists()) {
- if (mAndroidElements.size() > 0) {
- getDocument();
- Change change = new AndroidTypeMoveChange(mAndroidManifest, mManager, mDocument,
- mAndroidElements, mNewName, mOldName);
- if (change != null) {
- result.add(change);
- }
- }
-
- for (AndroidLayoutFileChanges fileChange : mFileChanges) {
- IFile file = fileChange.getFile();
- ITextFileBufferManager lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- Change layoutChange = new AndroidLayoutChange(file, lDocument, lManager,
- fileChange.getChanges());
- if (layoutChange != null) {
- result.add(layoutChange);
- }
- }
- }
- return (result.getChildren().length == 0) ? null : result;
-
- }
-
- /**
- * @return the document
- * @throws CoreException
- */
- public IDocument getDocument() throws CoreException {
- if (mDocument == null) {
- mManager = FileBuffers.getTextFileBufferManager();
- mManager.connect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- ITextFileBuffer buffer = mManager.getTextFileBuffer(mAndroidManifest.getFullPath(),
- LocationKind.NORMALIZE);
- mDocument = buffer.getDocument();
- }
- return mDocument;
- }
-
- /**
- * @return the android manifest file
- */
- public IFile getAndroidManifest() {
- return mAndroidManifest;
- }
-
- @Override
- public String getName() {
- return "Android Type Move";
- }
-
- @Override
- protected boolean initialize(Object element) {
-
- if (element instanceof IType) {
- IType type = (IType) element;
- IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
- IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AdtConstants.WS_SEP
- + SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- if (manifestResource == null || !manifestResource.exists()
- || !(manifestResource instanceof IFile)) {
- RefactoringUtil.logInfo("Invalid or missing the "
- + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + project.getName()
- + " project.");
- return false;
- }
- mAndroidManifest = (IFile) manifestResource;
- ManifestData manifestData;
- manifestData = AndroidManifestHelper.parseForData(mAndroidManifest);
- if (manifestData == null) {
- return false;
- }
- mJavaPackage = manifestData.getPackage();
- mOldName = type.getFullyQualifiedName();
- Object destination = getArguments().getDestination();
- if (destination instanceof IPackageFragment) {
- IPackageFragment packageFragment = (IPackageFragment) destination;
- mNewName = packageFragment.getElementName() + "." + type.getElementName();
- }
- if (mOldName == null || mNewName == null) {
- return false;
- }
- mAndroidElements = addAndroidElements();
- try {
- ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(null);
- if (typeHierarchy == null) {
- return false;
- }
- IType[] superTypes = typeHierarchy.getAllSuperclasses(type);
- for (int i = 0; i < superTypes.length; i++) {
- IType superType = superTypes[i];
- String className = superType.getFullyQualifiedName();
- if (className.equals(SdkConstants.CLASS_VIEW)) {
- addLayoutChanges(project, type.getFullyQualifiedName());
- break;
- }
- }
- } catch (JavaModelException ignore) {
- }
- return mAndroidElements.size() > 0 || mFileChanges.size() > 0;
- }
- return false;
- }
-
- /**
- * Adds layout changes for project
- *
- * @param project the Android project
- * @param className the layout classes
- *
- */
- private void addLayoutChanges(IProject project, String className) {
- try {
- IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
- IFolder layoutFolder = resFolder.getFolder(SdkConstants.FD_RES_LAYOUT);
- IResource[] members = layoutFolder.members();
- for (int i = 0; i < members.length; i++) {
- IResource member = members[i];
- if ((member instanceof IFile) && member.exists()) {
- IFile file = (IFile) member;
- Set<AndroidLayoutChangeDescription> changes = parse(file, className);
- if (changes.size() > 0) {
- AndroidLayoutFileChanges fileChange = new AndroidLayoutFileChanges(file);
- fileChange.getChanges().addAll(changes);
- mFileChanges.add(fileChange);
- }
- }
- }
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
-
- /**
- * Searches the layout file for classes
- *
- * @param file the Android layout file
- * @param className the layout classes
- *
- */
- private Set<AndroidLayoutChangeDescription> parse(IFile file, String className) {
- Set<AndroidLayoutChangeDescription> changes = new HashSet<AndroidLayoutChangeDescription>();
- ITextFileBufferManager lManager = null;
- try {
- lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(), LocationKind.NORMALIZE, new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForRead(lDocument);
- if (model == null) {
- if (lDocument instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) lDocument;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- NodeList nodes = xmlDoc.getElementsByTagName(SdkConstants.VIEW);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Node attributeNode =
- attributes.getNamedItem(SdkConstants.ATTR_CLASS);
- if (attributeNode instanceof Attr) {
- Attr attribute = (Attr) attributeNode;
- String value = attribute.getValue();
- if (value != null && value.equals(className)) {
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(className, mNewName,
- AndroidLayoutChangeDescription.VIEW_TYPE);
- changes.add(layoutChange);
- }
- }
- }
- }
- nodes = xmlDoc.getElementsByTagName(className);
- for (int i = 0; i < nodes.getLength(); i++) {
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(className, mNewName,
- AndroidLayoutChangeDescription.STANDALONE_TYPE);
- changes.add(layoutChange);
- }
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- } catch (CoreException ignore) {
- } finally {
- if (lManager != null) {
- try {
- lManager.disconnect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException ignore) {
- }
- }
- }
- return changes;
- }
-
- /**
- * Returns the elements (activity, receiver, service ...)
- * which have to be renamed
- *
- * @return the android elements
- */
- private Map<String, String> addAndroidElements() {
- Map<String, String> androidElements = new HashMap<String, String>();
-
- IDocument document;
- try {
- document = getDocument();
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- if (mManager != null) {
- try {
- mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e1) {
- RefactoringUtil.log(e1);
- }
- }
- document = null;
- return androidElements;
- }
-
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
- if (model == null) {
- if (document instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) document;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- add(xmlDoc, androidElements, AndroidManifest.NODE_ACTIVITY,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_APPLICATION,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_PROVIDER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_RECEIVER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_SERVICE,
- AndroidManifest.ATTRIBUTE_NAME);
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- return androidElements;
- }
-
- /**
- * Adds the element (activity, receiver, service ...) to the map
- *
- * @param xmlDoc the document
- * @param androidElements the map
- * @param element the element
- */
- private void add(IDOMDocument xmlDoc, Map<String, String> androidElements, String element,
- String argument) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, argument);
- if (attribute != null) {
- String value = attribute.getValue();
- if (value != null) {
- String fullName = AndroidManifest.combinePackageAndClassName(mJavaPackage,
- value);
- if (fullName != null && fullName.equals(mOldName)) {
- androidElements.put(element, value);
- }
- }
- }
- }
- }
- }
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java
deleted file mode 100644
index d62cc23..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/AndroidTypeRenameParticipant.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.core;
-
-import com.android.SdkConstants;
-import com.android.ide.common.xml.ManifestData;
-import com.android.ide.eclipse.adt.AdtConstants;
-import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChange;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutChangeDescription;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidLayoutFileChanges;
-import com.android.ide.eclipse.adt.internal.refactoring.changes.AndroidTypeRenameChange;
-import com.android.xml.AndroidManifest;
-
-import org.eclipse.core.filebuffers.FileBuffers;
-import org.eclipse.core.filebuffers.ITextFileBuffer;
-import org.eclipse.core.filebuffers.ITextFileBufferManager;
-import org.eclipse.core.filebuffers.LocationKind;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IFolder;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.OperationCanceledException;
-import org.eclipse.jdt.core.IJavaElement;
-import org.eclipse.jdt.core.IJavaProject;
-import org.eclipse.jdt.core.IType;
-import org.eclipse.jdt.core.ITypeHierarchy;
-import org.eclipse.jdt.core.JavaModelException;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.ltk.core.refactoring.Change;
-import org.eclipse.ltk.core.refactoring.CompositeChange;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument;
-import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A participant to participate in refactorings that rename a type in an Android project.
- * The class updates android manifest and the layout file
- * The user can suppress refactoring by disabling the "Update references" checkbox
- * <p>
- * Rename participants are registered via the extension point <code>
- * org.eclipse.ltk.core.refactoring.renameParticipants</code>.
- * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
- * </p>
- */
-@SuppressWarnings("restriction")
-public class AndroidTypeRenameParticipant extends AndroidRenameParticipant {
-
- private Set<AndroidLayoutFileChanges> mFileChanges = new HashSet<AndroidLayoutFileChanges>();
-
- private String mLayoutNewName;
-
- @Override
- public Change createChange(IProgressMonitor pm) throws CoreException,
- OperationCanceledException {
- if (pm.isCanceled()) {
- return null;
- }
- if (!getArguments().getUpdateReferences())
- return null;
- CompositeChange result = new CompositeChange(getName());
- if (mAndroidManifest.exists()) {
- if (mAndroidElements.size() > 0) {
- getDocument();
- Change change = new AndroidTypeRenameChange(mAndroidManifest, mManager, mDocument,
- mAndroidElements, mNewName, mOldName);
- if (change != null) {
- result.add(change);
- }
- }
- // add layoutChange
- for (AndroidLayoutFileChanges fileChange : mFileChanges) {
- IFile file = fileChange.getFile();
- ITextFileBufferManager lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- Change layoutChange = new AndroidLayoutChange(file, lDocument, lManager,
- fileChange.getChanges());
- if (layoutChange != null) {
- result.add(layoutChange);
- }
- }
- }
- return (result.getChildren().length == 0) ? null : result;
-
- }
-
- @Override
- public String getName() {
- return "Android Type Rename";
- }
-
- @Override
- protected boolean initialize(Object element) {
-
- if (element instanceof IType) {
- IType type = (IType) element;
- IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
- IProject project = javaProject.getProject();
- IResource manifestResource = project.findMember(AdtConstants.WS_SEP
- + SdkConstants.FN_ANDROID_MANIFEST_XML);
-
- if (manifestResource == null || !manifestResource.exists()
- || !(manifestResource instanceof IFile)) {
- RefactoringUtil.logInfo("Invalid or missing the "
- + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + project.getName()
- + " project.");
- return false;
- }
- mAndroidManifest = (IFile) manifestResource;
- ManifestData manifestData;
- manifestData = AndroidManifestHelper.parseForData(mAndroidManifest);
- if (manifestData == null) {
- return false;
- }
- mAppPackage = manifestData.getPackage();
- mOldName = type.getFullyQualifiedName();
- String packageName = type.getPackageFragment().getElementName();
- mNewName = getArguments().getNewName();
- if (packageName != null) {
- mLayoutNewName = packageName + "." + getArguments().getNewName(); //$NON-NLS-1$
- } else {
- mLayoutNewName = getArguments().getNewName();
- }
- if (mOldName == null || mNewName == null) {
- return false;
- }
- if (!RefactoringUtil.isRefactorAppPackage() && mNewName.indexOf(".") == -1) { //$NON-NLS-1$
- mNewName = packageName + "." + mNewName; //$NON-NLS-1$
- }
- mAndroidElements = addAndroidElements();
- try {
- ITypeHierarchy typeHierarchy = type.newSupertypeHierarchy(null);
- if (typeHierarchy == null) {
- return false;
- }
- IType[] superTypes = typeHierarchy.getAllSuperclasses(type);
- for (int i = 0; i < superTypes.length; i++) {
- IType superType = superTypes[i];
- String className = superType.getFullyQualifiedName();
- if (className.equals(SdkConstants.CLASS_VIEW)) {
- addLayoutChanges(project, type.getFullyQualifiedName());
- break;
- }
- }
- } catch (JavaModelException ignore) {
- }
-
- return mAndroidElements.size() > 0 || mFileChanges.size() > 0;
- }
- return false;
- }
-
- /**
- * Adds layout changes for project
- *
- * @param project the Android project
- * @param className the layout classes
- *
- */
- private void addLayoutChanges(IProject project, String className) {
- try {
- IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
- IFolder layoutFolder = resFolder.getFolder(SdkConstants.FD_RES_LAYOUT);
- IResource[] members = layoutFolder.members();
- for (int i = 0; i < members.length; i++) {
- IResource member = members[i];
- if ((member instanceof IFile) && member.exists()) {
- IFile file = (IFile) member;
- Set<AndroidLayoutChangeDescription> changes = parse(file, className);
- if (changes.size() > 0) {
- AndroidLayoutFileChanges fileChange = new AndroidLayoutFileChanges(file);
- fileChange.getChanges().addAll(changes);
- mFileChanges.add(fileChange);
- }
- }
- }
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- }
- }
-
- /**
- * Searches the layout file for classes
- *
- * @param file the Android layout file
- * @param className the layout classes
- *
- */
- private Set<AndroidLayoutChangeDescription> parse(IFile file, String className) {
- Set<AndroidLayoutChangeDescription> changes = new HashSet<AndroidLayoutChangeDescription>();
- ITextFileBufferManager lManager = null;
- try {
- lManager = FileBuffers.getTextFileBufferManager();
- lManager.connect(file.getFullPath(), LocationKind.NORMALIZE, new NullProgressMonitor());
- ITextFileBuffer buffer = lManager.getTextFileBuffer(file.getFullPath(),
- LocationKind.NORMALIZE);
- IDocument lDocument = buffer.getDocument();
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForRead(lDocument);
- if (model == null) {
- if (lDocument instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) lDocument;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- NodeList nodes = xmlDoc
- .getElementsByTagName(SdkConstants.VIEW);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Node attributeNode =
- attributes.getNamedItem(SdkConstants.ATTR_CLASS);
- if (attributeNode instanceof Attr) {
- Attr attribute = (Attr) attributeNode;
- String value = attribute.getValue();
- if (value != null && value.equals(className)) {
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(className, mLayoutNewName,
- AndroidLayoutChangeDescription.VIEW_TYPE);
- changes.add(layoutChange);
- }
- }
- }
- }
- nodes = xmlDoc.getElementsByTagName(className);
- for (int i = 0; i < nodes.getLength(); i++) {
- AndroidLayoutChangeDescription layoutChange =
- new AndroidLayoutChangeDescription(className, mLayoutNewName,
- AndroidLayoutChangeDescription.STANDALONE_TYPE);
- changes.add(layoutChange);
- }
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- } catch (CoreException ignore) {
- } finally {
- if (lManager != null) {
- try {
- lManager.disconnect(file.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException ignore) {
- }
- }
- }
- return changes;
- }
-
- /**
- * Returns the elements (activity, receiver, service ...)
- * which have to be renamed
- *
- * @return the android elements
- *
- */
- private Map<String, String> addAndroidElements() {
- Map<String, String> androidElements = new HashMap<String, String>();
-
- IDocument document;
- try {
- document = getDocument();
- } catch (CoreException e) {
- RefactoringUtil.log(e);
- if (mManager != null) {
- try {
- mManager.disconnect(mAndroidManifest.getFullPath(), LocationKind.NORMALIZE,
- new NullProgressMonitor());
- } catch (CoreException e1) {
- RefactoringUtil.log(e1);
- }
- }
- document = null;
- return androidElements;
- }
-
- IStructuredModel model = null;
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForRead(document);
- if (model == null) {
- if (document instanceof IStructuredDocument) {
- IStructuredDocument structuredDocument = (IStructuredDocument) document;
- model = StructuredModelManager.getModelManager().getModelForRead(
- structuredDocument);
- }
- }
- if (model != null) {
- IDOMModel xmlModel = (IDOMModel) model;
- IDOMDocument xmlDoc = xmlModel.getDocument();
- add(xmlDoc, androidElements, AndroidManifest.NODE_ACTIVITY,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_APPLICATION,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_PROVIDER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_RECEIVER,
- AndroidManifest.ATTRIBUTE_NAME);
- add(xmlDoc, androidElements, AndroidManifest.NODE_SERVICE,
- AndroidManifest.ATTRIBUTE_NAME);
- }
- } finally {
- if (model != null) {
- model.releaseFromRead();
- }
- }
-
- return androidElements;
- }
-
- /**
- * (non-Javadoc) Adds the element (activity, receiver, service ...) to the map
- *
- * @param xmlDoc the document
- * @param androidElements the map
- * @param element the element
- */
- private void add(IDOMDocument xmlDoc, Map<String, String> androidElements, String element,
- String argument) {
- NodeList nodes = xmlDoc.getElementsByTagName(element);
- for (int i = 0; i < nodes.getLength(); i++) {
- Node node = nodes.item(i);
- NamedNodeMap attributes = node.getAttributes();
- if (attributes != null) {
- Attr attribute = RefactoringUtil.findAndroidAttributes(attributes, argument);
- if (attribute != null) {
- String value = attribute.getValue();
- if (value != null) {
- String fullName = AndroidManifest.combinePackageAndClassName(mAppPackage,
- value);
- if (fullName != null && fullName.equals(mOldName)) {
- androidElements.put(element, value);
- }
- }
- }
- }
- }
- }
-
-
-
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java
deleted file mode 100644
index 1121081..0000000
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/RefactoringUtil.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Eclipse Public License, Version 1.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.eclipse.org/org/documents/epl-v10.php
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.ide.eclipse.adt.internal.refactoring.core;
-
-import com.android.SdkConstants;
-import com.android.ide.eclipse.adt.AdtPlugin;
-
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.jface.text.IDocument;
-import org.eclipse.wst.sse.core.StructuredModelManager;
-import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
-import org.w3c.dom.Attr;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-
-/**
- * The utility class for android refactoring
- *
- */
-@SuppressWarnings("restriction")
-public class RefactoringUtil {
-
- private static boolean sRefactorAppPackage = false;
-
- /**
- * Releases SSE read model; saves SSE model if exists edit model
- * Called in dispose method of refactoring change classes
- *
- * @param model the SSE model
- * @param document the document
- */
- public static void fixModel(IStructuredModel model, IDocument document) {
- if (model != null) {
- model.releaseFromRead();
- }
- model = null;
- if (document == null) {
- return;
- }
- try {
- model = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
- if (model != null) {
- model.save();
- }
- } catch (UnsupportedEncodingException e1) {
- // ignore
- } catch (IOException e1) {
- // ignore
- } catch (CoreException e1) {
- // ignore
- } finally {
- if (model != null) {
- model.releaseFromEdit();
- }
- }
- }
-
- /**
- * Finds attribute by name in android namespace
- *
- * @param attributes the attributes collection
- * @param localName the local part of the qualified name
- *
- * @return the first attribute with this name in android namespace
- */
- public static Attr findAndroidAttributes(final NamedNodeMap attributes,
- final String localName) {
- Attr attribute = null;
- for (int j = 0; j < attributes.getLength(); j++) {
- Node attNode = attributes.item(j);
- if (attNode instanceof Attr) {
- Attr attr = (Attr) attNode;
- String name = attr.getLocalName();
- String namespace = attr.getNamespaceURI();
- if (SdkConstants.NS_RESOURCES.equals(namespace)
- && name != null
- && name.equals(localName)) {
- attribute = attr;
- break;
- }
- }
- }
- return attribute;
- }
-
- /**
- * Logs the error message
- *
- * @param message the message
- */
- public static void logError(String message) {
- AdtPlugin.log(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message);
- }
-
- /**
- * Logs the info message
- *
- * @param message the message
- */
- public static void logInfo(String message) {
- AdtPlugin.log(IStatus.INFO, AdtPlugin.PLUGIN_ID, message);
- }
-
- /**
- * Logs the the exception
- *
- * @param e the exception
- */
- public static void log(Throwable e) {
- AdtPlugin.log(e, e.getMessage());
- }
-
- /**
- * @return true if Rename/Move package needs to change the application package
- * default is false
- *
- */
- public static boolean isRefactorAppPackage() {
- return sRefactorAppPackage;
- }
-
- /**
- * @param refactorAppPackage true if Rename/Move package needs to change the application package
- */
- public static void setRefactorAppPackage(boolean refactorAppPackage) {
- RefactoringUtil.sRefactorAppPackage = refactorAppPackage;
- }
-}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java
new file mode 100644
index 0000000..b821777
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidPackageRenameParticipant.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_CONTEXT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_PACKAGE;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_TAG;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.internal.corext.refactoring.changes.RenamePackageChange;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameCompilationUnitProcessor;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.FileStatusContext;
+import org.eclipse.ltk.core.refactoring.NullChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
+import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A participant to participate in refactorings that rename a package in an Android project.
+ * The class updates android manifest and the layout file
+ * The user can suppress refactoring by disabling the "Update references" checkbox
+ * <p>
+ * Rename participants are registered via the extension point <code>
+ * org.eclipse.ltk.core.refactoring.renameParticipants</code>.
+ * Extensions to this extension point must therefore extend
+ * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
+ * </p>
+ */
+@SuppressWarnings("restriction")
+public class AndroidPackageRenameParticipant extends RenameParticipant {
+
+ private IProject mProject;
+ private IFile mManifestFile;
+ private IPackageFragment mPackageFragment;
+ private String mOldPackage;
+ private String mNewPackage;
+ private String mAppPackage;
+ private boolean mRefactoringAppPackage;
+
+ @Override
+ public String getName() {
+ return "Android Package Rename";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ if (mAppPackage.equals(mOldPackage) && !mRefactoringAppPackage) {
+ IRegion region = null;
+ Document document = DomUtilities.getDocument(mManifestFile);
+ if (document != null && document.getDocumentElement() != null) {
+ Attr attribute = document.getDocumentElement().getAttributeNode(ATTR_PACKAGE);
+ if (attribute instanceof IndexedRegion) {
+ IndexedRegion ir = (IndexedRegion) attribute;
+ int start = ir.getStartOffset();
+ region = new Region(start, ir.getEndOffset() - start);
+ }
+ }
+ if (region == null) {
+ region = new Region(0, 0);
+ }
+ // There's no line wrapping in the error dialog, so split up the message into
+ // individually digestible pieces of information
+ RefactoringStatusContext ctx = new FileStatusContext(mManifestFile, region);
+ RefactoringStatus status = RefactoringStatus.createInfoStatus(
+ "You are refactoring the same package as your application's " +
+ "package (specified in the manifest).\n", ctx);
+ status.addInfo(
+ "Note that this refactoring does NOT also update your " +
+ "application package.", ctx);
+ status.addInfo("The application package defines your application's identity.", ctx);
+ status.addInfo(
+ "If you change it, then it is considered to be a different application.", ctx);
+ status.addInfo("(Users of the previous version cannot update to the new version.)",
+ ctx);
+ status.addInfo(
+ "The application package, and the package containing the code, can differ.",
+ ctx);
+ status.addInfo(
+ "To really change application package, " +
+ "choose \"Android Tools\" > \"Rename Application Package.\" " +
+ "from the project context menu.", ctx);
+ return status;
+ }
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ protected boolean initialize(final Object element) {
+ mRefactoringAppPackage = false;
+ try {
+ // Only propose this refactoring if the "Update References" checkbox is set.
+ if (!getArguments().getUpdateReferences()) {
+ return false;
+ }
+
+ if (element instanceof IPackageFragment) {
+ mPackageFragment = (IPackageFragment) element;
+ if (!mPackageFragment.containsJavaResources()) {
+ return false;
+ }
+ IJavaProject javaProject = (IJavaProject) mPackageFragment
+ .getAncestor(IJavaElement.JAVA_PROJECT);
+ mProject = javaProject.getProject();
+ IResource manifestResource = mProject.findMember(AdtConstants.WS_SEP
+ + SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (manifestResource == null || !manifestResource.exists()
+ || !(manifestResource instanceof IFile)) {
+ RefactoringUtil.logInfo("Invalid or missing the "
+ + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the "
+ + mProject.getName() + " project.");
+ return false;
+ }
+ mManifestFile = (IFile) manifestResource;
+ String packageName = mPackageFragment.getElementName();
+ ManifestData manifestData;
+ manifestData = AndroidManifestHelper.parseForData(mManifestFile);
+ if (manifestData == null) {
+ return false;
+ }
+ mAppPackage = manifestData.getPackage();
+ mOldPackage = packageName;
+ mNewPackage = getArguments().getNewName();
+ if (mOldPackage == null || mNewPackage == null) {
+ return false;
+ }
+
+ if (RefactoringUtil.isRefactorAppPackage()
+ && mAppPackage != null
+ && mAppPackage.equals(packageName)) {
+ mRefactoringAppPackage = true;
+ }
+
+ return true;
+ }
+ } catch (JavaModelException ignore) {
+ }
+ return false;
+ }
+
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (pm.isCanceled()) {
+ return null;
+ }
+ if (!getArguments().getUpdateReferences()) {
+ return null;
+ }
+
+ RefactoringProcessor p = getProcessor();
+ if (p instanceof RenameCompilationUnitProcessor) {
+ RenameTypeProcessor rtp =
+ ((RenameCompilationUnitProcessor) p).getRenameTypeProcessor();
+ if (rtp != null) {
+ String pattern = rtp.getFilePatterns();
+ boolean updQualf = rtp.getUpdateQualifiedNames();
+ if (updQualf && pattern != null && pattern.contains("xml")) { //$NON-NLS-1$
+ // Do not propose this refactoring if the
+ // "Update fully qualified names in non-Java files" option is
+ // checked and the file patterns mention XML. [c.f. SDK bug 21589]
+ return null;
+ }
+ }
+ }
+
+ IPath pkgPath = mPackageFragment.getPath();
+ IPath genPath = mProject.getFullPath().append(SdkConstants.FD_GEN_SOURCES);
+ if (genPath.isPrefixOf(pkgPath)) {
+ RefactoringUtil.logInfo(getName() + ": Cannot rename generated package.");
+ return null;
+ }
+ CompositeChange result = new CompositeChange(getName());
+ result.markAsSynthetic();
+
+ addManifestFileChanges(result);
+
+ // Update layout files; we don't just need to react to custom view
+ // changes, we need to update fragment references and even tool:context activity
+ // references
+ addLayoutFileChanges(mProject, result);
+
+ // Also update in dependent projects
+ ProjectState projectState = Sdk.getProjectState(mProject);
+ if (projectState != null) {
+ Collection<ProjectState> parentProjects = projectState.getFullParentProjects();
+ for (ProjectState parentProject : parentProjects) {
+ IProject project = parentProject.getProject();
+ addLayoutFileChanges(project, result);
+ }
+ }
+
+ if (mRefactoringAppPackage) {
+ Change genChange = getGenPackageChange(pm);
+ if (genChange != null) {
+ result.add(genChange);
+ }
+
+ return new NullChange("Update Imports") {
+ @Override
+ public Change perform(IProgressMonitor monitor) throws CoreException {
+ FixImportsJob job = new FixImportsJob("Fix Rename Package",
+ mManifestFile, mNewPackage);
+ job.schedule(500);
+
+ // Not undoable: just return null instead of an undo-change.
+ return null;
+ }
+ };
+ }
+
+ return (result.getChildren().length == 0) ? null : result;
+ }
+
+ /**
+ * Returns Android gen package text change
+ *
+ * @param pm the progress monitor
+ *
+ * @return Android gen package text change
+ * @throws CoreException if an error happens
+ * @throws OperationCanceledException if the operation is canceled
+ */
+ public Change getGenPackageChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (mRefactoringAppPackage) {
+ IPackageFragment genJavaPackageFragment = getGenPackageFragment();
+ if (genJavaPackageFragment != null && genJavaPackageFragment.exists()) {
+ return new RenamePackageChange(genJavaPackageFragment, mNewPackage, true);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return the gen package fragment
+ */
+ private IPackageFragment getGenPackageFragment() throws JavaModelException {
+ IJavaProject javaProject = (IJavaProject) mPackageFragment
+ .getAncestor(IJavaElement.JAVA_PROJECT);
+ if (javaProject != null && javaProject.isOpen()) {
+ IProject project = javaProject.getProject();
+ IFolder genFolder = project.getFolder(SdkConstants.FD_GEN_SOURCES);
+ if (genFolder.exists()) {
+ String javaPackagePath = mAppPackage.replace('.', '/');
+ IPath genJavaPackagePath = genFolder.getFullPath().append(javaPackagePath);
+ IPackageFragment genPackageFragment = javaProject
+ .findPackageFragment(genJavaPackagePath);
+ return genPackageFragment;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the new class name
+ *
+ * @param fqcn the fully qualified class name in the renamed package
+ * @return the new class name
+ */
+ private String getNewClassName(String fqcn) {
+ assert isInRenamedPackage(fqcn) : fqcn;
+ int lastDot = fqcn.lastIndexOf('.');
+ if (lastDot < 0) {
+ return mNewPackage;
+ }
+ String name = fqcn.substring(lastDot, fqcn.length());
+ String newClassName = mNewPackage + name;
+ return newClassName;
+ }
+
+ private void addManifestFileChanges(CompositeChange result) {
+ addXmlFileChanges(mManifestFile, result, true);
+ }
+
+ private void addLayoutFileChanges(IProject project, CompositeChange result) {
+ try {
+ // Update references in XML resource files
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ IResource[] folders = resFolder.members();
+ for (IResource folder : folders) {
+ String folderName = folder.getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ if (folderType != ResourceFolderType.LAYOUT) {
+ continue;
+ }
+ if (!(folder instanceof IFolder)) {
+ continue;
+ }
+ IResource[] files = ((IFolder) folder).members();
+ for (int i = 0; i < files.length; i++) {
+ IResource member = files[i];
+ if ((member instanceof IFile) && member.exists()) {
+ IFile file = (IFile) member;
+ String fileName = member.getName();
+
+ if (SdkUtils.endsWith(fileName, DOT_XML)) {
+ addXmlFileChanges(file, result, false);
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ RefactoringUtil.log(e);
+ }
+ }
+
+ private boolean addXmlFileChanges(IFile file, CompositeChange changes, boolean isManifest) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ try {
+ model = modelManager.getExistingModelForRead(file);
+ if (model == null) {
+ model = modelManager.getModelForRead(file);
+ }
+ if (model != null) {
+ IStructuredDocument document = model.getStructuredDocument();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Element root = domModel.getDocument().getDocumentElement();
+ if (root != null) {
+ List<TextEdit> edits = new ArrayList<TextEdit>();
+ if (isManifest) {
+ addManifestReplacements(edits, root, document);
+ } else {
+ addLayoutReplacements(edits, root, document);
+ }
+ if (!edits.isEmpty()) {
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()]));
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ change.setTextType(EXT_XML);
+ change.setEdit(rootEdit);
+ changes.add(change);
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isInRenamedPackage(String fqcn) {
+ return fqcn.startsWith(mOldPackage)
+ && fqcn.length() > mOldPackage.length()
+ && fqcn.indexOf('.', mOldPackage.length() + 1) == -1;
+ }
+
+ private void addLayoutReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ String tag = element.getTagName();
+ if (isInRenamedPackage(tag)) {
+ int start = RefactoringUtil.getTagNameRangeStart(element, document);
+ if (start != -1) {
+ int end = start + tag.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(tag)));
+ }
+ } else {
+ Attr classNode = null;
+ if (tag.equals(VIEW_TAG)) {
+ classNode = element.getAttributeNode(ATTR_CLASS);
+ } else if (tag.equals(VIEW_FRAGMENT)) {
+ classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode == null) {
+ classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ }
+ } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) {
+ classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT);
+ if (classNode != null && classNode.getValue().startsWith(".")) { //$NON-NLS-1$
+ classNode = null;
+ }
+ }
+ if (classNode != null) {
+ String fqcn = classNode.getValue();
+ if (isInRenamedPackage(fqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + fqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(fqcn)));
+ }
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addLayoutReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+
+ private void addManifestReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ if (mRefactoringAppPackage &&
+ element == element.getOwnerDocument().getDocumentElement()) {
+ // Update the app package declaration
+ Attr pkg = element.getAttributeNode(ATTR_PACKAGE);
+ if (pkg != null && pkg.getValue().equals(mOldPackage)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(pkg, document);
+ if (start != -1) {
+ int end = start + mOldPackage.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewPackage));
+ }
+ }
+ }
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ if (!RefactoringUtil.isManifestClassAttribute(attr)) {
+ continue;
+ }
+
+ String value = attr.getValue();
+ if (isInRenamedPackage(value)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + value.length();
+ edits.add(new ReplaceEdit(start, end - start, getNewClassName(value)));
+ }
+ } else if (value.startsWith(".")) {
+ // If we're renaming the app package
+ String fqcn = mAppPackage + value;
+ if (isInRenamedPackage(fqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + value.length();
+ String newClassName = getNewClassName(fqcn);
+ if (mRefactoringAppPackage) {
+ newClassName = newClassName.substring(mNewPackage.length());
+ } else if (newClassName.startsWith(mOldPackage)
+ && newClassName.charAt(mOldPackage.length()) == '.') {
+ newClassName = newClassName.substring(mOldPackage.length());
+ }
+
+ if (!newClassName.equals(value)) {
+ edits.add(new ReplaceEdit(start, end - start, newClassName));
+ }
+ }
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addManifestReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java
new file mode 100644
index 0000000..2146184
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeMoveParticipant.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_CONTEXT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_TAG;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IPackageFragment;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A participant to participate in refactorings that move a type in an Android project.
+ * The class updates android manifest and the layout file
+ * The user can suppress refactoring by disabling the "Update references" checkbox
+ * <p>
+ * Rename participants are registered via the extension point <code>
+ * org.eclipse.ltk.core.refactoring.moveParticipants</code>.
+ * Extensions to this extension point must therefore extend <code>org.eclipse.ltk.core.refactoring.participants.MoveParticipant</code>.
+ * </p>
+ */
+@SuppressWarnings("restriction")
+public class AndroidTypeMoveParticipant extends MoveParticipant {
+
+ private IProject mProject;
+ protected IFile mManifestFile;
+ protected String mOldFqcn;
+ protected String mNewFqcn;
+ protected String mAppPackage;
+
+ @Override
+ public String getName() {
+ return "Android Type Move";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ return new RefactoringStatus();
+ }
+
+ @Override
+ protected boolean initialize(Object element) {
+ if (element instanceof IType) {
+ IType type = (IType) element;
+ IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
+ mProject = javaProject.getProject();
+ IResource manifestResource = mProject.findMember(AdtConstants.WS_SEP
+ + SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (manifestResource == null || !manifestResource.exists()
+ || !(manifestResource instanceof IFile)) {
+ RefactoringUtil.logInfo("Invalid or missing the "
+ + SdkConstants.FN_ANDROID_MANIFEST_XML + " in the " + mProject.getName()
+ + " project.");
+ return false;
+ }
+ mManifestFile = (IFile) manifestResource;
+ ManifestData manifestData;
+ manifestData = AndroidManifestHelper.parseForData(mManifestFile);
+ if (manifestData == null) {
+ return false;
+ }
+ mAppPackage = manifestData.getPackage();
+ mOldFqcn = type.getFullyQualifiedName();
+ Object destination = getArguments().getDestination();
+ if (destination instanceof IPackageFragment) {
+ IPackageFragment packageFragment = (IPackageFragment) destination;
+ mNewFqcn = packageFragment.getElementName() + "." + type.getElementName();
+ } else if (destination instanceof IResource) {
+ try {
+ IPackageFragment[] fragments = javaProject.getPackageFragments();
+ for (IPackageFragment fragment : fragments) {
+ IResource resource = fragment.getResource();
+ if (resource.equals(destination)) {
+ mNewFqcn = fragment.getElementName() + '.' + type.getElementName();
+ break;
+ }
+ }
+ } catch (JavaModelException e) {
+ // pass
+ }
+ }
+ return mOldFqcn != null && mNewFqcn != null;
+ }
+
+ return false;
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (pm.isCanceled()) {
+ return null;
+ }
+ if (!getArguments().getUpdateReferences()) {
+ return null;
+ }
+ CompositeChange result = new CompositeChange(getName());
+ result.markAsSynthetic();
+
+ addManifestFileChanges(result);
+
+ // Update layout files; we don't just need to react to custom view
+ // changes, we need to update fragment references and even tool:context activity
+ // references
+ addLayoutFileChanges(mProject, result);
+
+ // Also update in dependent projects
+ ProjectState projectState = Sdk.getProjectState(mProject);
+ if (projectState != null) {
+ Collection<ProjectState> parentProjects = projectState.getFullParentProjects();
+ for (ProjectState parentProject : parentProjects) {
+ IProject project = parentProject.getProject();
+ addLayoutFileChanges(project, result);
+ }
+ }
+
+ return (result.getChildren().length == 0) ? null : result;
+ }
+
+ private void addManifestFileChanges(CompositeChange result) {
+ addXmlFileChanges(mManifestFile, result, true);
+ }
+
+ private void addLayoutFileChanges(IProject project, CompositeChange result) {
+ try {
+ // Update references in XML resource files
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ IResource[] folders = resFolder.members();
+ for (IResource folder : folders) {
+ String folderName = folder.getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ if (folderType != ResourceFolderType.LAYOUT) {
+ continue;
+ }
+ if (!(folder instanceof IFolder)) {
+ continue;
+ }
+ IResource[] files = ((IFolder) folder).members();
+ for (int i = 0; i < files.length; i++) {
+ IResource member = files[i];
+ if ((member instanceof IFile) && member.exists()) {
+ IFile file = (IFile) member;
+ String fileName = member.getName();
+
+ if (SdkUtils.endsWith(fileName, DOT_XML)) {
+ addXmlFileChanges(file, result, false);
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ RefactoringUtil.log(e);
+ }
+ }
+
+ private boolean addXmlFileChanges(IFile file, CompositeChange changes, boolean isManifest) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ try {
+ model = modelManager.getExistingModelForRead(file);
+ if (model == null) {
+ model = modelManager.getModelForRead(file);
+ }
+ if (model != null) {
+ IStructuredDocument document = model.getStructuredDocument();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Element root = domModel.getDocument().getDocumentElement();
+ if (root != null) {
+ List<TextEdit> edits = new ArrayList<TextEdit>();
+ if (isManifest) {
+ addManifestReplacements(edits, root, document);
+ } else {
+ addLayoutReplacements(edits, root, document);
+ }
+ if (!edits.isEmpty()) {
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()]));
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ change.setTextType(EXT_XML);
+ change.setEdit(rootEdit);
+ changes.add(change);
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+
+ return false;
+ }
+
+ private void addLayoutReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ String tag = element.getTagName();
+ if (tag.equals(mOldFqcn)) {
+ int start = RefactoringUtil.getTagNameRangeStart(element, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ } else if (tag.equals(VIEW_TAG)) {
+ Attr classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ } else if (tag.equals(VIEW_FRAGMENT)) {
+ Attr classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode == null) {
+ classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ }
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) {
+ Attr classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT);
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addLayoutReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+
+ private void addManifestReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ if (!RefactoringUtil.isManifestClassAttribute(attr)) {
+ continue;
+ }
+
+ String value = attr.getValue();
+ if (value.equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ } else if (value.startsWith(".")) { //$NON-NLS-1$
+ String fqcn = mAppPackage + value;
+ if (fqcn.equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + value.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addManifestReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java
new file mode 100644
index 0000000..7843ab3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/AndroidTypeRenameParticipant.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_CLASS;
+import static com.android.SdkConstants.ATTR_CONTEXT;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.CLASS_VIEW;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.TOOLS_URI;
+import static com.android.SdkConstants.VIEW_FRAGMENT;
+import static com.android.SdkConstants.VIEW_TAG;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.ide.common.xml.ManifestData;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.core.ITypeHierarchy;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameCompilationUnitProcessor;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor;
+import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A participant to participate in refactorings that rename a type in an Android project.
+ * The class updates android manifest and the layout file
+ * The user can suppress refactoring by disabling the "Update references" checkbox.
+ * <p>
+ * Rename participants are registered via the extension point <code>
+ * org.eclipse.ltk.core.refactoring.renameParticipants</code>.
+ * Extensions to this extension point must therefore extend
+ * <code>org.eclipse.ltk.core.refactoring.participants.RenameParticipant</code>.
+ */
+@SuppressWarnings("restriction")
+public class AndroidTypeRenameParticipant extends RenameParticipant {
+ private IProject mProject;
+ private IFile mManifestFile;
+ private String mOldFqcn;
+ private String mNewFqcn;
+ private String mOldSimpleName;
+ private String mNewSimpleName;
+ private String mOldDottedName;
+ private String mNewDottedName;
+ private boolean mIsCustomView;
+
+ /**
+ * Set while we are creating an embedded Java refactoring. This could cause a recursive
+ * invocation of the XML renaming refactoring to react to the field, so this is flag
+ * during the call to the Java processor, and is used to ignore requests for adding in
+ * field reactions during that time.
+ */
+ private static boolean sIgnore;
+
+ @Override
+ public String getName() {
+ return "Android Type Rename";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ return new RefactoringStatus();
+ }
+
+ @Override
+ protected boolean initialize(Object element) {
+ if (sIgnore) {
+ return false;
+ }
+
+ if (element instanceof IType) {
+ IType type = (IType) element;
+ IJavaProject javaProject = (IJavaProject) type.getAncestor(IJavaElement.JAVA_PROJECT);
+ mProject = javaProject.getProject();
+ IResource manifestResource = mProject.findMember(AdtConstants.WS_SEP
+ + SdkConstants.FN_ANDROID_MANIFEST_XML);
+
+ if (manifestResource == null || !manifestResource.exists()
+ || !(manifestResource instanceof IFile)) {
+ RefactoringUtil.logInfo(
+ String.format("Invalid or missing file %1$s in project %2$s",
+ SdkConstants.FN_ANDROID_MANIFEST_XML,
+ mProject.getName()));
+ return false;
+ }
+
+ try {
+ IType classView = javaProject.findType(CLASS_VIEW);
+ if (classView != null) {
+ ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor());
+ if (hierarchy.contains(classView)) {
+ mIsCustomView = true;
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ mManifestFile = (IFile) manifestResource;
+ ManifestData manifestData;
+ manifestData = AndroidManifestHelper.parseForData(mManifestFile);
+ if (manifestData == null) {
+ return false;
+ }
+ mOldSimpleName = type.getElementName();
+ mOldDottedName = '.' + mOldSimpleName;
+ mOldFqcn = type.getFullyQualifiedName();
+ String packageName = type.getPackageFragment().getElementName();
+ mNewSimpleName = getArguments().getNewName();
+ mNewDottedName = '.' + mNewSimpleName;
+ if (packageName != null) {
+ mNewFqcn = packageName + mNewDottedName;
+ } else {
+ mNewFqcn = mNewSimpleName;
+ }
+ if (mOldFqcn == null || mNewFqcn == null) {
+ return false;
+ }
+ if (!RefactoringUtil.isRefactorAppPackage() && mNewFqcn.indexOf('.') == -1) {
+ mNewFqcn = packageName + mNewDottedName;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ if (pm.isCanceled()) {
+ return null;
+ }
+
+ // Only propose this refactoring if the "Update References" checkbox is set.
+ if (!getArguments().getUpdateReferences()) {
+ return null;
+ }
+
+ RefactoringProcessor p = getProcessor();
+ if (p instanceof RenameCompilationUnitProcessor) {
+ RenameTypeProcessor rtp =
+ ((RenameCompilationUnitProcessor) p).getRenameTypeProcessor();
+ if (rtp != null) {
+ String pattern = rtp.getFilePatterns();
+ boolean updQualf = rtp.getUpdateQualifiedNames();
+ if (updQualf && pattern != null && pattern.contains("xml")) { //$NON-NLS-1$
+ // Do not propose this refactoring if the
+ // "Update fully qualified names in non-Java files" option is
+ // checked and the file patterns mention XML. [c.f. SDK bug 21589]
+ return null;
+ }
+ }
+ }
+
+ CompositeChange result = new CompositeChange(getName());
+
+ // Only show the children in the refactoring preview dialog
+ result.markAsSynthetic();
+
+ addManifestFileChanges(mManifestFile, result);
+ addLayoutFileChanges(mProject, result);
+ addJavaChanges(mProject, result, pm);
+
+ // Also update in dependent projects
+ // TODO: Also do the Java elements, if they are in Jar files, since the library
+ // projects do this (and the JDT refactoring does not include them)
+ ProjectState projectState = Sdk.getProjectState(mProject);
+ if (projectState != null) {
+ Collection<ProjectState> parentProjects = projectState.getFullParentProjects();
+ for (ProjectState parentProject : parentProjects) {
+ IProject project = parentProject.getProject();
+ IResource manifestResource = project.findMember(AdtConstants.WS_SEP
+ + SdkConstants.FN_ANDROID_MANIFEST_XML);
+ if (manifestResource != null && manifestResource.exists()
+ && manifestResource instanceof IFile) {
+ addManifestFileChanges((IFile) manifestResource, result);
+ }
+ addLayoutFileChanges(project, result);
+ addJavaChanges(project, result, pm);
+ }
+ }
+
+ // Look for the field change on the R.java class; it's a derived file
+ // and will generate file modified manually warnings. Disable it.
+ RenameResourceParticipant.disableRClassChanges(result);
+
+ return (result.getChildren().length == 0) ? null : result;
+ }
+
+ private void addJavaChanges(IProject project, CompositeChange result, IProgressMonitor monitor) {
+ if (!mIsCustomView) {
+ return;
+ }
+
+ // Also rename styleables, if any
+ try {
+ // Find R class
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ ManifestInfo info = ManifestInfo.get(project);
+ info.getPackage();
+ String rFqcn = info.getPackage() + '.' + R_CLASS;
+ IType styleable = javaProject.findType(rFqcn + '.' + ResourceType.STYLEABLE.getName());
+ if (styleable != null) {
+ IField[] fields = styleable.getFields();
+ CompositeChange fieldChanges = null;
+ for (IField field : fields) {
+ String name = field.getElementName();
+ if (name.equals(mOldSimpleName) || name.startsWith(mOldSimpleName)
+ && name.length() > mOldSimpleName.length()
+ && name.charAt(mOldSimpleName.length()) == '_') {
+ // Rename styleable fields
+ String newName = name.equals(mOldSimpleName) ? mNewSimpleName :
+ mNewSimpleName + name.substring(mOldSimpleName.length());
+ RenameRefactoring refactoring =
+ RenameResourceParticipant.createFieldRefactoring(field,
+ newName, true);
+
+ try {
+ sIgnore = true;
+ RefactoringStatus status = refactoring.checkAllConditions(monitor);
+ if (status != null && !status.hasError()) {
+ Change fieldChange = refactoring.createChange(monitor);
+ if (fieldChange != null) {
+ if (fieldChanges == null) {
+ fieldChanges = new CompositeChange(
+ "Update custom view styleable fields");
+ // Disable these changes. They sometimes end up
+ // editing the wrong offsets. It looks like Eclipse
+ // doesn't ensure that after applying each change it
+ // also adjusts the other field offsets. I poked around
+ // and couldn't find a way to do this properly, but
+ // at least by listing the diffs here it shows what should
+ // be done.
+ fieldChanges.setEnabled(false);
+ }
+ // Disable change: see comment above.
+ fieldChange.setEnabled(false);
+ fieldChanges.add(fieldChange);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ sIgnore = false;
+ }
+ }
+ }
+ if (fieldChanges != null) {
+ result.add(fieldChanges);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ private void addManifestFileChanges(IFile manifestFile, CompositeChange result) {
+ addXmlFileChanges(manifestFile, result, null);
+ }
+
+ private void addLayoutFileChanges(IProject project, CompositeChange result) {
+ try {
+ // Update references in XML resource files
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ IResource[] folders = resFolder.members();
+ for (IResource folder : folders) {
+ String folderName = folder.getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ if (folderType != ResourceFolderType.LAYOUT &&
+ folderType != ResourceFolderType.VALUES) {
+ continue;
+ }
+ if (!(folder instanceof IFolder)) {
+ continue;
+ }
+ IResource[] files = ((IFolder) folder).members();
+ for (int i = 0; i < files.length; i++) {
+ IResource member = files[i];
+ if ((member instanceof IFile) && member.exists()) {
+ IFile file = (IFile) member;
+ String fileName = member.getName();
+
+ if (SdkUtils.endsWith(fileName, DOT_XML)) {
+ addXmlFileChanges(file, result, folderType);
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ RefactoringUtil.log(e);
+ }
+ }
+
+ private boolean addXmlFileChanges(IFile file, CompositeChange changes,
+ ResourceFolderType folderType) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ try {
+ model = modelManager.getExistingModelForRead(file);
+ if (model == null) {
+ model = modelManager.getModelForRead(file);
+ }
+ if (model != null) {
+ IStructuredDocument document = model.getStructuredDocument();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Element root = domModel.getDocument().getDocumentElement();
+ if (root != null) {
+ List<TextEdit> edits = new ArrayList<TextEdit>();
+ if (folderType == null) {
+ assert file.getName().equals(ANDROID_MANIFEST_XML);
+ addManifestReplacements(edits, root, document);
+ } else if (folderType == ResourceFolderType.VALUES) {
+ addValueReplacements(edits, root, document);
+ } else {
+ assert folderType == ResourceFolderType.LAYOUT;
+ addLayoutReplacements(edits, root, document);
+ }
+ if (!edits.isEmpty()) {
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()]));
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ change.setTextType(EXT_XML);
+ change.setEdit(rootEdit);
+ changes.add(change);
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+
+ return false;
+ }
+
+ private void addLayoutReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ String tag = element.getTagName();
+ if (tag.equals(mOldFqcn)) {
+ int start = RefactoringUtil.getTagNameRangeStart(element, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ } else if (tag.equals(VIEW_TAG)) {
+ // TODO: Handle inner classes ($ vs .) ?
+ Attr classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ } else if (tag.equals(VIEW_FRAGMENT)) {
+ Attr classNode = element.getAttributeNode(ATTR_CLASS);
+ if (classNode == null) {
+ classNode = element.getAttributeNodeNS(ANDROID_URI, ATTR_NAME);
+ }
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ }
+ } else if (element.hasAttributeNS(TOOLS_URI, ATTR_CONTEXT)) {
+ Attr classNode = element.getAttributeNodeNS(TOOLS_URI, ATTR_CONTEXT);
+ if (classNode != null && classNode.getValue().equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ } else if (classNode != null && classNode.getValue().equals(mOldDottedName)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(classNode, document);
+ if (start != -1) {
+ int end = start + mOldDottedName.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewDottedName));
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addLayoutReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+
+ private void addValueReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element root,
+ @NonNull IStructuredDocument document) {
+ // Look for styleable renames for custom views
+ String declareStyleable = ResourceType.DECLARE_STYLEABLE.getName();
+ List<Element> topLevel = DomUtilities.getChildren(root);
+ for (Element element : topLevel) {
+ String tag = element.getTagName();
+ if (declareStyleable.equals(tag)) {
+ Attr nameNode = element.getAttributeNode(ATTR_NAME);
+ if (nameNode != null && mOldSimpleName.equals(nameNode.getValue())) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(nameNode, document);
+ if (start != -1) {
+ int end = start + mOldSimpleName.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewSimpleName));
+ }
+ }
+ }
+ }
+ }
+
+ private void addManifestReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document) {
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ if (!RefactoringUtil.isManifestClassAttribute(attr)) {
+ continue;
+ }
+
+ String value = attr.getValue();
+ if (value.equals(mOldFqcn)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + mOldFqcn.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewFqcn));
+ }
+ } else if (value.equals(mOldDottedName)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + mOldDottedName.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewDottedName));
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addManifestReplacements(edits, (Element) child, document);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/FixImportsJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java
index 3b63acf..552e6a8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactoring/core/FixImportsJob.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/FixImportsJob.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.ide.eclipse.adt.internal.refactoring.core;
+package com.android.ide.eclipse.adt.internal.refactorings.core;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java
new file mode 100644
index 0000000..04ebcfa
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RefactoringUtil.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_BACKUP_AGENT;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_MANAGE_SPACE_ACTIVITY;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_PARENT_ACTIVITY_NAME;
+import static com.android.xml.AndroidManifest.ATTRIBUTE_TARGET_ACTIVITY;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.xml.AndroidManifest;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+/**
+ * The utility class for android refactoring
+ *
+ */
+@SuppressWarnings("restriction")
+public class RefactoringUtil {
+
+ private static boolean sRefactorAppPackage = false;
+
+ /**
+ * Releases SSE read model; saves SSE model if exists edit model
+ * Called in dispose method of refactoring change classes
+ *
+ * @param model the SSE model
+ * @param document the document
+ */
+ public static void fixModel(IStructuredModel model, IDocument document) {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ model = null;
+ if (document == null) {
+ return;
+ }
+ try {
+ model = StructuredModelManager.getModelManager().getExistingModelForEdit(document);
+ if (model != null) {
+ model.save();
+ }
+ } catch (UnsupportedEncodingException e1) {
+ // ignore
+ } catch (IOException e1) {
+ // ignore
+ } catch (CoreException e1) {
+ // ignore
+ } finally {
+ if (model != null) {
+ model.releaseFromEdit();
+ }
+ }
+ }
+
+ /**
+ * Logs the info message
+ *
+ * @param message the message
+ */
+ public static void logInfo(String message) {
+ AdtPlugin.log(IStatus.INFO, AdtPlugin.PLUGIN_ID, message);
+ }
+
+ /**
+ * Logs the the exception
+ *
+ * @param e the exception
+ */
+ public static void log(Throwable e) {
+ AdtPlugin.log(e, e.getMessage());
+ }
+
+ /**
+ * @return true if Rename/Move package needs to change the application package
+ * default is false
+ *
+ */
+ public static boolean isRefactorAppPackage() {
+ return sRefactorAppPackage;
+ }
+
+ /**
+ * @param refactorAppPackage true if Rename/Move package needs to change the application package
+ */
+ public static void setRefactorAppPackage(boolean refactorAppPackage) {
+ RefactoringUtil.sRefactorAppPackage = refactorAppPackage;
+ }
+
+ /**
+ * Returns the range of the attribute value in the given document
+ *
+ * @param attr the attribute to look up
+ * @param document the document containing the attribute
+ * @return the range of the value text, not including quotes, in the document
+ */
+ public static int getAttributeValueRangeStart(
+ @NonNull Attr attr,
+ @NonNull IDocument document) {
+ IndexedRegion region = (IndexedRegion) attr;
+ int potentialStart = attr.getName().length() + 2; // + 2: add ="
+ String text;
+ try {
+ text = document.get(region.getStartOffset(),
+ region.getEndOffset() - region.getStartOffset());
+ } catch (BadLocationException e) {
+ return -1;
+ }
+ String value = attr.getValue();
+ int index = text.indexOf(value, potentialStart);
+ if (index != -1) {
+ return region.getStartOffset() + index;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the start of the tag name of the given element
+ *
+ * @param element the element to look up
+ * @param document the document containing the attribute
+ * @return the index of the start tag in the document
+ */
+ public static int getTagNameRangeStart(
+ @NonNull Element element,
+ @NonNull IDocument document) {
+ IndexedRegion region = (IndexedRegion) element;
+ int potentialStart = 1; // add '<'
+ String text;
+ try {
+ text = document.get(region.getStartOffset(),
+ region.getEndOffset() - region.getStartOffset());
+ } catch (BadLocationException e) {
+ return -1;
+ }
+ int index = text.indexOf(element.getTagName(), potentialStart);
+ if (index != -1) {
+ return region.getStartOffset() + index;
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns whether the given manifest attribute should be considered to describe
+ * a class name. These will be eligible for refactoring when classes are renamed
+ * or moved.
+ *
+ * @param attribute the manifest attribute
+ * @return true if this attribute can describe a class
+ */
+ public static boolean isManifestClassAttribute(@NonNull Attr attribute) {
+ return isManifestClassAttribute(
+ attribute.getOwnerElement().getTagName(),
+ attribute.getNamespaceURI(),
+ attribute.getLocalName());
+ }
+
+ /**
+ * Returns whether the given manifest attribute should be considered to describe
+ * a class name. These will be eligible for refactoring when classes are renamed
+ * or moved.
+ *
+ * @param tag the tag, if known
+ * @param uri the attribute namespace, if any
+ * @param name the attribute local name, if any
+ * @return true if this attribute can describe a class
+ */
+ public static boolean isManifestClassAttribute(
+ @Nullable String tag,
+ @Nullable String uri,
+ @Nullable String name) {
+ if (name == null) {
+ return false;
+ }
+
+ if ((name.equals(ATTR_NAME)
+ && (AndroidManifest.NODE_ACTIVITY.equals(tag)
+ || AndroidManifest.NODE_APPLICATION.equals(tag)
+ || AndroidManifest.NODE_INSTRUMENTATION.equals(tag)
+ || AndroidManifest.NODE_PROVIDER.equals(tag)
+ || AndroidManifest.NODE_SERVICE.equals(tag)
+ || AndroidManifest.NODE_RECEIVER.equals(tag)))
+ || name.equals(ATTRIBUTE_TARGET_ACTIVITY)
+ || name.equals(ATTRIBUTE_MANAGE_SPACE_ACTIVITY)
+ || name.equals(ATTRIBUTE_BACKUP_AGENT)
+ || name.equals(ATTRIBUTE_PARENT_ACTIVITY_NAME)) {
+ return ANDROID_URI.equals(uri);
+ }
+
+ return false;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java
new file mode 100644
index 0000000..6779fd3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourcePage.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.R_CLASS;
+
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.resources.ResourceType;
+
+import org.eclipse.jdt.internal.ui.refactoring.TextInputWizardPage;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.util.Set;
+
+@SuppressWarnings("restriction") // JDT refactoring UI
+class RenameResourcePage extends TextInputWizardPage implements SelectionListener {
+ private Label mXmlLabel;
+ private Label mJavaLabel;
+ private Button mUpdateReferences;
+ private boolean mCanClear;
+ private ResourceType mType;
+ private ResourceNameValidator mValidator;
+
+ /**
+ * Create the wizard.
+ * @param type the type of the resource to be renamed
+ * @param initial initial renamed value
+ * @param canClear whether the dialog should allow clearing the field
+ */
+ public RenameResourcePage(ResourceType type, String initial, boolean canClear) {
+ super(type.getName(), true, initial);
+ mType = type;
+ mCanClear = canClear;
+
+ mValidator = ResourceNameValidator.create(false /*allowXmlExtension*/,
+ (Set<String>) null, mType);
+ }
+
+ @SuppressWarnings("unused") // SWT constructors aren't really unused, they have side effects
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ initializeDialogUnits(container);
+ container.setLayout(new GridLayout(2, false));
+ Label nameLabel = new Label(container, SWT.NONE);
+ nameLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ nameLabel.setText("New Name:");
+ Text text = super.createTextInputField(container);
+ text.selectAll();
+ text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ Label xmlLabel = new Label(container, SWT.NONE);
+ xmlLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ xmlLabel.setText("XML:");
+ mXmlLabel = new Label(container, SWT.NONE);
+ mXmlLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ Label javaLabel = new Label(container, SWT.NONE);
+ javaLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ javaLabel.setText("Java:");
+ mJavaLabel = new Label(container, SWT.NONE);
+ mJavaLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+ mUpdateReferences = new Button(container, SWT.CHECK);
+ mUpdateReferences.setSelection(true);
+ mUpdateReferences.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mUpdateReferences.setText("Update References");
+ mUpdateReferences.addSelectionListener(this);
+
+ Dialog.applyDialogFont(container);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ RenameResourceProcessor processor = getProcessor();
+ String newName = processor.getNewName();
+ if (newName != null && newName.length() > 0
+ && !newName.equals(getInitialValue())) {
+ Text textField = getTextField();
+ textField.setText(newName);
+ textField.setSelection(0, newName.length());
+ }
+ }
+
+ super.setVisible(visible);
+ }
+
+ @Override
+ protected RefactoringStatus validateTextField(String newName) {
+ if (newName.isEmpty() && isEmptyInputValid()) {
+ getProcessor().setNewName("");
+ return RefactoringStatus.createWarningStatus(
+ "The resource definition will be deleted");
+ }
+
+ String error = mValidator.isValid(newName);
+ if (error != null) {
+ return RefactoringStatus.createErrorStatus(error);
+ }
+
+ RenameResourceProcessor processor = getProcessor();
+ processor.setNewName(newName);
+ return processor.checkNewName(newName);
+ }
+
+ private RenameResourceProcessor getProcessor() {
+ RenameRefactoring refactoring = (RenameRefactoring) getRefactoring();
+ return (RenameResourceProcessor) refactoring.getProcessor();
+ }
+
+ @Override
+ protected boolean isEmptyInputValid() {
+ return mCanClear;
+ }
+
+ @Override
+ protected boolean isInitialInputValid() {
+ RenameResourceProcessor processor = getProcessor();
+ return processor.getNewName() != null
+ && !processor.getNewName().equals(processor.getCurrentName());
+ }
+
+ @Override
+ protected void textModified(String text) {
+ super.textModified(text);
+ if (mXmlLabel != null && mJavaLabel != null) {
+ String xml = PREFIX_RESOURCE_REF + mType.getName() + '/' + text;
+ String java = R_CLASS + '.' + mType.getName() + '.' + text;
+ if (text.isEmpty()) {
+ xml = java = "";
+ }
+ mXmlLabel.setText(xml);
+ mJavaLabel.setText(java);
+ }
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == mUpdateReferences) {
+ RenameResourceProcessor processor = getProcessor();
+ boolean update = mUpdateReferences.getSelection();
+ processor.setUpdateReferences(update);
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java
new file mode 100644
index 0000000..438e822
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceParticipant.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_URI;
+import static com.android.SdkConstants.ATTR_ID;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.DOT_XML;
+import static com.android.SdkConstants.EXT_XML;
+import static com.android.SdkConstants.FD_RES;
+import static com.android.SdkConstants.FN_RESOURCE_CLASS;
+import static com.android.SdkConstants.NEW_ID_PREFIX;
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+import static com.android.SdkConstants.PREFIX_THEME_REF;
+import static com.android.SdkConstants.R_CLASS;
+import static com.android.SdkConstants.TAG_ITEM;
+import static com.android.SdkConstants.TOOLS_URI;
+
+import com.android.SdkConstants;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.android.utils.SdkUtils;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jdt.core.IField;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameFieldProcessor;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextChange;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.wst.sse.core.StructuredModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
+import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A rename participant handling renames of resources (such as R.id.foo and R.layout.bar).
+ * This reacts to refactorings of fields in the R inner classes (such as R.id), and updates
+ * the XML files as appropriate; renaming .xml files, updating XML attributes, resource
+ * references in style declarations, and so on.
+ */
+@SuppressWarnings("restriction") // WTP API
+public class RenameResourceParticipant extends RenameParticipant {
+ /** The project we're refactoring in */
+ private @NonNull IProject mProject;
+
+ /** The type of the resource we're refactoring, such as {@link ResourceType#ID} */
+ private @NonNull ResourceType mType;
+ /**
+ * The type of the resource folder we're refactoring in, such as
+ * {@link ResourceFolderType#VALUES}. When refactoring non value files, we need to
+ * rename the files as well.
+ */
+ private @NonNull ResourceFolderType mFolderType;
+
+ /** The previous name of the resource */
+ private @NonNull String mOldName;
+
+ /** The new name of the resource */
+ private @NonNull String mNewName;
+
+ /** Whether references to the resource should be updated */
+ private boolean mUpdateReferences;
+
+ /** A match pattern to look for in XML, such as {@code @attr/foo} */
+ private @NonNull String mXmlMatch1;
+
+ /** A match pattern to look for in XML, such as {@code ?attr/foo} */
+ private @Nullable String mXmlMatch2;
+
+ /** A match pattern to look for in XML, such as {@code ?foo} */
+ private @Nullable String mXmlMatch3;
+
+ /** The value to replace a reference to {@link #mXmlMatch1} with, such as {@code @attr/bar} */
+ private @NonNull String mXmlNewValue1;
+
+ /** The value to replace a reference to {@link #mXmlMatch2} with, such as {@code ?attr/bar} */
+ private @Nullable String mXmlNewValue2;
+
+ /** The value to replace a reference to {@link #mXmlMatch3} with, such as {@code ?bar} */
+ private @Nullable String mXmlNewValue3;
+
+ /**
+ * If non null, this refactoring was initiated as a file rename of an XML file (and if
+ * null, we are just reacting to a Java field rename)
+ */
+ private IFile mRenamedFile;
+
+ /**
+ * If renaming a field, we need to create an embedded field refactoring to update the
+ * Java sources referring to the corresponding R class field. This is stored as an
+ * instance such that we can have it participate in both the condition check methods
+ * as well as the {@link #createChange(IProgressMonitor)} refactoring operation.
+ */
+ private RenameRefactoring mFieldRefactoring;
+
+ /**
+ * Set while we are creating an embedded Java refactoring. This could cause a recursive
+ * invocation of the XML renaming refactoring to react to the field, so this is flag
+ * during the call to the Java processor, and is used to ignore requests for adding in
+ * field reactions during that time.
+ */
+ private static boolean sIgnore;
+
+ /**
+ * Creates a new {@linkplain RenameResourceParticipant}
+ */
+ public RenameResourceParticipant() {
+ }
+
+ @Override
+ public String getName() {
+ return "Android Rename Field Participant";
+ }
+
+ @Override
+ protected boolean initialize(Object element) {
+ if (sIgnore) {
+ return false;
+ }
+
+ if (element instanceof IField) {
+ IField field = (IField) element;
+ IType declaringType = field.getDeclaringType();
+ if (declaringType != null) {
+ if (R_CLASS.equals(declaringType.getParent().getElementName())) {
+ String typeName = declaringType.getElementName();
+ mType = ResourceType.getEnum(typeName);
+ if (mType != null) {
+ mUpdateReferences = getArguments().getUpdateReferences();
+ mFolderType = AdtUtils.getFolderTypeFor(mType);
+ IJavaProject javaProject = (IJavaProject) field.getAncestor(
+ IJavaElement.JAVA_PROJECT);
+ mProject = javaProject.getProject();
+ mOldName = field.getElementName();
+ mNewName = getArguments().getNewName();
+ mFieldRefactoring = null;
+ mRenamedFile = null;
+ createXmlSearchPatterns();
+ return true;
+ }
+ }
+ }
+
+ return false;
+ } else if (element instanceof IFile) {
+ IFile file = (IFile) element;
+ mProject = file.getProject();
+ if (BaseProjectHelper.isAndroidProject(mProject)) {
+ IPath path = file.getFullPath();
+ int segments = path.segmentCount();
+ if (segments == 4 && path.segment(1).equals(FD_RES)) {
+ String parentName = file.getParent().getName();
+ mFolderType = ResourceFolderType.getFolderType(parentName);
+ if (mFolderType != null && mFolderType != ResourceFolderType.VALUES) {
+ mType = AdtUtils.getResourceTypeFor(mFolderType);
+ if (mType != null) {
+ mUpdateReferences = getArguments().getUpdateReferences();
+ mProject = file.getProject();
+ mOldName = AdtUtils.stripAllExtensions(file.getName());
+ mNewName = AdtUtils.stripAllExtensions(getArguments().getNewName());
+ mRenamedFile = file;
+ createXmlSearchPatterns();
+
+ mFieldRefactoring = null;
+ IField field = getResourceField(mProject, mType, mOldName);
+ if (field != null) {
+ mFieldRefactoring = createFieldRefactoring(field);
+ } else {
+ // no corresponding field; aapt has not run yet. Perhaps user has
+ // turned off auto build.
+ mFieldRefactoring = null;
+ }
+
+ return true;
+ }
+ }
+ }
+ }
+ } else if (element instanceof String) {
+ String uri = (String) element;
+ if (uri.startsWith(PREFIX_RESOURCE_REF) && !uri.startsWith(ANDROID_PREFIX)) {
+ RenameResourceProcessor processor = (RenameResourceProcessor) getProcessor();
+ mProject = processor.getProject();
+ mType = processor.getType();
+ mFolderType = AdtUtils.getFolderTypeFor(mType);
+ mOldName = processor.getCurrentName();
+ mNewName = processor.getNewName();
+ assert uri.endsWith(mOldName) && uri.contains(mType.getName()) : uri;
+ mUpdateReferences = getArguments().getUpdateReferences();
+ if (mNewName.isEmpty()) {
+ mUpdateReferences = false;
+ }
+ mRenamedFile = null;
+ createXmlSearchPatterns();
+ mFieldRefactoring = null;
+ if (!mNewName.isEmpty()) {
+ IField field = getResourceField(mProject, mType, mOldName);
+ if (field != null) {
+ mFieldRefactoring = createFieldRefactoring(field);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Create nested Java refactoring which updates the R field references, if applicable */
+ private RenameRefactoring createFieldRefactoring(IField field) {
+ return createFieldRefactoring(field, mNewName, mUpdateReferences);
+ }
+
+ /**
+ * Create nested Java refactoring which updates the R field references, if
+ * applicable
+ *
+ * @param field the field to be refactored
+ * @param newName the new name
+ * @param updateReferences whether references should be updated
+ * @return a new rename refactoring
+ */
+ public static RenameRefactoring createFieldRefactoring(
+ @NonNull IField field,
+ @NonNull String newName,
+ boolean updateReferences) {
+ RenameFieldProcessor processor = new RenameFieldProcessor(field);
+ processor.setRenameGetter(false);
+ processor.setRenameSetter(false);
+ RenameRefactoring refactoring = new RenameRefactoring(processor);
+ processor.setUpdateReferences(updateReferences);
+ processor.setUpdateTextualMatches(false);
+ processor.setNewElementName(newName);
+ try {
+ if (refactoring.isApplicable()) {
+ return refactoring;
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ private void createXmlSearchPatterns() {
+ // Set up search strings for the attribute iterator. This will
+ // identify string matches for mXmlMatch1, 2 and 3, and when matched,
+ // will add a replacement edit for mXmlNewValue1, 2, or 3.
+ mXmlMatch2 = null;
+ mXmlNewValue2 = null;
+ mXmlMatch3 = null;
+ mXmlNewValue3 = null;
+
+ String typeName = mType.getName();
+ if (mUpdateReferences) {
+ mXmlMatch1 = PREFIX_RESOURCE_REF + typeName + '/' + mOldName;
+ mXmlNewValue1 = PREFIX_RESOURCE_REF + typeName + '/' + mNewName;
+ if (mType == ResourceType.ID) {
+ mXmlMatch2 = NEW_ID_PREFIX + mOldName;
+ mXmlNewValue2 = NEW_ID_PREFIX + mNewName;
+ } else if (mType == ResourceType.ATTR) {
+ // When renaming @attr/foo, also edit ?attr/foo
+ mXmlMatch2 = PREFIX_THEME_REF + typeName + '/' + mOldName;
+ mXmlNewValue2 = PREFIX_THEME_REF + typeName + '/' + mNewName;
+ // as well as ?foo
+ mXmlMatch3 = PREFIX_THEME_REF + mOldName;
+ mXmlNewValue3 = PREFIX_THEME_REF + mNewName;
+ }
+ } else if (mType == ResourceType.ID) {
+ mXmlMatch1 = NEW_ID_PREFIX + mOldName;
+ mXmlNewValue1 = NEW_ID_PREFIX + mNewName;
+ }
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ if (mRenamedFile != null && getArguments().getNewName().indexOf('.') == -1
+ && mRenamedFile.getName().indexOf('.') != -1) {
+ return RefactoringStatus.createErrorStatus(
+ String.format("You must include the file extension (%1$s?)",
+ mRenamedFile.getName().substring(mRenamedFile.getName().indexOf('.'))));
+ }
+
+ // Ensure that the new name is valid
+ if (mNewName != null && !mNewName.isEmpty()) {
+ ResourceNameValidator validator = ResourceNameValidator.create(false, mProject, mType);
+ String error = validator.isValid(mNewName);
+ if (error != null) {
+ return RefactoringStatus.createErrorStatus(error);
+ }
+ }
+
+ if (mFieldRefactoring != null) {
+ try {
+ sIgnore = true;
+ return mFieldRefactoring.checkAllConditions(pm);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ sIgnore = false;
+ }
+ }
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor monitor) throws CoreException,
+ OperationCanceledException {
+ if (monitor.isCanceled()) {
+ return null;
+ }
+
+ CompositeChange result = new CompositeChange("Update resource references");
+
+ // Only show the children in the refactoring preview dialog
+ result.markAsSynthetic();
+
+ addResourceFileChanges(result, mProject, monitor);
+
+ // If renaming resources in a library project, also offer to rename references
+ // in including projects
+ if (mUpdateReferences) {
+ ProjectState projectState = Sdk.getProjectState(mProject);
+ if (projectState != null && projectState.isLibrary()) {
+ List<ProjectState> parentProjects = projectState.getParentProjects();
+ for (ProjectState state : parentProjects) {
+ IProject project = state.getProject();
+ CompositeChange nested = new CompositeChange(
+ String.format("Update references in %1$s", project.getName()));
+ addResourceFileChanges(nested, project, monitor);
+ if (nested.getChildren().length > 0) {
+ result.add(nested);
+ }
+ }
+ }
+ }
+
+ if (mFieldRefactoring != null) {
+ // We have to add in Java field refactoring
+ try {
+ sIgnore = true;
+ addJavaChanges(result, monitor);
+ } finally {
+ sIgnore = false;
+ }
+ } else {
+ // Disable field refactoring added by the default Java field rename handler
+ disableExistingResourceFileChange();
+ }
+
+ return (result.getChildren().length == 0) ? null : result;
+ }
+
+ /**
+ * Adds all changes to resource files (typically XML but also renaming drawable files
+ *
+ * @param project the Android project
+ * @param className the layout classes
+ */
+ private void addResourceFileChanges(
+ CompositeChange change,
+ IProject project,
+ IProgressMonitor monitor)
+ throws OperationCanceledException {
+ if (monitor.isCanceled()) {
+ return;
+ }
+
+ try {
+ // Update resource references in the manifest
+ IFile manifest = project.getFile(SdkConstants.ANDROID_MANIFEST_XML);
+ if (manifest != null) {
+ addResourceXmlChanges(manifest, change, null);
+ }
+
+ // Update references in XML resource files
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ IResource[] folders = resFolder.members();
+ for (IResource folder : folders) {
+ if (!(folder instanceof IFolder)) {
+ continue;
+ }
+ String folderName = folder.getName();
+ ResourceFolderType folderType = ResourceFolderType.getFolderType(folderName);
+ IResource[] files = ((IFolder) folder).members();
+ for (int i = 0; i < files.length; i++) {
+ IResource member = files[i];
+ if ((member instanceof IFile) && member.exists()) {
+ IFile file = (IFile) member;
+ String fileName = member.getName();
+
+ if (SdkUtils.endsWith(fileName, DOT_XML)) {
+ addResourceXmlChanges(file, change, folderType);
+ }
+
+ if ((mRenamedFile == null || !mRenamedFile.equals(file))
+ && fileName.startsWith(mOldName)
+ && fileName.length() > mOldName.length()
+ && fileName.charAt(mOldName.length()) == '.'
+ && mFolderType != ResourceFolderType.VALUES
+ && mFolderType == folderType) {
+ // Rename this file
+ String newFile = mNewName + fileName.substring(mOldName.length());
+ IPath path = file.getFullPath();
+ change.add(new RenameResourceChange(path, newFile));
+ }
+ }
+ }
+ }
+ } catch (CoreException e) {
+ RefactoringUtil.log(e);
+ }
+ }
+
+ private void addJavaChanges(CompositeChange result, IProgressMonitor monitor)
+ throws CoreException, OperationCanceledException {
+ if (monitor.isCanceled()) {
+ return;
+ }
+
+ RefactoringStatus status = mFieldRefactoring.checkAllConditions(monitor);
+ if (status != null && !status.hasError()) {
+ Change fieldChanges = mFieldRefactoring.createChange(monitor);
+ if (fieldChanges != null) {
+ result.add(fieldChanges);
+
+ // Look for the field change on the R.java class; it's a derived file
+ // and will generate file modified manually warnings. Disable it.
+ disableRClassChanges(fieldChanges);
+ }
+ }
+ }
+
+ private boolean addResourceXmlChanges(
+ IFile file,
+ CompositeChange changes,
+ ResourceFolderType folderType) {
+ IModelManager modelManager = StructuredModelManager.getModelManager();
+ IStructuredModel model = null;
+ try {
+ model = modelManager.getExistingModelForRead(file);
+ if (model == null) {
+ model = modelManager.getModelForRead(file);
+ }
+ if (model != null) {
+ IStructuredDocument document = model.getStructuredDocument();
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ Element root = domModel.getDocument().getDocumentElement();
+ if (root != null) {
+ List<TextEdit> edits = new ArrayList<TextEdit>();
+ addReplacements(edits, root, document, folderType);
+ if (!edits.isEmpty()) {
+ MultiTextEdit rootEdit = new MultiTextEdit();
+ rootEdit.addChildren(edits.toArray(new TextEdit[edits.size()]));
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ change.setTextType(EXT_XML);
+ change.setEdit(rootEdit);
+ changes.add(change);
+ }
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ } finally {
+ if (model != null) {
+ model.releaseFromRead();
+ }
+ }
+
+ return false;
+ }
+
+ private void addReplacements(
+ @NonNull List<TextEdit> edits,
+ @NonNull Element element,
+ @NonNull IStructuredDocument document,
+ @Nullable ResourceFolderType folderType) {
+ String tag = element.getTagName();
+ if (folderType == ResourceFolderType.VALUES) {
+ // Look for
+ // <item name="main_layout" type="layout">...</item>
+ // <item name="myid" type="id"/>
+ // <string name="mystring">...</string>
+ // etc
+ if (tag.equals(mType.getName())
+ || (tag.equals(TAG_ITEM)
+ && (mType == ResourceType.ID
+ || mType.getName().equals(element.getAttribute(ATTR_TYPE))))) {
+ Attr nameNode = element.getAttributeNode(ATTR_NAME);
+ if (nameNode != null && nameNode.getValue().equals(mOldName)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(nameNode, document);
+ if (start != -1) {
+ int end = start + mOldName.length();
+ edits.add(new ReplaceEdit(start, end - start, mNewName));
+ }
+ }
+ }
+ }
+
+ NamedNodeMap attributes = element.getAttributes();
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attr = (Attr) attributes.item(i);
+ String value = attr.getValue();
+
+ // If not updating references, only update XML matches that define the id
+ if (!mUpdateReferences && (!ATTR_ID.equals(attr.getLocalName()) ||
+ !ANDROID_URI.equals(attr.getNamespaceURI()))) {
+
+ if (TOOLS_URI.equals(attr.getNamespaceURI()) && value.equals(mXmlMatch1)) {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + mXmlMatch1.length();
+ edits.add(new ReplaceEdit(start, end - start, mXmlNewValue1));
+ }
+ }
+
+ continue;
+ }
+
+ // Replace XML attribute reference, such as
+ // android:id="@+id/oldName" => android:id="+id/newName"
+
+ String match = null;
+ String matchedValue = null;
+
+ if (value.equals(mXmlMatch1)) {
+ match = mXmlMatch1;
+ matchedValue = mXmlNewValue1;
+ } else if (value.equals(mXmlMatch2)) {
+ match = mXmlMatch2;
+ matchedValue = mXmlNewValue2;
+ } else if (value.equals(mXmlMatch3)) {
+ match = mXmlMatch3;
+ matchedValue = mXmlNewValue3;
+ } else {
+ continue;
+ }
+
+ if (match != null) {
+ if (mNewName.isEmpty() && ATTR_ID.equals(attr.getLocalName()) &&
+ ANDROID_URI.equals(attr.getNamespaceURI())) {
+ // Delete attribute
+ IndexedRegion region = (IndexedRegion) attr;
+ int start = region.getStartOffset();
+ int end = region.getEndOffset();
+ edits.add(new ReplaceEdit(start, end - start, ""));
+ } else {
+ int start = RefactoringUtil.getAttributeValueRangeStart(attr, document);
+ if (start != -1) {
+ int end = start + match.length();
+ edits.add(new ReplaceEdit(start, end - start, matchedValue));
+ }
+ }
+ }
+ }
+
+ NodeList children = element.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ addReplacements(edits, (Element) child, document, folderType);
+ } else if (child.getNodeType() == Node.TEXT_NODE && mUpdateReferences) {
+ // Replace XML text, such as @color/custom_theme_color in
+ // <item name="android:windowBackground">@color/custom_theme_color</item>
+ //
+ String text = child.getNodeValue();
+ int index = getFirstNonBlankIndex(text);
+ if (index != -1) {
+ String match = null;
+ String matchedValue = null;
+ if (mXmlMatch1 != null
+ && text.startsWith(mXmlMatch1) && text.trim().equals(mXmlMatch1)) {
+ match = mXmlMatch1;
+ matchedValue = mXmlNewValue1;
+ } else if (mXmlMatch2 != null
+ && text.startsWith(mXmlMatch2) && text.trim().equals(mXmlMatch2)) {
+ match = mXmlMatch2;
+ matchedValue = mXmlNewValue2;
+ } else if (mXmlMatch3 != null
+ && text.startsWith(mXmlMatch3) && text.trim().equals(mXmlMatch3)) {
+ match = mXmlMatch3;
+ matchedValue = mXmlNewValue3;
+ }
+ if (match != null) {
+ IndexedRegion region = (IndexedRegion) child;
+ int start = region.getStartOffset() + index;
+ int end = start + match.length();
+ edits.add(new ReplaceEdit(start, end - start, matchedValue));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the index of the first non-space character in the string, or -1
+ * if the string is empty or has only whitespace
+ *
+ * @param s the string to check
+ * @return the index of the first non whitespace character
+ */
+ private int getFirstNonBlankIndex(String s) {
+ for (int i = 0, n = s.length(); i < n; i++) {
+ if (!Character.isWhitespace(s.charAt(i))) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Initiates a renaming of a resource item
+ *
+ * @param project the project containing the resource references
+ * @param type the type of resource
+ * @param name the name of the resource
+ * @return false if initiating the rename failed
+ */
+ @Nullable
+ private static IField getResourceField(
+ @NonNull IProject project,
+ @NonNull ResourceType type,
+ @NonNull String name) {
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ if (javaProject == null) {
+ return null;
+ }
+
+ String pkg = ManifestInfo.get(project).getPackage();
+ // TODO: Rename in all libraries too?
+ IType t = javaProject.findType(pkg + '.' + R_CLASS + '.' + type.getName());
+ if (t == null) {
+ return null;
+ }
+
+ return t.getField(name);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Searches for existing changes in the refactoring which modifies the R
+ * field to rename it. it's derived so performing this change will generate
+ * a "generated code was modified manually" warning
+ */
+ private void disableExistingResourceFileChange() {
+ IFolder genFolder = mProject.getFolder(SdkConstants.FD_GEN_SOURCES);
+ if (genFolder != null && genFolder.exists()) {
+ ManifestInfo manifestInfo = ManifestInfo.get(mProject);
+ String pkg = manifestInfo.getPackage();
+ if (pkg != null) {
+ IFile rFile = genFolder.getFile(pkg.replace('.', '/') + '/' + FN_RESOURCE_CLASS);
+ TextChange change = getTextChange(rFile);
+ if (change != null) {
+ change.setEnabled(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * Searches for existing changes in the refactoring which modifies the R
+ * field to rename it. it's derived so performing this change will generate
+ * a "generated code was modified manually" warning
+ *
+ * @param change the change to disable R file changes in
+ */
+ public static void disableRClassChanges(Change change) {
+ if (change.getName().equals(FN_RESOURCE_CLASS)) {
+ change.setEnabled(false);
+ }
+ // Look for the field change on the R.java class; it's a derived file
+ // and will generate file modified manually warnings. Disable it.
+ if (change instanceof CompositeChange) {
+ for (Change outer : ((CompositeChange) change).getChildren()) {
+ disableRClassChanges(outer);
+ }
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java
new file mode 100644
index 0000000..5ea9941
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceProcessor.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtConstants;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.resources.ResourceType;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.ParticipantManager;
+import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
+import org.eclipse.ltk.core.refactoring.participants.RenameArguments;
+import org.eclipse.ltk.core.refactoring.participants.RenameProcessor;
+import org.eclipse.ltk.core.refactoring.participants.SharableParticipants;
+
+/**
+ * A rename processor for Android resources.
+ */
+public class RenameResourceProcessor extends RenameProcessor {
+ private IProject mProject;
+ private ResourceType mType;
+ private String mCurrentName;
+ private String mNewName;
+ private boolean mUpdateReferences = true;
+ private ResourceNameValidator mValidator;
+ private RenameArguments mRenameArguments;
+
+ /**
+ * Creates a new rename resource processor.
+ *
+ * @param project the project containing the renamed resource
+ * @param type the type of the resource
+ * @param currentName the current name of the resource
+ * @param newName the new name of the resource, or null if not known
+ */
+ public RenameResourceProcessor(
+ @NonNull IProject project,
+ @NonNull ResourceType type,
+ @NonNull String currentName,
+ @Nullable String newName) {
+ mProject = project;
+ mType = type;
+ mCurrentName = currentName;
+ mNewName = newName != null ? newName : currentName;
+ mUpdateReferences= true;
+ mValidator = ResourceNameValidator.create(false, mProject, mType);
+ }
+
+ /**
+ * Returns the project containing the renamed resource
+ *
+ * @return the project containing the renamed resource
+ */
+ @NonNull
+ public IProject getProject() {
+ return mProject;
+ }
+
+ /**
+ * Returns the new resource name
+ *
+ * @return the new resource name
+ */
+ @NonNull
+ public String getNewName() {
+ return mNewName;
+ }
+
+ /**
+ * Returns the current name of the resource
+ *
+ * @return the current name of the resource
+ */
+ public String getCurrentName() {
+ return mCurrentName;
+ }
+
+ /**
+ * Returns the type of the resource
+ *
+ * @return the type of the resource
+ */
+ @NonNull
+ public ResourceType getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the new name
+ *
+ * @param newName the new name
+ */
+ public void setNewName(@NonNull String newName) {
+ mNewName = newName;
+ }
+
+ /**
+ * Returns {@code true} if the refactoring processor also updates references
+ *
+ * @return {@code true} if the refactoring processor also updates references
+ */
+ public boolean isUpdateReferences() {
+ return mUpdateReferences;
+ }
+
+ /**
+ * Specifies if the refactoring processor also updates references. The
+ * default behavior is to update references.
+ *
+ * @param updateReferences {@code true} if the refactoring processor should
+ * also updates references
+ */
+ public void setUpdateReferences(boolean updateReferences) {
+ mUpdateReferences = updateReferences;
+ }
+
+ /**
+ * Checks the given new potential name and returns a {@link RefactoringStatus} indicating
+ * whether the potential new name is valid
+ *
+ * @param name the name to check
+ * @return a {@link RefactoringStatus} with the validation result
+ */
+ public RefactoringStatus checkNewName(String name) {
+ String error = mValidator.isValid(name);
+ if (error != null) {
+ return RefactoringStatus.createFatalErrorStatus(error);
+ }
+
+ return new RefactoringStatus();
+ }
+
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
+ return new RefactoringStatus();
+ }
+
+ @Override
+ public RefactoringStatus checkFinalConditions(IProgressMonitor pm,
+ CheckConditionsContext context) throws CoreException {
+ pm.beginTask("", 1);
+ try {
+ mRenameArguments = new RenameArguments(getNewName(), isUpdateReferences());
+ return new RefactoringStatus();
+ } finally {
+ pm.done();
+ }
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException {
+ pm.beginTask("", 1);
+ try {
+ // Added by {@link RenameResourceParticipant}
+ return null;
+ } finally {
+ pm.done();
+ }
+ }
+
+ @Override
+ public Object[] getElements() {
+ return new Object[0];
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "com.android.ide.renameResourceProcessor"; //$NON-NLS-1$
+ }
+
+ @Override
+ public String getProcessorName() {
+ return "Rename Android Resource";
+ }
+
+ @Override
+ public boolean isApplicable() {
+ return true;
+ }
+
+ @Override
+ public RefactoringParticipant[] loadParticipants(RefactoringStatus status,
+ SharableParticipants shared) throws CoreException {
+ String[] affectedNatures = new String[] { AdtConstants.NATURE_DEFAULT };
+ String url = PREFIX_RESOURCE_REF + mType.getName() + '/' + mCurrentName;
+ return ParticipantManager.loadRenameParticipants(status, this, url, mRenameArguments,
+ null, affectedNatures, shared);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java
new file mode 100644
index 0000000..6ffe25d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceWizard.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.resources.ResourceType;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.internal.ui.IJavaHelpContextIds;
+import org.eclipse.jdt.internal.ui.JavaPluginImages;
+import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameRefactoringWizard;
+import org.eclipse.jdt.ui.refactoring.RefactoringSaveHelper;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Rename refactoring wizard for Android resources such as {@code @id/foo}
+ */
+@SuppressWarnings("restriction") // JDT refactoring UI
+public class RenameResourceWizard extends RenameRefactoringWizard {
+ private ResourceType mType;
+ private boolean mCanClear;
+
+ /**
+ * Constructs a new {@linkplain RenameResourceWizard}
+ *
+ * @param refactoring the refactoring
+ * @param type the type of resource being renamed
+ * @param canClear whether the user can clear the value
+ */
+ public RenameResourceWizard(
+ @NonNull RenameRefactoring refactoring,
+ @NonNull ResourceType type,
+ boolean canClear) {
+ super(refactoring,
+ "Rename Resource",
+ "Enter the new name for this resource",
+ JavaPluginImages.DESC_WIZBAN_REFACTOR_FIELD,
+ IJavaHelpContextIds.RENAME_FIELD_WIZARD_PAGE);
+ mType = type;
+ mCanClear = canClear;
+ }
+
+ @Override
+ protected void addUserInputPages() {
+ RenameRefactoring refactoring = (RenameRefactoring) getRefactoring();
+ RenameResourceProcessor processor = (RenameResourceProcessor) refactoring.getProcessor();
+ String name = processor.getNewName();
+ addPage(new RenameResourcePage(mType, name, mCanClear));
+ }
+
+ /**
+ * Initiates a renaming of a resource item
+ *
+ * @param shell the shell to parent the dialog to
+ * @param project the project containing the resource references
+ * @param type the type of resource
+ * @param currentName the name of the resource
+ * @param newName the new name, or null if not known
+ * @param canClear whether the name is allowed to be cleared
+ * @return false if initiating the rename failed
+ */
+ public static RenameResult renameResource(
+ @NonNull Shell shell,
+ @NonNull IProject project,
+ @NonNull ResourceType type,
+ @NonNull String currentName,
+ @Nullable String newName,
+ boolean canClear) {
+ try {
+ RenameResourceProcessor processor = new RenameResourceProcessor(project, type,
+ currentName, newName);
+ RenameRefactoring refactoring = new RenameRefactoring(processor);
+ if (!refactoring.isApplicable()) {
+ return RenameResult.unavailable();
+ }
+
+ if (!show(refactoring, processor, shell, type, canClear)) {
+ return RenameResult.canceled();
+ }
+ return RenameResult.name(processor.getNewName());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return RenameResult.unavailable();
+ }
+
+ /**
+ * Show a refactoring dialog for the given resource refactoring operation
+ *
+ * @param refactoring the rename refactoring
+ * @param processor the field processor
+ * @param parent the parent shell
+ * @param type the resource type
+ * @param canClear whether the user is allowed to clear/reset the name to
+ * nothing
+ * @return true if the refactoring was performed, and false if it was
+ * canceled
+ * @throws CoreException if an unexpected error occurs
+ */
+ private static boolean show(
+ @NonNull RenameRefactoring refactoring,
+ @NonNull RenameResourceProcessor processor,
+ @NonNull Shell parent,
+ @NonNull ResourceType type,
+ boolean canClear) throws CoreException {
+ RefactoringSaveHelper saveHelper = new RefactoringSaveHelper(
+ RefactoringSaveHelper.SAVE_REFACTORING);
+ if (!saveHelper.saveEditors(parent)) {
+ return false;
+ }
+
+ try {
+ RenameResourceWizard wizard = new RenameResourceWizard(refactoring, type, canClear);
+ RefactoringWizardOpenOperation operation = new RefactoringWizardOpenOperation(wizard);
+ String dialogTitle = wizard.getDefaultPageTitle();
+ int result = operation.run(parent, dialogTitle == null ? "" : dialogTitle);
+ RefactoringStatus status = operation.getInitialConditionCheckingStatus();
+ if (status.hasFatalError()) {
+ return false;
+ }
+ if (result == RefactoringWizardOpenOperation.INITIAL_CONDITION_CHECKING_FAILED
+ || result == IDialogConstants.CANCEL_ID) {
+ saveHelper.triggerIncrementalBuild();
+ return false;
+ }
+
+ // Save modified resources; need to trigger R file regeneration
+ saveHelper.saveEditors(parent);
+
+ return true;
+ } catch (InterruptedException e) {
+ return false; // Canceled
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java
new file mode 100644
index 0000000..3b1fa52
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResourceXmlTextAction.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
+import static com.android.SdkConstants.ANDROID_PREFIX;
+import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
+import static com.android.SdkConstants.ATTR_NAME;
+import static com.android.SdkConstants.ATTR_TYPE;
+import static com.android.SdkConstants.TAG_ITEM;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ResourceRepository;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.resources.ResourceType;
+import com.android.utils.Pair;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
+import org.eclipse.jdt.internal.corext.refactoring.rename.RenameTypeProcessor;
+import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameTypeWizard;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.ISelectionProvider;
+import org.eclipse.ltk.core.refactoring.participants.RenameRefactoring;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IEditorInput;
+import org.eclipse.ui.IFileEditorInput;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.texteditor.IDocumentProvider;
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorExtension;
+import org.eclipse.ui.texteditor.ITextEditorExtension2;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.List;
+
+/**
+ * Text action for XML files to invoke renaming
+ * <p>
+ * TODO: Handle other types of renaming: invoking class renaming when editing
+ * class names in layout files and manifest files, renaming attribute names when
+ * editing a styleable attribute, etc.
+ */
+@SuppressWarnings("restriction") // Java rename refactoring
+public final class RenameResourceXmlTextAction extends Action {
+ private final ITextEditor mEditor;
+
+ /**
+ * Creates a new {@linkplain RenameResourceXmlTextAction}
+ *
+ * @param editor the associated editor
+ */
+ public RenameResourceXmlTextAction(@NonNull ITextEditor editor) {
+ super("Rename");
+ mEditor = editor;
+ }
+
+ @Override
+ public void run() {
+ if (!validateEditorInputState()) {
+ return;
+ }
+ IFile file = getFile();
+ if (file == null) {
+ return;
+ }
+ IProject project = file.getProject();
+ if (project == null) {
+ return;
+ }
+ IDocument document = getDocument();
+ if (document == null) {
+ return;
+ }
+ ITextSelection selection = getSelection();
+ if (selection == null) {
+ return;
+ }
+
+ Pair<ResourceType, String> resource = findResource(document, selection.getOffset());
+
+ if (resource == null) {
+ resource = findItemDefinition(document, selection.getOffset());
+ }
+
+ if (resource != null) {
+ ResourceType type = resource.getFirst();
+ String name = resource.getSecond();
+ Shell shell = mEditor.getSite().getShell();
+ boolean canClear = false;
+
+ RenameResourceWizard.renameResource(shell, project, type, name, null, canClear);
+ return;
+ }
+
+ String className = findClassName(document, file, selection.getOffset());
+ if (className != null) {
+ assert className.equals(className.trim());
+ IType type = findType(className, project);
+ if (type != null) {
+ RenameTypeProcessor processor = new RenameTypeProcessor(type);
+ //processor.setNewElementName(className);
+ processor.setUpdateQualifiedNames(true);
+ processor.setUpdateSimilarDeclarations(false);
+ //processor.setMatchStrategy(?);
+ //processor.setFilePatterns(patterns);
+ processor.setUpdateReferences(true);
+
+ RenameRefactoring refactoring = new RenameRefactoring(processor);
+ RenameTypeWizard wizard = new RenameTypeWizard(refactoring);
+ RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
+ try {
+ IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
+ op.run(window.getShell(), wizard.getDefaultPageTitle());
+ } catch (InterruptedException e) {
+ }
+ }
+
+ return;
+ }
+
+ // Fallback: tell user the cursor isn't in the right place
+ MessageDialog.openInformation(mEditor.getSite().getShell(),
+ "Rename",
+ "Operation unavailable on the current selection.\n"
+ + "Select an Android resource name or class.");
+ }
+
+ private boolean validateEditorInputState() {
+ if (mEditor instanceof ITextEditorExtension2)
+ return ((ITextEditorExtension2) mEditor).validateEditorInputState();
+ else if (mEditor instanceof ITextEditorExtension)
+ return !((ITextEditorExtension) mEditor).isEditorInputReadOnly();
+ else if (mEditor != null)
+ return mEditor.isEditable();
+ else
+ return false;
+ }
+
+ /**
+ * Searches for a resource URL around the caret, such as {@code @string/foo}
+ *
+ * @param document the document to search in
+ * @param offset the offset to search at
+ * @return a resource pair, or null if not found
+ */
+ @Nullable
+ public static Pair<ResourceType,String> findResource(@NonNull IDocument document, int offset) {
+ try {
+ int max = document.getLength();
+ if (offset >= max) {
+ offset = max - 1;
+ } else if (offset < 0) {
+ offset = 0;
+ } else if (offset > 0) {
+ // If the caret is right after a resource name (meaning getChar(offset) points
+ // to the following character), back up
+ char c = document.getChar(offset);
+ if (!isValidResourceNameChar(c)) {
+ offset--;
+ }
+ }
+
+ int start = offset;
+ boolean valid = true;
+ for (; start >= 0; start--) {
+ char c = document.getChar(start);
+ if (c == '@' || c == '?') {
+ break;
+ } else if (!isValidResourceNameChar(c)) {
+ valid = false;
+ break;
+ }
+ }
+ if (valid) {
+ // Search forwards for the end
+ int end = start + 1;
+ for (; end < max; end++) {
+ char c = document.getChar(end);
+ if (!isValidResourceNameChar(c)) {
+ break;
+ }
+ }
+ if (end > start + 1) {
+ String url = document.get(start, end - start);
+
+ // Don't allow renaming framework resources -- @android:string/ok etc
+ if (url.startsWith(ANDROID_PREFIX) || url.startsWith(ANDROID_THEME_PREFIX)) {
+ return null;
+ }
+
+ return ResourceRepository.parseResource(url);
+ }
+ }
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ private static boolean isValidResourceNameChar(char c) {
+ return c == '@' || c == '?' || c == '/' || c == '+' || Character.isJavaIdentifierPart(c);
+ }
+
+ /**
+ * Searches for an item definition around the caret, such as
+ * {@code <string name="foo">My String</string>}
+ */
+ private Pair<ResourceType, String> findItemDefinition(IDocument document, int offset) {
+ Node node = DomUtilities.getNode(document, offset);
+ if (node == null) {
+ return null;
+ }
+ if (node.getNodeType() == Node.TEXT_NODE) {
+ node = node.getParentNode();
+ }
+ if (node == null || node.getNodeType() != Node.ELEMENT_NODE) {
+ return null;
+ }
+
+ Element element = (Element) node;
+ String name = element.getAttribute(ATTR_NAME);
+ if (name == null || name.isEmpty()) {
+ return null;
+ }
+ String typeString = element.getTagName();
+ if (TAG_ITEM.equals(typeString)) {
+ typeString = element.getAttribute(ATTR_TYPE);
+ if (typeString == null || typeString.isEmpty()) {
+ return null;
+ }
+ }
+ ResourceType type = ResourceType.getEnum(typeString);
+ if (type != null) {
+ return Pair.of(type, name);
+ }
+
+ return null;
+ }
+
+ /**
+ * Searches for a fully qualified class name around the caret, such as {@code foo.bar.MyClass}
+ *
+ * @param document the document to search in
+ * @param file the file, if known
+ * @param offset the offset to search at
+ * @return a resource pair, or null if not found
+ */
+ @Nullable
+ public static String findClassName(
+ @NonNull IDocument document,
+ @Nullable IFile file,
+ int offset) {
+ try {
+ int max = document.getLength();
+ if (offset >= max) {
+ offset = max - 1;
+ } else if (offset < 0) {
+ offset = 0;
+ } else if (offset > 0) {
+ // If the caret is right after a resource name (meaning getChar(offset) points
+ // to the following character), back up
+ char c = document.getChar(offset);
+ if (Character.isJavaIdentifierPart(c)) {
+ offset--;
+ }
+ }
+
+ int start = offset;
+ for (; start >= 0; start--) {
+ char c = document.getChar(start);
+ if (c == '"' || c == '<' || c == '/') {
+ start++;
+ break;
+ } else if (c != '.' && !Character.isJavaIdentifierPart(c)) {
+ return null;
+ }
+ }
+ // Search forwards for the end
+ int end = start + 1;
+ for (; end < max; end++) {
+ char c = document.getChar(end);
+ if (c != '.' && !Character.isJavaIdentifierPart(c)) {
+ if (c != '"' && c != '>' && !Character.isWhitespace(c)) {
+ return null;
+ }
+ break;
+ }
+ }
+ if (end > start + 1) {
+ String fqcn = document.get(start, end - start);
+ int dot = fqcn.indexOf('.');
+ if (dot == -1) { // Only support fully qualified names
+ return null;
+ }
+ if (dot == 0) { // Special case for manifests: prepend package
+ if (file != null && file.getName().equals(ANDROID_MANIFEST_XML)) {
+ ManifestInfo info = ManifestInfo.get(file.getProject());
+ return info.getPackage() + fqcn;
+ }
+ return null;
+ }
+
+ return fqcn;
+ }
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private IType findType(@NonNull String className, @NonNull IProject project) {
+ IType type = null;
+ try {
+ IJavaProject javaProject = BaseProjectHelper.getJavaProject(project);
+ type = javaProject.findType(className);
+ if (type == null || !type.exists()) {
+ return null;
+ }
+ if (!type.isBinary()) {
+ return type;
+ }
+ // See if this class is coming through a library project jar file and
+ // if so locate the real class
+ ProjectState projectState = Sdk.getProjectState(project);
+ if (projectState != null) {
+ List<IProject> libraries = projectState.getFullLibraryProjects();
+ for (IProject library : libraries) {
+ javaProject = BaseProjectHelper.getJavaProject(library);
+ type = javaProject.findType(className);
+ if (type != null && type.exists() && !type.isBinary()) {
+ return type;
+ }
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ return null;
+ }
+
+ private ITextSelection getSelection() {
+ ISelectionProvider selectionProvider = mEditor.getSelectionProvider();
+ if (selectionProvider == null) {
+ return null;
+ }
+ ISelection selection = selectionProvider.getSelection();
+ if (!(selection instanceof ITextSelection)) {
+ return null;
+ }
+ return (ITextSelection) selection;
+ }
+
+ private IDocument getDocument() {
+ IDocumentProvider documentProvider = mEditor.getDocumentProvider();
+ if (documentProvider == null) {
+ return null;
+ }
+ IDocument document = documentProvider.getDocument(mEditor.getEditorInput());
+ if (document == null) {
+ return null;
+ }
+ return document;
+ }
+
+ @Nullable
+ private IFile getFile() {
+ IEditorInput input = mEditor.getEditorInput();
+ if (input instanceof IFileEditorInput) {
+ IFileEditorInput fileInput = (IFileEditorInput) input;
+ return fileInput.getFile();
+ }
+
+ return null;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java
new file mode 100644
index 0000000..ade346f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/core/RenameResult.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.refactorings.core;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+/**
+ * A result from a renaming operation
+ */
+public class RenameResult {
+ private boolean mCanceled;
+ private boolean mUnavailable;
+ private @Nullable String mName;
+ private boolean mClear;
+
+ /**
+ * Constructs a new rename result
+ */
+ private RenameResult() {
+ }
+
+ /**
+ * Creates a new blank {@linkplain RenameResult}
+ * @return a new result
+ */
+ @NonNull
+ public static RenameResult create() {
+ return new RenameResult();
+ }
+
+ /**
+ * Creates a new {@linkplain RenameResult} for a user canceled renaming operation
+ * @return a canceled operation
+ */
+ @NonNull
+ public static RenameResult canceled() {
+ return new RenameResult().setCanceled(true);
+ }
+
+ /**
+ * Creates a {@linkplain RenameResult} for a renaming operation that was
+ * not available (for example because the field attempted to be renamed
+ * does not yet exist (or does not exist any more)
+ *
+ * @return a new result
+ */
+ @NonNull
+ public static RenameResult unavailable() {
+ return new RenameResult().setUnavailable(true);
+ }
+
+ /**
+ * Creates a new {@linkplain RenameResult} for a successful renaming
+ * operation to the given name
+ *
+ * @param name the new name
+ * @return a new result
+ */
+ @NonNull
+ public static RenameResult name(@Nullable String name) {
+ return new RenameResult().setName(name);
+ }
+
+ /**
+ * Marks this result as canceled
+ *
+ * @param canceled whether the result was canceled
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public RenameResult setCanceled(boolean canceled) {
+ mCanceled = canceled;
+ return this;
+ }
+
+ /**
+ * Marks this result as unavailable
+ *
+ * @param unavailable whether this result was unavailable
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public RenameResult setUnavailable(boolean unavailable) {
+ mUnavailable = unavailable;
+ return this;
+ }
+
+ /**
+ * Sets the new name of the renaming operation
+ *
+ * @param name the new name
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public RenameResult setName(@Nullable String name) {
+ mName = name;
+ return this;
+ }
+
+ /**
+ * Marks this result as clearing the name (reverting it back to the default)
+ *
+ * @param clear whether the name was cleared
+ * @return this, for constructor chaining
+ */
+ @NonNull
+ public RenameResult setCleared(boolean clear) {
+ mClear = clear;
+ return this;
+ }
+
+ /**
+ * Returns whether this result represents a canceled renaming operation
+ *
+ * @return true if the operation was canceled
+ */
+ public boolean isCanceled() {
+ return mCanceled;
+ }
+
+ /**
+ * Returns whether this result represents an unavailable renaming operation
+ *
+ * @return true if the operation was not available
+ */
+ public boolean isUnavailable() {
+ return mUnavailable;
+ }
+
+ /**
+ * Returns whether this result represents a renaming back to the default (possibly
+ * clear) name. In this case, {@link #getName()} will return {@code null}.
+ *
+ * @return true if the name should be reset
+ */
+ public boolean isCleared() {
+ return mClear;
+ }
+
+ /**
+ * Returns the new name.
+ *
+ * @return the new name
+ */
+ @Nullable
+ public String getName() {
+ return mName;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java
index 14fd506..61bd06e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringContribution.java
@@ -4,7 +4,7 @@
* Licensed under the Eclipse Public License, Version 1.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.eclipse.org/org/documents/epl-v10.php
*
* Unless required by applicable law or agreed to in writing, software
@@ -25,11 +25,11 @@ import java.util.Map;
* @see ExtractStringDescriptor
*/
public class ExtractStringContribution extends RefactoringContribution {
-
+
/* (non-Javadoc)
* @see org.eclipse.ltk.core.refactoring.RefactoringContribution#createDescriptor(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.util.Map, int)
*/
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "rawtypes"})
@Override
public RefactoringDescriptor createDescriptor(
String id,
@@ -42,7 +42,7 @@ public class ExtractStringContribution extends RefactoringContribution {
return new ExtractStringDescriptor(project, description, comment, arguments);
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
@Override
public Map retrieveArgumentMap(RefactoringDescriptor descriptor) {
if (descriptor instanceof ExtractStringDescriptor) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
index e9d386e..7d0f926 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ExtractStringRefactoring.java
@@ -16,12 +16,11 @@
package com.android.ide.eclipse.adt.internal.refactorings.extractstring;
-import static com.android.SdkConstants.AMP_ENTITY;
-import static com.android.SdkConstants.LT_ENTITY;
import static com.android.SdkConstants.QUOT_ENTITY;
import static com.android.SdkConstants.STRING_PREFIX;
import com.android.SdkConstants;
+import com.android.ide.common.resources.ValueResourceParser;
import com.android.ide.common.xml.ManifestData;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
@@ -1218,7 +1217,7 @@ public class ExtractStringRefactoring extends Refactoring {
IStructuredModel smodel = null;
// Single and double quotes must be escaped in the <string>value</string> declaration
- tokenString = escapeString(tokenString);
+ tokenString = ValueResourceParser.escapeResourceString(tokenString);
try {
IStructuredDocument sdoc = null;
@@ -1450,79 +1449,6 @@ public class ExtractStringRefactoring extends Refactoring {
}
/**
- * Escape a string value to be placed in a string resource file such that it complies with
- * the escaping rules described here:
- * http://developer.android.com/guide/topics/resources/string-resource.html
- * More examples of the escaping rules can be found here:
- * http://androidcookbook.com/Recipe.seam?recipeId=2219&recipeFrom=ViewTOC
- * This method assumes that the String is not escaped already.
- *
- * Rules:
- * <ul>
- * <li>Double quotes are needed if string starts or ends with at least one space.
- * <li>{@code @, ?} at beginning of string have to be escaped with a backslash.
- * <li>{@code ', ", \} have to be escaped with a backslash.
- * <li>{@code <, >, &} have to be replaced by their predefined xml entity.
- * <li>{@code \n, \t} have to be replaced by a backslash and the appropriate character.
- * </ul>
- * @param s the string to be escaped
- * @return the escaped string as it would appear in the XML text in a values file
- */
- public static String escapeString(String s) {
- int n = s.length();
- if (n == 0) {
- return "";
- }
-
- StringBuilder sb = new StringBuilder(s.length() * 2);
- boolean hasSpace = s.charAt(0) == ' ' || s.charAt(n - 1) == ' ';
-
- if (hasSpace) {
- sb.append('"');
- } else if (s.charAt(0) == '@' || s.charAt(0) == '?') {
- sb.append('\\');
- }
-
- for (int i = 0; i < n; ++i) {
- char c = s.charAt(i);
- switch (c) {
- case '\'':
- if (!hasSpace) {
- sb.append('\\');
- }
- sb.append(c);
- break;
- case '"':
- case '\\':
- sb.append('\\');
- sb.append(c);
- break;
- case '<':
- sb.append(LT_ENTITY);
- break;
- case '&':
- sb.append(AMP_ENTITY);
- break;
- case '\n':
- sb.append("\\n"); //$NON-NLS-1$
- break;
- case '\t':
- sb.append("\\t"); //$NON-NLS-1$
- break;
- default:
- sb.append(c);
- break;
- }
- }
-
- if (hasSpace) {
- sb.append('"');
- }
-
- return sb.toString();
- }
-
- /**
* Computes the changes to be made to the source Android XML file and
* returns a list of {@link Change}.
* <p/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java
index 2f1185c..e058ce1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/extractstring/ReplaceStringsVisitor.java
@@ -191,7 +191,7 @@ class ReplaceStringsVisitor extends ASTVisitor {
*
* This covers the case of Activity.setTitle(int resId) vs setTitle(String str).
*/
- @SuppressWarnings("unchecked")
+ @SuppressWarnings("rawtypes")
private boolean examineMethodInvocation(StringLiteral node) {
ASTNode parent = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
index 7005d82..406cebc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/ApplicationPackageNameRefactoring.java
@@ -16,6 +16,10 @@
package com.android.ide.eclipse.adt.internal.refactorings.renamepackage;
+import static com.android.SdkConstants.FN_BUILD_CONFIG_BASE;
+import static com.android.SdkConstants.FN_MANIFEST_BASE;
+import static com.android.SdkConstants.FN_RESOURCE_BASE;
+
import com.android.SdkConstants;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
@@ -74,7 +78,6 @@ import java.util.List;
*/
@SuppressWarnings("restriction")
class ApplicationPackageNameRefactoring extends Refactoring {
-
private final IProject mProject;
private final Name mOldPackageName;
private final Name mNewPackageName;
@@ -100,8 +103,8 @@ class ApplicationPackageNameRefactoring extends Refactoring {
IMarker.PROBLEM,
true,
IResource.DEPTH_INFINITE) == IMarker.SEVERITY_ERROR) {
- return RefactoringStatus
- .createFatalErrorStatus("Fix the errors in your project, first.");
+ return
+ RefactoringStatus.createFatalErrorStatus("Fix the errors in your project, first.");
}
return new RefactoringStatus();
@@ -143,21 +146,36 @@ class ApplicationPackageNameRefactoring extends Refactoring {
TextEdit rewrittenImports = importVisitor.getTextEdit();
// If the import of R was potentially implicit, insert an import statement
- if (cu.getPackage().getName().getFullyQualifiedName()
+ if (rewrittenImports != null && cu.getPackage().getName().getFullyQualifiedName()
.equals(mOldPackageName.getFullyQualifiedName())) {
- ImportRewrite irw = ImportRewrite.create(cu, true);
- irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
- + SdkConstants.FN_RESOURCE_BASE);
+ UsageVisitor usageVisitor = new UsageVisitor();
+ cu.accept(usageVisitor);
- try {
- rewrittenImports.addChild( irw.rewriteImports(null) );
- } catch (MalformedTreeException e) {
- Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
- AdtPlugin.getDefault().getLog().log(s);
- } catch (CoreException e) {
- Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
- AdtPlugin.getDefault().getLog().log(s);
+ if (usageVisitor.seenAny()) {
+ ImportRewrite irw = ImportRewrite.create(cu, true);
+ if (usageVisitor.hasSeenR()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_RESOURCE_BASE);
+ }
+ if (usageVisitor.hasSeenBuildConfig()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_BUILD_CONFIG_BASE);
+ }
+ if (usageVisitor.hasSeenManifest()) {
+ irw.addImport(mNewPackageName.getFullyQualifiedName() + '.'
+ + FN_MANIFEST_BASE);
+ }
+
+ try {
+ rewrittenImports.addChild( irw.rewriteImports(null) );
+ } catch (MalformedTreeException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ } catch (CoreException e) {
+ Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
+ AdtPlugin.getDefault().getLog().log(s);
+ }
}
}
@@ -405,11 +423,11 @@ class ApplicationPackageNameRefactoring extends Refactoring {
Collections.reverse(mChanges);
CompositeChange change = new CompositeChange("Refactoring Application package name",
mChanges.toArray(new Change[mChanges.size()]));
+ change.markAsSynthetic();
return change;
}
@Override
- @SuppressWarnings("unused")
public boolean visit(IResource resource) throws CoreException {
if (resource instanceof IFile) {
IFile file = (IFile) resource;
@@ -420,10 +438,10 @@ class ApplicationPackageNameRefactoring extends Refactoring {
mParser.setSource(icu);
CompilationUnit cu = (CompilationUnit) mParser.createAST(null);
- TextEdit text_edit = updateJavaFileImports(cu);
- if (text_edit.hasChildren()) {
+ TextEdit textEdit = updateJavaFileImports(cu);
+ if (textEdit != null && textEdit.hasChildren()) {
MultiTextEdit edit = new MultiTextEdit();
- edit.addChild(text_edit);
+ edit.addChild(textEdit);
TextFileChange text_file_change = new TextFileChange(file.getName(), file);
text_file_change.setTextType(SdkConstants.EXT_JAVA);
@@ -437,10 +455,13 @@ class ApplicationPackageNameRefactoring extends Refactoring {
} else if (SdkConstants.EXT_XML.equals(file.getFileExtension())) {
if (SdkConstants.FN_ANDROID_MANIFEST_XML.equals(file.getName())) {
-
- TextFileChange manifest_change = editAndroidManifest(file);
- mChanges.add(manifest_change);
-
+ // Ensure that this is the root manifest, not some other copy
+ // (such as the one in bin/)
+ IPath path = file.getFullPath();
+ if (path.segmentCount() == 2) {
+ TextFileChange manifest_change = editAndroidManifest(file);
+ mChanges.add(manifest_change);
+ }
} else {
// Currently we only support Android resource XML files,
@@ -475,7 +496,43 @@ class ApplicationPackageNameRefactoring extends Refactoring {
}
}
- class ImportVisitor extends ASTVisitor {
+ private static class UsageVisitor extends ASTVisitor {
+ private boolean mSeenManifest;
+ private boolean mSeenR;
+ private boolean mSeenBuildConfig;
+
+ @Override
+ public boolean visit(QualifiedName node) {
+ Name qualifier = node.getQualifier();
+ if (qualifier.isSimpleName()) {
+ String name = qualifier.toString();
+ if (name.equals(FN_RESOURCE_BASE)) {
+ mSeenR = true;
+ } else if (name.equals(FN_BUILD_CONFIG_BASE)) {
+ mSeenBuildConfig = true;
+ } else if (name.equals(FN_MANIFEST_BASE)) {
+ mSeenManifest = true;
+ }
+ }
+ return super.visit(node);
+ };
+
+ public boolean seenAny() {
+ return mSeenR || mSeenBuildConfig || mSeenManifest;
+ }
+
+ public boolean hasSeenBuildConfig() {
+ return mSeenBuildConfig;
+ }
+ public boolean hasSeenManifest() {
+ return mSeenManifest;
+ }
+ public boolean hasSeenR() {
+ return mSeenR;
+ }
+ }
+
+ private class ImportVisitor extends ASTVisitor {
final AST mAst;
final ASTRewrite mRewriter;
@@ -505,8 +562,19 @@ class ApplicationPackageNameRefactoring extends Refactoring {
if (importName.isQualifiedName()) {
QualifiedName qualifiedImportName = (QualifiedName) importName;
- if (qualifiedImportName.getName().getIdentifier()
- .equals(SdkConstants.FN_RESOURCE_BASE)) {
+ String identifier = qualifiedImportName.getName().getIdentifier();
+ if (identifier.equals(FN_RESOURCE_BASE)) {
+ mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
+ null);
+ } else if (identifier.equals(FN_BUILD_CONFIG_BASE)
+ && mOldPackageName.toString().equals(
+ qualifiedImportName.getQualifier().toString())) {
+ mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
+ null);
+
+ } else if (identifier.equals(FN_MANIFEST_BASE)
+ && mOldPackageName.toString().equals(
+ qualifiedImportName.getQualifier().toString())) {
mRewriter.replace(qualifiedImportName.getQualifier(), mNewPackageName,
null);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java
index c556f14..bb475aa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/refactorings/renamepackage/RenamePackageAction.java
@@ -102,7 +102,7 @@ public class RenamePackageAction implements IObjectActionDelegate {
// enforce a save as a convenience.
RefactoringSaveHelper save_helper = new RefactoringSaveHelper(
RefactoringSaveHelper.SAVE_ALL_ALWAYS_ASK);
- if (save_helper.saveEditors(AdtPlugin.getDisplay().getActiveShell())) {
+ if (save_helper.saveEditors(AdtPlugin.getShell())) {
promptNewName(project);
}
}
@@ -142,7 +142,7 @@ public class RenamePackageAction implements IObjectActionDelegate {
}
};
- InputDialog dialog = new InputDialog(AdtPlugin.getDisplay().getActiveShell(),
+ InputDialog dialog = new InputDialog(AdtPlugin.getShell(),
"Rename Application Package", "Enter new package name:", oldPackageNameString,
validator);
@@ -165,7 +165,7 @@ public class RenamePackageAction implements IObjectActionDelegate {
new ApplicationPackageNameRefactoringWizard(package_name_refactoring);
RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
try {
- op.run(AdtPlugin.getDisplay().getActiveShell(), package_name_refactoring.getName());
+ op.run(AdtPlugin.getShell(), package_name_refactoring.getName());
} catch (InterruptedException e) {
Status s = new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, e.getMessage(), e);
AdtPlugin.getDefault().getLog().log(s);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
index 6b09b34..978980b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/ResourceHelper.java
@@ -34,12 +34,14 @@ import static com.android.ide.eclipse.adt.AdtConstants.WS_SEP;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceDeltaKind;
+import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.common.resources.configuration.CountryCodeQualifier;
import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.KeyboardStateQualifier;
import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
import com.android.ide.common.resources.configuration.NavigationMethodQualifier;
import com.android.ide.common.resources.configuration.NavigationStateQualifier;
import com.android.ide.common.resources.configuration.NetworkCodeQualifier;
@@ -127,6 +129,7 @@ public class ResourceHelper {
sIconMap.put(NetworkCodeQualifier.class, factory.getIcon("mnc")); //$NON-NLS-1$
sIconMap.put(LanguageQualifier.class, factory.getIcon("language")); //$NON-NLS-1$
sIconMap.put(RegionQualifier.class, factory.getIcon("region")); //$NON-NLS-1$
+ sIconMap.put(LayoutDirectionQualifier.class, factory.getIcon("bidi")); //$NON-NLS-1$
sIconMap.put(ScreenSizeQualifier.class, factory.getIcon("size")); //$NON-NLS-1$
sIconMap.put(ScreenRatioQualifier.class, factory.getIcon("ratio")); //$NON-NLS-1$
sIconMap.put(ScreenOrientationQualifier.class, factory.getIcon("orientation")); //$NON-NLS-1$
@@ -178,39 +181,6 @@ public class ResourceHelper {
}
/**
- * Return the resource type of the given url, and the resource name
- *
- * @param url the resource url to be parsed
- * @return a pair of the resource type and the resource name
- */
- public static Pair<ResourceType,String> parseResource(String url) {
- if (!url.startsWith(PREFIX_RESOURCE_REF)) {
- return null;
- }
- int typeEnd = url.indexOf('/', 1);
- if (typeEnd == -1) {
- return null;
- }
- int nameBegin = typeEnd + 1;
-
- // Skip @ and @+
- int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
-
- int colon = url.lastIndexOf(':', typeEnd);
- if (colon != -1) {
- typeBegin = colon + 1;
- }
- String typeName = url.substring(typeBegin, typeEnd);
- ResourceType type = ResourceType.getEnum(typeName);
- if (type == null) {
- return null;
- }
- String name = url.substring(nameBegin);
-
- return Pair.of(type, name);
- }
-
- /**
* Is this a resource that can be defined in any file within the "values" folder?
* <p>
* Some resource types can be defined <b>both</b> as a separate XML file as well
@@ -279,7 +249,7 @@ public class ResourceHelper {
return false;
}
- Pair<ResourceType,String> parsed = parseResource(resource);
+ Pair<ResourceType,String> parsed = ResourceRepository.parseResource(resource);
if (parsed != null) {
ResourceType type = parsed.getFirst();
String name = parsed.getSecond();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
index 6554cc2..ab5ae40 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/CompiledResourcesMonitor.java
@@ -84,9 +84,9 @@ public final class CompiledResourcesMonitor implements IFileListener, IProjectLi
*/
@Override
public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
- if (flags == IResourceDelta.MARKERS) {
- // Only the markers changed: not relevant
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject) {
+ if (!isAndroidProject || flags == IResourceDelta.MARKERS) {
+ // Not Android or only the markers changed: not relevant
return;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
index 7cb4e94..674a601 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/GlobalProjectMonitor.java
@@ -20,6 +20,7 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.common.resources.ResourceFolder;
+import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
@@ -76,9 +77,10 @@ public final class GlobalProjectMonitor {
* not have an extension
* @param flags the {@link IResourceDelta#getFlags()} value with details
* on what changed in the file
+ * @param isAndroidProject whether the parent project is an Android Project
*/
public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags);
+ int kind, @Nullable String extension, int flags, boolean isAndroidProject);
}
/**
@@ -138,8 +140,9 @@ public final class GlobalProjectMonitor {
* Sent when a folder changed.
* @param folder The file that was changed
* @param kind The change kind. This is equivalent to {@link IResourceDelta#getKind()}
+ * @param isAndroidProject whether the parent project is an Android Project
*/
- public void folderChanged(IFolder folder, int kind);
+ public void folderChanged(IFolder folder, int kind, boolean isAndroidProject);
}
/**
@@ -208,6 +211,8 @@ public final class GlobalProjectMonitor {
private IWorkspace mWorkspace;
+ private boolean mIsAndroidProject;
+
/**
* Delta visitor for resource changes.
*/
@@ -226,7 +231,7 @@ public final class GlobalProjectMonitor {
|| (bundle.kindMask & kind) != 0) {
try {
bundle.listener.fileChanged((IFile)r, delta.getMarkerDeltas(), kind,
- r.getFileExtension(), delta.getFlags());
+ r.getFileExtension(), delta.getFlags(), mIsAndroidProject);
} catch (Throwable t) {
AdtPlugin.log(t,"Failed to call IFileListener.fileChanged");
}
@@ -240,7 +245,7 @@ public final class GlobalProjectMonitor {
if (bundle.kindMask == ListenerBundle.MASK_NONE
|| (bundle.kindMask & kind) != 0) {
try {
- bundle.listener.folderChanged((IFolder)r, kind);
+ bundle.listener.folderChanged((IFolder)r, kind, mIsAndroidProject);
} catch (Throwable t) {
AdtPlugin.log(t,"Failed to call IFileListener.folderChanged");
}
@@ -248,11 +253,27 @@ public final class GlobalProjectMonitor {
}
return true;
} else if (type == IResource.PROJECT) {
+ IProject project = (IProject)r;
+
+ try {
+ mIsAndroidProject = project.hasNature(AdtConstants.NATURE_DEFAULT);
+ } catch (CoreException e) {
+ // this can only happen if the project does not exist or is not open, neither
+ // of which can happen here since we are processing changes in the project
+ // or at worst a project post-open event.
+ return false;
+ }
+
+ if (mIsAndroidProject == false) {
+ // for non android project, skip the project listeners but return true
+ // to visit the children and notify the IFileListeners
+ return true;
+ }
+
int flags = delta.getFlags();
if ((flags & IResourceDelta.OPEN) != 0) {
// the project is opening or closing.
- IProject project = (IProject)r;
if (project.isOpen()) {
// notify the listeners.
@@ -491,9 +512,15 @@ public final class GlobalProjectMonitor {
// notify the listeners.
for (IProjectListener pl : mProjectListeners) {
try {
- pl.projectDeleted(project);
- } catch (Throwable t) {
- AdtPlugin.log(t,"Failed to call IProjectListener.projectDeleted");
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT)) {
+ try {
+ pl.projectDeleted(project);
+ } catch (Throwable t) {
+ AdtPlugin.log(t,"Failed to call IProjectListener.projectDeleted");
+ }
+ }
+ } catch (CoreException e) {
+ // just ignore this project.
}
}
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
index ccd1666..68c2257 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ProjectResources.java
@@ -16,6 +16,7 @@
package com.android.ide.eclipse.adt.internal.resources.manager;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.IntArrayWrapper;
@@ -26,6 +27,7 @@ import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFolderWrapper;
+import com.android.io.IAbstractFolder;
import com.android.resources.ResourceType;
import com.android.util.Pair;
@@ -48,6 +50,7 @@ import java.util.Map.Entry;
* on the fly.</li>
*</ul>
*/
+@SuppressWarnings("deprecation")
public class ProjectResources extends ResourceRepository {
// project resources are defined as 0x7FXX#### where XX is the resource type (layout, drawable,
// etc...). Using FF as the type allows for 255 resource types before we get a collision
@@ -65,12 +68,18 @@ public class ProjectResources extends ResourceRepository {
private final IProject mProject;
+ public static ProjectResources create(IProject project) {
+ IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
+
+ return new ProjectResources(project, new IFolderWrapper(resFolder));
+ }
+
/**
* Makes a ProjectResources for a given <var>project</var>.
* @param project the project.
*/
- public ProjectResources(IProject project) {
- super(false /*isFrameworkRepository*/);
+ private ProjectResources(IProject project, IAbstractFolder resFolder) {
+ super(resFolder, false /*isFrameworkRepository*/);
mProject = project;
}
@@ -85,6 +94,7 @@ public class ProjectResources extends ResourceRepository {
@NonNull
public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
@NonNull FolderConfiguration referenceConfig) {
+ ensureInitialized();
Map<ResourceType, Map<String, ResourceValue>> resultMap =
new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
index e1a12d7..e407b6a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/resources/manager/ResourceManager.java
@@ -49,7 +49,6 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -151,14 +150,14 @@ public final class ResourceManager {
/**
* Returns the resources of a project.
* @param project The project
- * @return a ProjectResources object or null if none was found.
+ * @return a ProjectResources object
*/
public ProjectResources getProjectResources(IProject project) {
synchronized (mMap) {
ProjectResources resources = mMap.get(project);
if (resources == null) {
- resources = new ProjectResources(project);
+ resources = ProjectResources.create(project);
mMap.put(project, resources);
}
@@ -253,7 +252,7 @@ public final class ResourceManager {
// if it doesn't exist, we create it.
if (resources == null) {
- resources = new ProjectResources(project);
+ resources = ProjectResources.create(project);
mMap.put(project, resources);
}
}
@@ -422,6 +421,15 @@ public final class ResourceManager {
for (IResourceDelta delta : projectDeltas) {
if (delta.getResource() instanceof IProject) {
IProject project = (IProject) delta.getResource();
+
+ try {
+ if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
+ continue;
+ }
+ } catch (CoreException e) {
+ // only happens if the project is closed or doesn't exist.
+ }
+
IdeScanningContext context =
new IdeScanningContext(getProjectResources(project), project, true);
@@ -482,16 +490,11 @@ public final class ResourceManager {
FolderWrapper frameworkRes = new FolderWrapper(osResourcesPath);
if (frameworkRes.exists()) {
- FrameworkResources resources = new FrameworkResources();
-
- try {
- resources.loadResources(frameworkRes);
- resources.loadPublicResources(frameworkRes, AdtPlugin.getDefault());
- return resources;
- } catch (IOException e) {
- // since we test that folders are folders, and files are files, this shouldn't
- // happen. We can ignore it.
- }
+ FrameworkResources resources = new FrameworkResources(frameworkRes);
+
+ resources.loadResources();
+ resources.loadPublicResources(AdtPlugin.getDefault());
+ return resources;
}
return null;
@@ -503,62 +506,13 @@ public final class ResourceManager {
*/
private void createProject(IProject project) {
if (project.isOpen()) {
- try {
- if (project.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
- return;
- }
- } catch (CoreException e1) {
- // can't check the nature of the project? ignore it.
- return;
- }
-
- IFolder resourceFolder = project.getFolder(SdkConstants.FD_RESOURCES);
-
- ProjectResources projectResources;
synchronized (mMap) {
- projectResources = mMap.get(project);
+ ProjectResources projectResources = mMap.get(project);
if (projectResources == null) {
- projectResources = new ProjectResources(project);
+ projectResources = ProjectResources.create(project);
mMap.put(project, projectResources);
}
}
- IdeScanningContext context = new IdeScanningContext(projectResources, project, true);
-
- if (resourceFolder != null && resourceFolder.exists()) {
- try {
- IResource[] resources = resourceFolder.members();
-
- for (IResource res : resources) {
- if (res.getType() == IResource.FOLDER) {
- IFolder folder = (IFolder)res;
- ResourceFolder resFolder = projectResources.processFolder(
- new IFolderWrapper(folder));
-
- if (resFolder != null) {
- // now we process the content of the folder
- IResource[] files = folder.members();
-
- for (IResource fileRes : files) {
- if (fileRes.getType() == IResource.FILE) {
- IFile file = (IFile)fileRes;
-
- context.startScanning(file);
-
- resFolder.processFile(new IFileWrapper(file),
- ResourceHelper.getResourceDeltaKind(
- IResourceDelta.ADDED), context);
-
- context.finishScanning(file);
- }
- }
- }
- }
- }
- } catch (CoreException e) {
- // This happens if the project is closed or if the folder doesn't exist.
- // Since we already test for that, we can ignore this exception.
- }
- }
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java
index 26afebd..2396a4c 100755
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AdtConsoleSdkLog.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.sdk;
import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.utils.ILogger;
@@ -28,7 +29,7 @@ public class AdtConsoleSdkLog implements ILogger {
private static final String TAG = "SDK Manager"; //$NON-NLS-1$
@Override
- public void error(Throwable t, String errorFormat, Object... args) {
+ public void error(@Nullable Throwable t, @Nullable String errorFormat, Object... args) {
if (t != null) {
AdtPlugin.logAndPrintError(t, TAG, "Error: " + errorFormat, args);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
index 161d567..c4eb37f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
@@ -17,6 +17,7 @@
package com.android.ide.eclipse.adt.internal.sdk;
import com.android.SdkConstants;
+import com.google.common.io.Closeables;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
@@ -205,6 +206,7 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader
* @throws InvalidAttributeValueException
* @throws ClassFormatError
*/
+ @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
@Override
public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
String packageFilter,
@@ -223,43 +225,47 @@ public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader
// create streams to read the intermediary archive
FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
ZipInputStream zis = new ZipInputStream(fis);
- ZipEntry entry;
- while ((entry = zis.getNextEntry()) != null) {
- // get the name of the entry and convert to a class binary name
- String entryPath = entry.getName();
- if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
- // only accept class files
- continue;
- }
- if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
- // only accept stuff from the requested root package.
- continue;
- }
- String className = entryPathToClassName(entryPath);
-
- Class<?> loaded_class = mClassCache.get(className);
- if (loaded_class == null) {
- byte[] data = mEntryCache.get(className);
- if (data == null) {
- // Get the class and cache it
- long entrySize = entry.getSize();
- if (entrySize > Integer.MAX_VALUE) {
- throw new InvalidAttributeValueException();
+ try {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry and convert to a class binary name
+ String entryPath = entry.getName();
+ if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
+ // only accept class files
+ continue;
+ }
+ if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
+ // only accept stuff from the requested root package.
+ continue;
+ }
+ String className = entryPathToClassName(entryPath);
+
+ Class<?> loaded_class = mClassCache.get(className);
+ if (loaded_class == null) {
+ byte[] data = mEntryCache.get(className);
+ if (data == null) {
+ // Get the class and cache it
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+ data = readZipData(zis, (int)entrySize);
}
- data = readZipData(zis, (int)entrySize);
+ loaded_class = defineAndCacheClass(className, data);
}
- loaded_class = defineAndCacheClass(className, data);
- }
- for (Class<?> superClass = loaded_class.getSuperclass();
- superClass != null;
- superClass = superClass.getSuperclass()) {
- String superName = superClass.getCanonicalName();
- if (mClassesFound.containsKey(superName)) {
- mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
- break;
+ for (Class<?> superClass = loaded_class.getSuperclass();
+ superClass != null;
+ superClass = superClass.getSuperclass()) {
+ String superName = superClass.getCanonicalName();
+ if (mClassesFound.containsKey(superName)) {
+ mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
+ break;
+ }
}
}
+ } finally {
+ Closeables.closeQuietly(zis);
}
return mClassesFound;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
index 64053ad..4628509 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/ProjectState.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.sdk;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
@@ -160,6 +161,7 @@ public final class ProjectState {
private final IProject mProject;
private final ProjectProperties mProperties;
private IAndroidTarget mTarget;
+ private BuildToolInfo mBuildToolInfo;
/**
* list of libraries. Access to this list must be protected by
@@ -240,6 +242,23 @@ public final class ProjectState {
return mTarget;
}
+ public void setBuildToolInfo(BuildToolInfo buildToolInfo) {
+ mBuildToolInfo = buildToolInfo;
+ }
+
+ public BuildToolInfo getBuildToolInfo() {
+ return mBuildToolInfo;
+ }
+
+ /**
+ * Returns the build tools version from the project's properties.
+ * @return the value or null
+ */
+ @Nullable
+ public String getBuildToolInfoVersion() {
+ return mProperties.getProperty(ProjectProperties.PROPERTY_BUILD_TOOLS);
+ }
+
public static class LibraryDifference {
public boolean removed = false;
public boolean added = false;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
index 1a299d9..74f08c2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/Sdk.java
@@ -43,18 +43,21 @@ import com.android.ide.eclipse.adt.internal.sdk.ProjectState.LibraryState;
import com.android.io.StreamException;
import com.android.prefs.AndroidLocation.AndroidLocationException;
import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.SdkManager;
-import com.android.sdklib.devices.Device;
import com.android.sdklib.devices.DeviceManager;
import com.android.sdklib.internal.avd.AvdManager;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
import com.android.sdklib.internal.project.ProjectPropertiesWorkingCopy;
+import com.android.sdklib.repository.FullRevision;
import com.android.utils.ILogger;
+import com.google.common.collect.Maps;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
@@ -131,7 +134,7 @@ public final class Sdk {
}
private final SdkManager mManager;
- private final DexWrapper mDexWrapper;
+ private final Map<String, DexWrapper> mDexWrappers = Maps.newHashMap();
private final AvdManager mAvdManager;
private final DeviceManager mDeviceManager;
@@ -236,7 +239,8 @@ public final class Sdk {
final ArrayList<String> logMessages = new ArrayList<String>();
ILogger log = new ILogger() {
@Override
- public void error(Throwable throwable, String errorFormat, Object... arg) {
+ public void error(@Nullable Throwable throwable, @Nullable String errorFormat,
+ Object... arg) {
if (errorFormat != null) {
logMessages.add(String.format("Error: " + errorFormat, arg));
}
@@ -265,17 +269,6 @@ public final class Sdk {
// get an SdkManager object for the location
SdkManager manager = SdkManager.createManager(sdkLocation, log);
if (manager != null) {
- // load DX.
- DexWrapper dexWrapper = new DexWrapper();
- String dexLocation =
- sdkLocation + File.separator +
- SdkConstants.OS_SDK_PLATFORM_TOOLS_LIB_FOLDER + SdkConstants.FN_DX_JAR;
- IStatus res = dexWrapper.loadDex(dexLocation);
- if (res != Status.OK_STATUS) {
- log.error(null, res.getMessage());
- dexWrapper = null;
- }
-
// create the AVD Manager
AvdManager avdManager = null;
try {
@@ -283,7 +276,7 @@ public final class Sdk {
} catch (AndroidLocationException e) {
log.error(e, "Error parsing the AVDs");
}
- sCurrentSdk = new Sdk(manager, dexWrapper, avdManager);
+ sCurrentSdk = new Sdk(manager, avdManager);
return sCurrentSdk;
} else {
StringBuilder sb = new StringBuilder("Error Loading the SDK:\n");
@@ -329,7 +322,7 @@ public final class Sdk {
* @param log The logger for the {@link SdkManager}.
* @return A new {@link SdkManager} parsing the same location.
*/
- public @NonNull SdkManager getNewSdkManager(@NonNull ILogger log) {
+ public @Nullable SdkManager getNewSdkManager(@NonNull ILogger log) {
return SdkManager.createManager(getSdkLocation(), log);
}
@@ -376,6 +369,24 @@ public final class Sdk {
return mManager.getTargetFromHashString(hash);
}
+ @Nullable
+ public BuildToolInfo getBuildToolInfo(@Nullable String buildToolVersion) {
+ if (buildToolVersion != null) {
+ try {
+ return mManager.getBuildTool(FullRevision.parseRevision(buildToolVersion));
+ } catch (Exception e) {
+ // ignore, return null below.
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ public BuildToolInfo getLatestBuildTool() {
+ return mManager.getLatestBuildTool(false /*isPreview*/);
+ }
+
/**
* Initializes a new project with a target. This creates the <code>project.properties</code>
* file.
@@ -487,7 +498,7 @@ public final class Sdk {
// try to resolve the target
if (AdtPlugin.getDefault().getSdkLoadStatus() == LoadStatus.LOADED) {
- sCurrentSdk.loadTarget(state);
+ sCurrentSdk.loadTargetAndBuildTools(state);
}
}
@@ -513,26 +524,84 @@ public final class Sdk {
}
/**
- * Loads the {@link IAndroidTarget} for a given project.
+ * Loads the {@link IAndroidTarget} and BuildTools for a given project.
* <p/>This method will get the target hash string from the project properties, and resolve
* it to an {@link IAndroidTarget} object and store it inside the {@link ProjectState}.
* @param state the state representing the project to load.
* @return the target that was loaded.
*/
@Nullable
- public IAndroidTarget loadTarget(ProjectState state) {
+ public IAndroidTarget loadTargetAndBuildTools(ProjectState state) {
IAndroidTarget target = null;
if (state != null) {
String hash = state.getTargetHashString();
if (hash != null) {
state.setTarget(target = getTargetFromHashString(hash));
}
+
+ String markerMessage = null;
+ String buildToolInfoVersion = state.getBuildToolInfoVersion();
+ if (buildToolInfoVersion != null) {
+ BuildToolInfo buildToolsInfo = getBuildToolInfo(buildToolInfoVersion);
+
+ if (buildToolsInfo != null) {
+ state.setBuildToolInfo(buildToolsInfo);
+ } else {
+ markerMessage = String.format("Unable to resolve %s property value '%s'",
+ ProjectProperties.PROPERTY_BUILD_TOOLS,
+ buildToolInfoVersion);
+ }
+ } else {
+ // this is ok, we'll use the latest one automatically.
+ state.setBuildToolInfo(null);
+ }
+
+ handleBuildToolsMarker(state.getProject(), markerMessage);
}
return target;
}
/**
+ * Adds or edit a build tools marker from the given project. This is done through a Job.
+ * @param project the project
+ * @param markerMessage the message. if null the marker is removed.
+ */
+ private void handleBuildToolsMarker(final IProject project, final String markerMessage) {
+ Job markerJob = new Job("Android SDK: Build Tools Marker") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ if (project.isAccessible()) {
+ // always delete existing marker first
+ project.deleteMarkers(AdtConstants.MARKER_BUILD_TOOLS, true,
+ IResource.DEPTH_ZERO);
+
+ // add the new one if needed.
+ if (markerMessage != null) {
+ BaseProjectHelper.markProject(project,
+ AdtConstants.MARKER_BUILD_TOOLS,
+ markerMessage, IMarker.SEVERITY_ERROR,
+ IMarker.PRIORITY_HIGH);
+ }
+ }
+ } catch (CoreException e2) {
+ AdtPlugin.log(e2, null);
+ // Don't return e2.getStatus(); the job control will then produce
+ // a popup with this error, which isn't very interesting for the
+ // user.
+ }
+
+ return Status.OK_STATUS;
+ }
+ };
+
+ // build jobs are run after other interactive jobs
+ markerJob.setPriority(Job.BUILD);
+ markerJob.schedule();
+ }
+
+ /**
* Checks and loads (if needed) the data for a given target.
* <p/> The data is loaded in a separate {@link Job}, and opened editors will be notified
* through their implementation of {@link ITargetChangeListener#onTargetLoaded(IAndroidTarget)}.
@@ -668,8 +737,38 @@ public final class Sdk {
* Returns a {@link DexWrapper} object to be used to execute dx commands. If dx.jar was not
* loaded properly, then this will return <code>null</code>.
*/
- public DexWrapper getDexWrapper() {
- return mDexWrapper;
+ @Nullable
+ public DexWrapper getDexWrapper(@Nullable BuildToolInfo buildToolInfo) {
+ if (buildToolInfo == null) {
+ return null;
+ }
+ synchronized (LOCK) {
+ String dexLocation = buildToolInfo.getPath(BuildToolInfo.PathId.DX_JAR);
+ DexWrapper dexWrapper = mDexWrappers.get(dexLocation);
+
+ if (dexWrapper == null) {
+ // load DX.
+ dexWrapper = new DexWrapper();
+ IStatus res = dexWrapper.loadDex(dexLocation);
+ if (res != Status.OK_STATUS) {
+ AdtPlugin.log(null, res.getMessage());
+ dexWrapper = null;
+ } else {
+ mDexWrappers.put(dexLocation, dexWrapper);
+ }
+ }
+
+ return dexWrapper;
+ }
+ }
+
+ public void unloadDexWrappers() {
+ synchronized (LOCK) {
+ for (DexWrapper wrapper : mDexWrappers.values()) {
+ wrapper.unload();
+ }
+ mDexWrappers.clear();
+ }
}
/**
@@ -702,12 +801,6 @@ public final class Sdk {
return mDeviceManager;
}
- /** Returns the devices provided by the SDK, including user created devices */
- @NonNull
- public List<Device> getDevices() {
- return mDeviceManager.getDevices(getSdkLocation());
- }
-
/**
* Returns a list of {@link ProjectState} representing projects depending, directly or
* indirectly on a given library project.
@@ -769,9 +862,8 @@ public final class Sdk {
}
}
- private Sdk(SdkManager manager, DexWrapper dexWrapper, AvdManager avdManager) {
+ private Sdk(SdkManager manager, AvdManager avdManager) {
mManager = manager;
- mDexWrapper = dexWrapper;
mAvdManager = avdManager;
// listen to projects closing
@@ -784,16 +876,16 @@ public final class Sdk {
IResourceDelta.CHANGED | IResourceDelta.ADDED | IResourceDelta.REMOVED);
// pre-compute some paths
- mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() +
+ mDocBaseUrl = getDocumentationBaseUrl(manager.getLocation() +
SdkConstants.OS_SDK_DOCS_FOLDER);
- mDeviceManager = new DeviceManager(AdtPlugin.getDefault());
+ mDeviceManager = DeviceManager.createInstance(manager.getLocation(),
+ AdtPlugin.getDefault());
// update whatever ProjectState is already present with new IAndroidTarget objects.
synchronized (LOCK) {
for (Entry<IProject, ProjectState> entry: sProjectStateMap.entrySet()) {
- entry.getValue().setTarget(
- getTargetFromHashString(entry.getValue().getTargetHashString()));
+ loadTargetAndBuildTools(entry.getValue());
}
}
}
@@ -880,16 +972,6 @@ public final class Sdk {
}
private void onProjectRemoved(IProject removedProject, boolean deleted) {
- try {
- if (removedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
- return;
- }
- } catch (CoreException e) {
- // this can only happen if the project does not exist or is not open, neither
- // of which can happen here since we're processing a Project removed/deleted event
- // which is processed before the project is actually removed/closed.
- }
-
if (DEBUG) {
System.out.println(">>> CLOSED: " + removedProject.getName());
}
@@ -963,15 +1045,6 @@ public final class Sdk {
}
private void onProjectOpened(final IProject openedProject) {
- try {
- if (openedProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
- return;
- }
- } catch (CoreException e) {
- // this can only happen if the project does not exist or is not open, neither
- // of which can happen here since we're processing a Project opened event.
- }
-
ProjectState openedState = getProjectState(openedProject);
if (openedState != null) {
@@ -1052,7 +1125,11 @@ public final class Sdk {
private IFileListener mFileListener = new IFileListener() {
@Override
public void fileChanged(final @NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas,
- int kind, @Nullable String extension, int flags) {
+ int kind, @Nullable String extension, int flags, boolean isAndroidPRoject) {
+ if (!isAndroidPRoject) {
+ return;
+ }
+
if (SdkConstants.FN_PROJECT_PROPERTIES.equals(file.getName()) &&
file.getParent() == file.getProject()) {
try {
@@ -1060,13 +1137,9 @@ public final class Sdk {
// the target.
IProject iProject = file.getProject();
- if (iProject.hasNature(AdtConstants.NATURE_DEFAULT) == false) {
- return;
- }
-
ProjectState state = Sdk.getProjectState(iProject);
- // get the current target
+ // get the current target and build tools
IAndroidTarget oldTarget = state.getTarget();
// get the current library flag
@@ -1075,7 +1148,7 @@ public final class Sdk {
LibraryDifference diff = state.reloadProperties();
// load the (possibly new) target.
- IAndroidTarget newTarget = loadTarget(state);
+ IAndroidTarget newTarget = loadTargetAndBuildTools(state);
// reload the libraries if needed
if (diff.hasDiff()) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java
index ce36457..15453fb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ConfigurationSelector.java
@@ -22,6 +22,7 @@ import com.android.ide.common.resources.configuration.DensityQualifier;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.KeyboardStateQualifier;
import com.android.ide.common.resources.configuration.LanguageQualifier;
+import com.android.ide.common.resources.configuration.LayoutDirectionQualifier;
import com.android.ide.common.resources.configuration.NavigationMethodQualifier;
import com.android.ide.common.resources.configuration.NavigationStateQualifier;
import com.android.ide.common.resources.configuration.NetworkCodeQualifier;
@@ -44,6 +45,7 @@ import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.resources.Density;
import com.android.resources.Keyboard;
import com.android.resources.KeyboardState;
+import com.android.resources.LayoutDirection;
import com.android.resources.Navigation;
import com.android.resources.NavigationState;
import com.android.resources.NightMode;
@@ -433,6 +435,8 @@ public class ConfigurationSelector extends Composite {
mUiMap.put(NetworkCodeQualifier.class, new MNCEdit(mQualifierEditParent));
mUiMap.put(LanguageQualifier.class, new LanguageEdit(mQualifierEditParent));
mUiMap.put(RegionQualifier.class, new RegionEdit(mQualifierEditParent));
+ mUiMap.put(LayoutDirectionQualifier.class,
+ new LayoutDirectionEdit(mQualifierEditParent));
mUiMap.put(SmallestScreenWidthQualifier.class,
new SmallestScreenWidthEdit(mQualifierEditParent));
mUiMap.put(ScreenWidthQualifier.class, new ScreenWidthEdit(mQualifierEditParent));
@@ -1017,6 +1021,65 @@ public class ConfigurationSelector extends Composite {
}
/**
+ * Edit widget for {@link LayoutDirectionQualifier}.
+ */
+ private class LayoutDirectionEdit extends QualifierEditBase {
+
+ private Combo mDirection;
+
+ public LayoutDirectionEdit(Composite parent) {
+ super(parent, LayoutDirectionQualifier.NAME);
+
+ mDirection = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
+ fillCombo(mDirection, LayoutDirection.values());
+
+ mDirection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
+ mDirection.addSelectionListener(new SelectionListener() {
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ onDirectionChange();
+ }
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ onDirectionChange();
+ }
+ });
+ }
+
+ protected void onDirectionChange() {
+ // update the current config
+ int index = mDirection.getSelectionIndex();
+
+ if (index != -1) {
+ mSelectedConfiguration.setLayoutDirectionQualifier(new LayoutDirectionQualifier(
+ LayoutDirection.getByIndex(index)));
+ } else {
+ // empty selection, means no qualifier.
+ // Since the qualifier classes are immutable, and we don't want to
+ // remove the qualifier from the configuration, we create a new default one.
+ mSelectedConfiguration.setLayoutDirectionQualifier(
+ new LayoutDirectionQualifier());
+ }
+
+ // notify of change
+ onChange(true /* keepSelection */);
+ }
+
+ @Override
+ public void setQualifier(ResourceQualifier qualifier) {
+ LayoutDirectionQualifier q = (LayoutDirectionQualifier)qualifier;
+
+ LayoutDirection value = q.getValue();
+ if (value == null) {
+ mDirection.clearSelection();
+ } else {
+ mDirection.select(LayoutDirection.getIndex(value));
+ }
+ }
+ }
+
+
+ /**
* Edit widget for {@link SmallestScreenWidthQualifier}.
*/
private class SmallestScreenWidthEdit extends QualifierEditBase {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java
index 066f4a1..c2a5f71 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/MarginChooser.java
@@ -15,14 +15,10 @@
*/
package com.android.ide.eclipse.adt.internal.ui;
-import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
-import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
-import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.resources.ResourceType;
-import org.eclipse.core.resources.IProject;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
@@ -219,19 +215,13 @@ public class MarginChooser extends SelectionStatusDialog implements Listener {
// Button pressed - open resource chooser
if (event.widget instanceof Button) {
Button button = (Button) event.widget;
+ Text text = (Text) button.getData(PROP_TEXTFIELD);
// Open a resource chooser dialog for specified resource type.
- IProject project = mEditor.getProject();
- ProjectResources projectRepository = ResourceManager.getInstance()
- .getProjectResources(project);
- ResourceRepository frameworkRepository = mTargetData.getFrameworkResources();
- ResourceChooser dlg = new ResourceChooser(project, ResourceType.DIMEN,
- projectRepository, frameworkRepository, getShell());
- dlg.setResourceResolver(mEditor.getResourceResolver());
- Text text = (Text) button.getData(PROP_TEXTFIELD);
- dlg.setCurrentResource(text.getText().trim());
- if (dlg.open() == Window.OK) {
- text.setText(dlg.getCurrentResource());
+ ResourceChooser chooser = ResourceChooser.create(mEditor, ResourceType.DIMEN)
+ .setCurrentResource(text.getText().trim());
+ if (chooser.open() == Window.OK) {
+ text.setText(chooser.getCurrentResource());
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
index 4906730..6c62865 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ReferenceChooserDialog.java
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.ui;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
import com.android.resources.ResourceType;
@@ -142,6 +143,11 @@ public class ReferenceChooserDialog extends SelectionStatusDialog {
// create the "New Resource" button
createNewResButtons(top);
+ Composite workaround = PropertyFactory.addWorkaround(top);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ }
+
return top;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
index 1291af8..ce828cf 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourceChooser.java
@@ -28,16 +28,20 @@ import com.android.ide.common.resources.ResourceResolver;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.assetstudio.OpenCreateAssetSetWizardAction;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.GraphicalEditorPart;
+import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFactory;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringWizard;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceType;
import com.android.utils.Pair;
+import com.google.common.collect.Maps;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
@@ -71,11 +75,13 @@ import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog;
import org.eclipse.ui.dialogs.SelectionStatusDialog;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -90,7 +96,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
private Pattern mProjectResourcePattern;
private ResourceType mResourceType;
- private final ResourceRepository mProjectResources;
+ private final List<ResourceRepository> mProjectResources;
private final ResourceRepository mFrameworkResources;
private Pattern mSystemResourcePattern;
private Button mProjectButton;
@@ -140,10 +146,12 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
* @param frameworkResources The Framework resource repository
* @param parent the parent shell
*/
- public ResourceChooser(IProject project, ResourceType type,
- ResourceRepository projectResources,
- ResourceRepository frameworkResources,
- Shell parent) {
+ private ResourceChooser(
+ @NonNull IProject project,
+ @NonNull ResourceType type,
+ @NonNull List<ResourceRepository> projectResources,
+ @Nullable ResourceRepository frameworkResources,
+ @NonNull Shell parent) {
super(parent, new ResourceLabelProvider());
mProject = project;
@@ -163,14 +171,85 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
}
/**
+ * Creates a new {@link ResourceChooser}
+ *
+ * @param editor the associated layout editor
+ * @param type the resource type to choose
+ * @return a new {@link ResourceChooser}
+ */
+ @NonNull
+ public static ResourceChooser create(
+ @NonNull GraphicalEditorPart editor,
+ @NonNull ResourceType type) {
+ IProject project = editor.getProject();
+ Shell parent = editor.getCanvasControl().getShell();
+ AndroidTargetData targetData = editor.getEditorDelegate().getEditor().getTargetData();
+ ResourceChooser chooser = create(project, type, targetData, parent);
+
+ // When editing Strings, allow editing the value text directly. When we
+ // get inline editing support (where values entered directly into the
+ // textual widget are translated automatically into a resource) this can
+ // go away.
+ if (type == ResourceType.STRING) {
+ chooser.setResourceResolver(editor.getResourceResolver());
+ chooser.setShowValueText(true);
+ } else if (type == ResourceType.DIMEN || type == ResourceType.INTEGER) {
+ chooser.setResourceResolver(editor.getResourceResolver());
+ }
+
+ chooser.setPreviewHelper(new ResourcePreviewHelper(chooser, editor));
+ return chooser;
+ }
+
+ /**
+ * Creates a new {@link ResourceChooser}
+ *
+ * @param project the associated project
+ * @param type the resource type to choose
+ * @param targetData the associated framework target data
+ * @param parent the target shell
+ * @return a new {@link ResourceChooser}
+ */
+ @NonNull
+ public static ResourceChooser create(
+ @NonNull IProject project,
+ @NonNull ResourceType type,
+ @Nullable AndroidTargetData targetData,
+ @NonNull Shell parent) {
+ ResourceManager manager = ResourceManager.getInstance();
+
+ List<ResourceRepository> projectResources = new ArrayList<ResourceRepository>();
+ ProjectResources resources = manager.getProjectResources(project);
+ projectResources.add(resources);
+
+ // Add in library project resources
+ ProjectState projectState = Sdk.getProjectState(project);
+ if (projectState != null) {
+ List<IProject> libraries = projectState.getFullLibraryProjects();
+ if (libraries != null && !libraries.isEmpty()) {
+ for (IProject library : libraries) {
+ projectResources.add(manager.getProjectResources(library));
+ }
+ }
+ }
+
+ ResourceRepository frameworkResources =
+ targetData != null ? targetData.getFrameworkResources() : null;
+ return new ResourceChooser(project, type, projectResources, frameworkResources, parent);
+ }
+
+ /**
* Sets whether this dialog should show the value field as a separate text
* value (and take the resulting value of the dialog from this text field
* rather than from the selection)
*
* @param showValueText if true, show the value text field
+ * @return this, for constructor chaining
*/
- public void setShowValueText(boolean showValueText) {
+ public ResourceChooser setShowValueText(boolean showValueText) {
mShowValueText = showValueText;
+
+ return this;
}
/**
@@ -178,9 +257,12 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
* selection
*
* @param resourceResolver the resource resolver to use
+ * @return this, for constructor chaining
*/
- public void setResourceResolver(ResourceResolver resourceResolver) {
+ public ResourceChooser setResourceResolver(ResourceResolver resourceResolver) {
mResourceResolver = resourceResolver;
+
+ return this;
}
/**
@@ -188,9 +270,25 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
* resources, if any
*
* @param previewHelper the helper to use
+ * @return this, for constructor chaining
*/
- public void setPreviewHelper(ResourcePreviewHelper previewHelper) {
+ public ResourceChooser setPreviewHelper(ResourcePreviewHelper previewHelper) {
mPreviewHelper = previewHelper;
+
+ return this;
+ }
+
+ /**
+ * Sets the initial dialog size
+ *
+ * @param width the initial width
+ * @param height the initial height
+ * @return this, for constructor chaining
+ */
+ public ResourceChooser setInitialSize(int width, int height) {
+ setSize(width, height);
+
+ return this;
}
@Override
@@ -220,20 +318,42 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
}
}
- public void setCurrentResource(String resource) {
+ /**
+ * Sets the currently selected item
+ *
+ * @param resource the resource url for the currently selected item
+ * @return this, for constructor chaining
+ */
+ public ResourceChooser setCurrentResource(@Nullable String resource) {
mCurrentResource = resource;
if (mShowValueText && mEditValueText != null) {
mEditValueText.setText(resource);
}
+
+ return this;
}
+ /**
+ * Returns the currently selected url
+ *
+ * @return the currently selected url
+ */
+ @Nullable
public String getCurrentResource() {
return mCurrentResource;
}
- public void setInputValidator(IInputValidator inputValidator) {
+ /**
+ * Sets the input validator to use, if any
+ *
+ * @param inputValidator the validator
+ * @return this, for constructor chaining
+ */
+ public ResourceChooser setInputValidator(@Nullable IInputValidator inputValidator) {
mInputValidator = inputValidator;
+
+ return this;
}
@Override
@@ -328,6 +448,9 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
}
}
});
+ if (mFrameworkResources == null) {
+ mSystemButton.setVisible(false);
+ }
}
/**
@@ -439,6 +562,11 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
data.verticalAlignment = GridData.BEGINNING;
mResolvedLabel.setLayoutData(data);
}
+
+ Composite workaround = PropertyFactory.addWorkaround(top);
+ if (workaround != null) {
+ workaround.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+ }
}
private void updateResolvedLabel() {
@@ -513,7 +641,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
@Nullable
private String createNewFile(ResourceType type) {
// Show a name/value dialog entering the key name and the value
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
if (shell == null) {
return null;
}
@@ -521,7 +649,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
ResourceNameValidator validator = ResourceNameValidator.create(true /*allowXmlExtension*/,
mProject, mResourceType);
InputDialog d = new InputDialog(
- AdtPlugin.getDisplay().getActiveShell(),
+ AdtPlugin.getShell(),
"Enter name", // title
"Enter name",
"", //$NON-NLS-1$
@@ -546,7 +674,7 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
@Nullable
private String createNewValue(ResourceType type) {
// Show a name/value dialog entering the key name and the value
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
+ Shell shell = AdtPlugin.getShell();
if (shell == null) {
return null;
}
@@ -593,7 +721,19 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
private ResourceItem[] setupResourceList() {
Collection<ResourceItem> items = null;
if (mProjectButton.getSelection()) {
- items = mProjectResources.getResourceItemsOfType(mResourceType);
+ if (mProjectResources.size() == 1) {
+ items = mProjectResources.get(0).getResourceItemsOfType(mResourceType);
+ } else {
+ Map<String, ResourceItem> merged = Maps.newHashMapWithExpectedSize(200);
+ for (ResourceRepository repository : mProjectResources) {
+ for (ResourceItem item : repository.getResourceItemsOfType(mResourceType)) {
+ if (!merged.containsKey(item.getName())) {
+ merged.put(item.getName(), item);
+ }
+ }
+ }
+ items = merged.values();
+ }
} else if (mSystemButton.getSelection()) {
items = mFrameworkResources.getResourceItemsOfType(mResourceType);
}
@@ -799,50 +939,18 @@ public class ResourceChooser extends AbstractElementListSelectionDialog implemen
@NonNull GraphicalEditorPart graphicalEditor,
@NonNull ResourceType type,
String currentValue, IInputValidator validator) {
- AndroidXmlEditor editor = graphicalEditor.getEditorDelegate().getEditor();
- IProject project = editor.getProject();
- if (project != null) {
- // get the resource repository for this project and the system resources.
- ResourceRepository projectRepository = ResourceManager.getInstance()
- .getProjectResources(project);
- Shell shell = AdtPlugin.getDisplay().getActiveShell();
- if (shell == null) {
- return null;
- }
-
- AndroidTargetData data = editor.getTargetData();
- ResourceRepository systemRepository = data.getFrameworkResources();
-
- // open a resource chooser dialog for specified resource type.
- ResourceChooser dlg = new ResourceChooser(project, type, projectRepository,
- systemRepository, shell);
- dlg.setPreviewHelper(new ResourcePreviewHelper(dlg, graphicalEditor));
-
- // When editing Strings, allow editing the value text directly. When we
- // get inline editing support (where values entered directly into the
- // textual widget are translated automatically into a resource) this can
- // go away.
- if (type == ResourceType.STRING) {
- dlg.setResourceResolver(graphicalEditor.getResourceResolver());
- dlg.setShowValueText(true);
- } else if (type == ResourceType.DIMEN || type == ResourceType.INTEGER) {
- dlg.setResourceResolver(graphicalEditor.getResourceResolver());
- }
-
- if (validator != null) {
- // Ensure wide enough to accommodate validator error message
- dlg.setSize(85, 10);
- dlg.setInputValidator(validator);
- }
-
- dlg.setCurrentResource(currentValue);
-
- int result = dlg.open();
- if (result == ResourceChooser.CLEAR_RETURN_CODE) {
- return ""; //$NON-NLS-1$
- } else if (result == Window.OK) {
- return dlg.getCurrentResource();
- }
+ ResourceChooser chooser = create(graphicalEditor, type).
+ setCurrentResource(currentValue);
+ if (validator != null) {
+ // Ensure wide enough to accommodate validator error message
+ chooser.setSize(85, 10);
+ chooser.setInputValidator(validator);
+ }
+ int result = chooser.open();
+ if (result == ResourceChooser.CLEAR_RETURN_CODE) {
+ return ""; //$NON-NLS-1$
+ } else if (result == Window.OK) {
+ return chooser.getCurrentResource();
}
return null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
index eeaca0c..afd1df9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/ui/ResourcePreviewHelper.java
@@ -166,7 +166,7 @@ public class ResourcePreviewHelper {
if (image == null) {
RenderService renderService = RenderService.create(mEditor);
- renderService.setSize(WIDTH, HEIGHT);
+ renderService.setOverrideRenderSize(WIDTH, HEIGHT);
image = renderService.renderDrawable(drawable);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/utils/FingerprintUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/utils/FingerprintUtils.java
new file mode 100644
index 0000000..bfe301e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/utils/FingerprintUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.utils;
+
+import java.util.Locale;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+
+public class FingerprintUtils {
+
+ /**
+ * Returns the {@link Certificate} fingerprint as returned by <code>keytool</code>.
+ *
+ * @param certificate
+ * @param hashAlgorithm
+ */
+ public static String getFingerprint(Certificate cert, String hashAlgorithm) {
+ if (cert == null) {
+ return null;
+ }
+ try {
+ MessageDigest digest = MessageDigest.getInstance(hashAlgorithm);
+ return toHexadecimalString(digest.digest(cert.getEncoded()));
+ } catch(NoSuchAlgorithmException e) {
+ // ignore
+ } catch(CertificateEncodingException e) {
+ // ignore
+ }
+ return null;
+ }
+
+ private static String toHexadecimalString(byte[] value) {
+ StringBuffer sb = new StringBuffer();
+ int len = value.length;
+ for (int i = 0; i < len; i++) {
+ int num = ((int) value[i]) & 0xff;
+ if (num < 0x10) {
+ sb.append('0');
+ }
+ sb.append(Integer.toHexString(num));
+ if (i < len - 1) {
+ sb.append(':');
+ }
+ }
+ return sb.toString().toUpperCase(Locale.US);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java
index 8d8b688..65eee94 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java
@@ -71,15 +71,21 @@ public class AdtStartup implements IStartup, IWindowListener {
@Override
public void earlyStartup() {
- if (InstallDetails.isAndroidIdePackage()) {
- useBundledSdk();
+ if (!isSdkSpecified()) {
+ File bundledSdk = getBundledSdk();
+ if (bundledSdk != null) {
+ AdtPrefs.getPrefs().setSdkLocation(bundledSdk);
+ }
+ }
+
+ boolean showSdkInstallationPage = !isSdkSpecified() && isFirstTime();
+ boolean showOptInDialogPage = !mStore.hasPingId();
+
+ if (showSdkInstallationPage || showOptInDialogPage) {
+ showWelcomeWizard(showSdkInstallationPage, showOptInDialogPage);
}
- if (isFirstTime()) {
- showWelcomeWizard();
- // Usage statistics are sent after the wizard has run asynchronously (provided the
- // user opted in)
- } else if (mStore.isPingOptIn()) {
+ if (mStore.isPingOptIn()) {
sendUsageStats();
}
@@ -88,40 +94,37 @@ public class AdtStartup implements IStartup, IWindowListener {
AdtPlugin.getDefault().workbenchStarted();
}
- private void useBundledSdk() {
+ private boolean isSdkSpecified() {
String osSdkFolder = AdtPrefs.getPrefs().getOsSdkFolder();
+ return (osSdkFolder != null && !osSdkFolder.isEmpty());
+ }
- // sdk path is already set
- if (osSdkFolder != null && osSdkFolder.length() > 0) {
- return;
- }
-
- // The Android IDE bundle is structured as follows:
- // root
- // |--eclipse
- // |--sdk
- // So use the SDK folder that is
+ /**
+ * Returns the path to the bundled SDK if this is part of the ADT package.
+ * The ADT package has the following structure:
+ * root
+ * |--eclipse
+ * |--sdk
+ * @return path to bundled SDK, null if no valid bundled SDK detected.
+ */
+ private File getBundledSdk() {
Location install = Platform.getInstallLocation();
if (install != null && install.getURL() != null) {
- String toolsFolder = new File(install.getURL().getFile()).getParent();
+ File toolsFolder = new File(install.getURL().getFile()).getParentFile();
if (toolsFolder != null) {
- String osSdkPath = toolsFolder + File.separator + "sdk";
- if (AdtPlugin.getDefault().checkSdkLocationAndId(osSdkPath,
- new SdkValidator())) {
- AdtPrefs.getPrefs().setSdkLocation(new File(osSdkPath));
+ File sdkFolder = new File(toolsFolder, "sdk");
+ if (sdkFolder.exists() && AdtPlugin.getDefault().checkSdkLocationAndId(
+ sdkFolder.getAbsolutePath(),
+ new SdkValidator())) {
+ return sdkFolder;
}
}
}
+
+ return null;
}
private boolean isFirstTime() {
- // If we already have a known SDK location in our workspace then we know this
- // is not the first time this user is running ADT.
- String osSdkFolder = AdtPrefs.getPrefs().getOsSdkFolder();
- if (osSdkFolder != null && osSdkFolder.length() > 0) {
- return false;
- }
-
for (int i = 0; i < 2; i++) {
String osSdkPath = null;
@@ -251,14 +254,16 @@ public class AdtStartup implements IStartup, IWindowListener {
});
}
- private void showWelcomeWizard() {
+ private void showWelcomeWizard(final boolean showSdkInstallPage,
+ final boolean showUsageOptInPage) {
final IWorkbench workbench = PlatformUI.getWorkbench();
workbench.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
if (window != null) {
- WelcomeWizard wizard = new WelcomeWizard(mStore);
+ WelcomeWizard wizard = new WelcomeWizard(mStore, showSdkInstallPage,
+ showUsageOptInPage);
WizardDialog dialog = new WizardDialog(window.getShell(), wizard);
dialog.open();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java
index a81082b..916924e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java
@@ -41,16 +41,25 @@ import java.util.Set;
*/
public class WelcomeWizard extends Wizard {
private final DdmsPreferenceStore mStore;
+
private WelcomeWizardPage mWelcomePage;
private UsagePermissionPage mUsagePage;
+ private final boolean mShowWelcomePage;
+ private final boolean mShowUsagePage;
+
/**
* Creates a new {@link WelcomeWizard}
*
* @param store preferences for usage statistics collection etc
+ * @param showInstallSdkPage show page to install SDK's
+ * @param showUsageOptinPage show page to get user consent for usage data collection
*/
- public WelcomeWizard(DdmsPreferenceStore store) {
+ public WelcomeWizard(DdmsPreferenceStore store, boolean showInstallSdkPage,
+ boolean showUsageOptinPage) {
mStore = store;
+ mShowWelcomePage = showInstallSdkPage;
+ mShowUsagePage = showUsageOptinPage;
setWindowTitle("Welcome to Android Development");
ImageDescriptor image = AdtPlugin.getImageDescriptor("icons/android-64.png"); //$NON-NLS-1$
@@ -59,13 +68,15 @@ public class WelcomeWizard extends Wizard {
@Override
public void addPages() {
- mWelcomePage = new WelcomeWizardPage();
- addPage(mWelcomePage);
+ if (mShowWelcomePage) {
+ mWelcomePage = new WelcomeWizardPage();
+ addPage(mWelcomePage);
+ }
// It's possible that the user has already run the command line tools
// such as ddms and has agreed to usage statistics collection, but has never
// run ADT which is why the wizard was opened. No need to ask again.
- if (!mStore.isPingOptIn()) {
+ if (mShowUsagePage && !mStore.hasPingId()) {
mUsagePage = new UsagePermissionPage();
addPage(mUsagePage);
}
@@ -96,37 +107,40 @@ public class WelcomeWizard extends Wizard {
store.setPingOptIn(isUsageCollectionApproved);
}
- // Read out wizard settings immediately; we will perform the actual work
- // after the wizard window has been taken down and it's too late to read the
- // settings then
- final File path = mWelcomePage.getPath();
- final boolean installCommon = mWelcomePage.isInstallCommon();
- final boolean installLatest = mWelcomePage.isInstallLatest();
- final boolean createNew = mWelcomePage.isCreateNew();
-
- // Perform installation asynchronously since it takes a while.
- getShell().getDisplay().asyncExec(new Runnable() {
- @Override
- public void run() {
- if (createNew) {
- try {
- Set<Integer> apiLevels = new HashSet<Integer>();
- if (installCommon) {
- apiLevels.add(8);
- }
- if (installLatest) {
- apiLevels.add(AdtUpdateDialog.USE_MAX_REMOTE_API_LEVEL);
+ if (mWelcomePage != null) {
+ // Read out wizard settings immediately; we will perform the actual work
+ // after the wizard window has been taken down and it's too late to read the
+ // settings then
+ final File path = mWelcomePage.getPath();
+ final boolean installCommon = mWelcomePage.isInstallCommon();
+ final boolean installLatest = mWelcomePage.isInstallLatest();
+ final boolean createNew = mWelcomePage.isCreateNew();
+
+ // Perform installation asynchronously since it takes a while.
+ getShell().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (createNew) {
+ try {
+ Set<Integer> apiLevels = new HashSet<Integer>();
+ if (installCommon) {
+ apiLevels.add(8);
+ }
+ if (installLatest) {
+ apiLevels.add(AdtUpdateDialog.USE_MAX_REMOTE_API_LEVEL);
+ }
+ installSdk(path, apiLevels);
+ } catch (Exception e) {
+ AdtPlugin.logAndPrintError(e, "ADT Welcome Wizard",
+ "Installation failed");
}
- installSdk(path, apiLevels);
- } catch (Exception e) {
- AdtPlugin.logAndPrintError(e, "ADT Welcome Wizard", "Installation failed");
}
- }
- // Set SDK path after installation since this will trigger a SDK refresh.
- AdtPrefs.getPrefs().setSdkLocation(path);
- }
- });
+ // Set SDK path after installation since this will trigger a SDK refresh.
+ AdtPrefs.getPrefs().setSdkLocation(path);
+ }
+ });
+ }
// The wizard always succeeds, even if installation fails or is aborted
return true;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
index 35ef1c7..bcee887 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
@@ -99,7 +99,7 @@ public class WelcomeWizardPage extends WizardPage implements ModifyListener, Sel
mInstallCommonCheckbox = new Button(container, SWT.CHECK);
mInstallCommonCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3,
1));
- mInstallCommonCheckbox.setText("Install Android 2.2, a version which is supported by ~93% phones and tablets");
+ mInstallCommonCheckbox.setText("Install Android 2.2, a version which is supported by ~96% phones and tablets");
mInstallCommonCheckbox.addSelectionListener(this);
new Label(container, SWT.NONE);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java
index d7ace9a..62090d4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/ExportWizard.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.wizards.export;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.utils.FingerprintUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity;
import com.android.ide.eclipse.adt.internal.project.ExportHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
@@ -271,6 +272,14 @@ public final class ExportWizard extends Wizard implements IExportWizard {
if (entry != null) {
mPrivateKey = entry.getPrivateKey();
mCertificate = (X509Certificate)entry.getCertificate();
+
+ AdtPlugin.printToConsole(mProject,
+ String.format("New keystore %s has been created.",
+ mDestinationFile.getAbsolutePath()),
+ "Certificate fingerprints:",
+ String.format(" MD5 : %s", getCertMd5Fingerprint()),
+ String.format(" SHA1: %s", getCertSha1Fingerprint()));
+
} else {
// this really shouldn't happen since we now let the user choose the key
// from a list read from the store.
@@ -334,7 +343,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
/*
* (non-Javadoc)
- * @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench, org.eclipse.jface.viewers.IStructuredSelection)
+ * @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
+ * org.eclipse.jface.viewers.IStructuredSelection)
*/
@Override
public void init(IWorkbench workbench, IStructuredSelection selection) {
@@ -478,6 +488,14 @@ public final class ExportWizard extends Wizard implements IExportWizard {
return mDName;
}
+ String getCertSha1Fingerprint() {
+ return FingerprintUtils.getFingerprint(mCertificate, "SHA1");
+ }
+
+ String getCertMd5Fingerprint() {
+ return FingerprintUtils.getFingerprint(mCertificate, "MD5");
+ }
+
void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) {
mPrivateKey = privateKey;
mCertificate = certificate;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java
index 39ab258..c17f43e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/export/KeyCheckPage.java
@@ -57,6 +57,18 @@ import java.util.Calendar;
*/
final class KeyCheckPage extends ExportWizardPage {
+ private static final int REQUIRED_YEARS = 25;
+
+ private static final String VALIDITY_WARNING =
+ "<p>Make sure the certificate is valid for the planned lifetime of the product.</p>"
+ + "<p>If the certificate expires, you will be forced to sign your application with "
+ + "a different one.</p>"
+ + "<p>Applications cannot be upgraded if their certificate changes from "
+ + "one version to another, forcing a full uninstall/install, which will make "
+ + "the user lose his/her data.</p>"
+ + "<p>Google Play(Android Market) currently requires certificates to be valid "
+ + "until 2033.</p>";
+
private final ExportWizard mWizard;
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
@@ -169,12 +181,8 @@ final class KeyCheckPage extends ExportWizardPage {
String.format("<p>Certificate expires in %d years.</p>",
validity));
- if (validity < 25) {
- sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
- sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
- sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
- sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
- sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
+ if (validity < REQUIRED_YEARS) {
+ sb.append(VALIDITY_WARNING);
}
mKeyDetails = sb.toString();
@@ -239,7 +247,7 @@ final class KeyCheckPage extends ExportWizardPage {
int expirationYear = expirationCalendar.get(Calendar.YEAR);
int thisYear = today.get(Calendar.YEAR);
- if (thisYear + 25 < expirationYear) {
+ if (thisYear + REQUIRED_YEARS < expirationYear) {
// do nothing
} else {
if (expirationYear == thisYear) {
@@ -250,14 +258,19 @@ final class KeyCheckPage extends ExportWizardPage {
"<p>The Certificate expires in %1$s %2$s.</p>",
count, count == 1 ? "year" : "years"));
}
-
- sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
- sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
- sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
- sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
- sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
+ sb.append(VALIDITY_WARNING);
}
+ // show certificate fingerprints
+ String sha1 = mWizard.getCertSha1Fingerprint();
+ String md5 = mWizard.getCertMd5Fingerprint();
+
+ sb.append("<p></p>" /*blank line*/);
+ sb.append("<p>Certificate fingerprints:</p>");
+ sb.append(String.format("<li>MD5 : %s</li>", md5));
+ sb.append(String.format("<li>SHA1: %s</li>", sha1));
+ sb.append("<p></p>" /*blank line*/);
+
mKeyDetails = sb.toString();
}
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java
index f503c1e..6e63107 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java
@@ -24,18 +24,24 @@ import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.viewers.CellEditor;
+import org.eclipse.jface.viewers.CellLabelProvider;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ColumnViewer;
+import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ICheckStateListener;
-import org.eclipse.jface.viewers.IColorProvider;
-import org.eclipse.jface.viewers.ILabelProvider;
-import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
@@ -43,7 +49,7 @@ import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Color;
-import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
@@ -52,6 +58,7 @@ import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkingSet;
@@ -63,7 +70,10 @@ import java.util.List;
/** WizardPage for importing Android projects */
class ImportPage extends WizardPage implements SelectionListener, IStructuredContentProvider,
- ICheckStateListener, ILabelProvider, IColorProvider, KeyListener, TraverseListener {
+ ICheckStateListener, KeyListener, TraverseListener, ControlListener {
+ private static final int DIR_COLUMN = 0;
+ private static final int NAME_COLUMN = 1;
+
private final NewProjectWizardState mValues;
private List<ImportedProject> mProjectPaths;
private final IProject[] mExistingProjects;
@@ -120,15 +130,29 @@ class ImportPage extends WizardPage implements SelectionListener, IStructuredCon
projectsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
projectsLabel.setText("Projects:");
- mCheckboxTableViewer = CheckboxTableViewer.newCheckList(container,
- SWT.BORDER | SWT.FULL_SELECTION);
- mTable = mCheckboxTableViewer.getTable();
+ mTable = new Table(container, SWT.CHECK);
+ mTable.setHeaderVisible(true);
+ mCheckboxTableViewer = new CheckboxTableViewer(mTable);
+
+ TableViewerColumn dirViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE);
+ TableColumn dirColumn = dirViewerColumn.getColumn();
+ dirColumn.setWidth(200);
+ dirColumn.setText("Project to Import");
+ TableViewerColumn nameViewerColumn = new TableViewerColumn(mCheckboxTableViewer, SWT.NONE);
+ TableColumn nameColumn = nameViewerColumn.getColumn();
+ nameColumn.setWidth(200);
+ nameColumn.setText("New Project Name");
+ nameViewerColumn.setEditingSupport(new ProjectNameEditingSupport(mCheckboxTableViewer));
+
mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 4));
+ mTable.setLinesVisible(true);
+ mTable.setHeaderVisible(true);
mTable.addSelectionListener(this);
- mCheckboxTableViewer.setLabelProvider(this);
+ mTable.addControlListener(this);
mCheckboxTableViewer.setContentProvider(this);
mCheckboxTableViewer.setInput(this);
mCheckboxTableViewer.addCheckStateListener(this);
+ mCheckboxTableViewer.setLabelProvider(new ProjectCellLabelProvider());
mSelectAllButton = new Button(container, SWT.NONE);
mSelectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
@@ -153,6 +177,21 @@ class ImportPage extends WizardPage implements SelectionListener, IStructuredCon
Composite group = mWorkingSetGroup.createControl(container);
group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1));
+
+ updateColumnWidths();
+ }
+
+ private void updateColumnWidths() {
+ Rectangle r = mTable.getClientArea();
+ int availableWidth = r.width;
+ // Add all available size to the first column
+ for (int i = 1; i < mTable.getColumnCount(); i++) {
+ TableColumn column = mTable.getColumn(i);
+ availableWidth -= column.getWidth();
+ }
+ if (availableWidth > 100) {
+ mTable.getColumn(0).setWidth(availableWidth);
+ }
}
@Override
@@ -196,21 +235,25 @@ class ImportPage extends WizardPage implements SelectionListener, IStructuredCon
private List<ImportedProject> searchForProjects(File dir) {
List<ImportedProject> projects = new ArrayList<ImportedProject>();
- addProjects(dir, projects);
+ addProjects(dir, projects, dir.getPath().length() + 1);
return projects;
}
/** Finds all project directories under the given directory */
- private void addProjects(File dir, List<ImportedProject> projects) {
+ private void addProjects(File dir, List<ImportedProject> projects, int prefixLength) {
if (dir.isDirectory()) {
if (LintUtils.isProjectDir(dir)) {
- projects.add(new ImportedProject(dir));
+ String relative = dir.getPath();
+ if (relative.length() > prefixLength) {
+ relative = relative.substring(prefixLength);
+ }
+ projects.add(new ImportedProject(dir, relative));
}
File[] children = dir.listFiles();
if (children != null) {
for (File child : children) {
- addProjects(child, projects);
+ addProjects(child, projects, prefixLength);
}
}
}
@@ -234,6 +277,21 @@ class ImportPage extends WizardPage implements SelectionListener, IStructuredCon
String.format("Cannot import %1$s because the project name is in use",
project.getProjectName()));
break;
+ } else {
+ status = ProjectNamePage.validateProjectName(project.getProjectName());
+ if (status != null && !status.isOK()) {
+ // Need to insert project name to make it clear which project name
+ // is in violation
+ if (mValues.importProjects.size() > 1) {
+ String message = String.format("%1$s: %2$s",
+ project.getProjectName(), status.getMessage());
+ status = new Status(status.getSeverity(), AdtPlugin.PLUGIN_ID,
+ message);
+ }
+ break;
+ } else {
+ status = null; // Don't leave non null status with isOK() == true
+ }
}
}
}
@@ -365,48 +423,85 @@ class ImportPage extends WizardPage implements SelectionListener, IStructuredCon
}
mValues.importProjects = selected;
validatePage();
+
+ mCheckboxTableViewer.update(event.getElement(), null);
}
- // ---- Implements ILabelProvider ----
+ // ---- Implements ControlListener ----
@Override
- public void addListener(ILabelProviderListener listener) {
+ public void controlMoved(ControlEvent e) {
}
@Override
- public void removeListener(ILabelProviderListener listener) {
+ public void controlResized(ControlEvent e) {
+ updateColumnWidths();
}
- @Override
- public boolean isLabelProperty(Object element, String property) {
- return false;
- }
+ private final class ProjectCellLabelProvider extends CellLabelProvider {
+ @Override
+ public void update(ViewerCell cell) {
+ Object element = cell.getElement();
+ int index = cell.getColumnIndex();
+ ImportedProject project = (ImportedProject) element;
+
+ Display display = mTable.getDisplay();
+ Color fg;
+ if (mCheckboxTableViewer.getGrayed(element)) {
+ fg = display.getSystemColor(SWT.COLOR_DARK_GRAY);
+ } else {
+ fg = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
+ }
+ cell.setForeground(fg);
+ cell.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+
+ switch (index) {
+ case DIR_COLUMN: {
+ // Directory name
+ cell.setText(project.getRelativePath());
+ return;
+ }
- @Override
- public Image getImage(Object element) {
- return null;
+ case NAME_COLUMN: {
+ // New name
+ cell.setText(project.getProjectName());
+ return;
+ }
+ default:
+ assert false : index;
+ }
+ cell.setText("");
+ }
}
- @Override
- public String getText(Object element) {
- ImportedProject file = (ImportedProject) element;
- return String.format("%1$s (%2$s)", file.getProjectName(), file.getLocation().getPath());
- }
+ /** Editing support for the project name column */
+ private class ProjectNameEditingSupport extends EditingSupport {
+ private ProjectNameEditingSupport(ColumnViewer viewer) {
+ super(viewer);
+ }
- // ---- IColorProvider ----
+ @Override
+ protected void setValue(Object element, Object value) {
+ ImportedProject project = (ImportedProject) element;
+ project.setProjectName(value.toString());
+ mCheckboxTableViewer.update(element, null);
+ validatePage();
+ }
- @Override
- public Color getForeground(Object element) {
- Display display = mTable.getDisplay();
- if (mCheckboxTableViewer.getGrayed(element)) {
- return display.getSystemColor(SWT.COLOR_DARK_GRAY);
+ @Override
+ protected Object getValue(Object element) {
+ ImportedProject project = (ImportedProject) element;
+ return project.getProjectName();
}
- return display.getSystemColor(SWT.COLOR_LIST_FOREGROUND);
- }
+ @Override
+ protected CellEditor getCellEditor(Object element) {
+ return new TextCellEditor(mTable);
+ }
- @Override
- public Color getBackground(Object element) {
- return mTable.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
+ @Override
+ protected boolean canEdit(Object element) {
+ return true;
+ }
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java
index 064aa34..74af651 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java
@@ -15,27 +15,36 @@
*/
package com.android.ide.eclipse.adt.internal.wizards.newproject;
+import static com.android.SdkConstants.ATTR_NAME;
+
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.xml.AndroidManifestParser;
import com.android.ide.common.xml.ManifestData;
import com.android.ide.common.xml.ManifestData.Activity;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.io.FolderWrapper;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.internal.project.ProjectProperties;
import com.android.sdklib.internal.project.ProjectProperties.PropertyType;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.File;
+import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -45,16 +54,22 @@ class ImportedProject {
private String mActivityName;
private ManifestData mManifest;
private String mProjectName;
+ private String mRelativePath;
- ImportedProject(File location) {
+ ImportedProject(File location, String relativePath) {
super();
mLocation = location;
+ mRelativePath = relativePath;
}
File getLocation() {
return mLocation;
}
+ String getRelativePath() {
+ return mRelativePath;
+ }
+
@Nullable
ManifestData getManifest() {
if (mManifest == null) {
@@ -104,6 +119,12 @@ class ImportedProject {
@NonNull
public String getProjectName() {
if (mProjectName == null) {
+ // Are we importing an Eclipse project? If so just use the existing project name
+ mProjectName = findEclipseProjectName();
+ if (mProjectName != null) {
+ return mProjectName;
+ }
+
String activityName = getActivityName();
if (activityName == null || activityName.isEmpty()) {
// I could also look at the build files, say build.xml from ant, and
@@ -136,6 +157,37 @@ class ImportedProject {
return mProjectName;
}
+ @Nullable
+ private String findEclipseProjectName() {
+ File projectFile = new File(mLocation, ".project"); //$NON-NLS-1$
+ if (projectFile.exists()) {
+ String xml;
+ try {
+ xml = Files.toString(projectFile, Charsets.UTF_8);
+ Document doc = DomUtilities.parseDocument(xml, false);
+ if (doc != null) {
+ NodeList names = doc.getElementsByTagName(ATTR_NAME);
+ if (names.getLength() >= 1) {
+ Node nameElement = names.item(0);
+ String name = nameElement.getTextContent().trim();
+ if (!name.isEmpty()) {
+ return name;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // pass: don't attempt to read project name; must be some sort of unrelated
+ // file with the same name, perhaps from a different editor or IDE
+ }
+ }
+
+ return null;
+ }
+
+ public void setProjectName(@NonNull String newName) {
+ mProjectName = newName;
+ }
+
public IAndroidTarget getTarget() {
// Pick a target:
// First try to find the one requested by project.properties
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
index 2f7ad7a..eea9d36 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
@@ -23,18 +23,18 @@ import static org.eclipse.core.resources.IResource.DEPTH_ZERO;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.ide.common.resources.ValueResourceParser;
import com.android.ide.common.xml.ManifestData;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtConstants;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.project.AndroidNature;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.ProjectHelper;
-import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode;
@@ -522,10 +522,10 @@ public class NewProjectCreator {
if (core.getStatus().getCode() == IResourceStatus.CASE_VARIANT_EXISTS) {
// The error indicates the file system is not case sensitive
// and there's a resource with a similar name.
- MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
+ MessageDialog.openError(AdtPlugin.getShell(),
"Error", "Error: Case Variant Exists");
} else {
- ErrorDialog.openError(AdtPlugin.getDisplay().getActiveShell(),
+ ErrorDialog.openError(AdtPlugin.getShell(),
"Error", core.getMessage(), core.getStatus());
}
} else {
@@ -539,7 +539,7 @@ public class NewProjectCreator {
if (msg == null) {
msg = t.toString();
}
- MessageDialog.openError(AdtPlugin.getDisplay().getActiveShell(), "Error", msg);
+ MessageDialog.openError(AdtPlugin.getShell(), "Error", msg);
}
e.printStackTrace();
} catch (InterruptedException e) {
@@ -1058,7 +1058,7 @@ public class NewProjectCreator {
String value = strings.get(key);
// Escape values if necessary
- value = ExtractStringRefactoring.escapeString(value);
+ value = ValueResourceParser.escapeResourceString(value);
// place them in the template
String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key);
@@ -1088,8 +1088,8 @@ public class NewProjectCreator {
/** Reformats the given contents with the current formatting settings */
private String reformat(XmlFormatStyle style, String contents) {
if (AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
- return XmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
+ EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
+ return EclipseXmlPrettyPrinter.prettyPrint(contents, formatPrefs, style,
null /*lineSeparator*/);
} else {
return contents;
@@ -1404,7 +1404,8 @@ public class NewProjectCreator {
if (reformat) {
// Guess the formatting style based on the file location
- XmlFormatStyle style = XmlFormatStyle.getForFile(destFile.getProjectRelativePath());
+ XmlFormatStyle style = EclipseXmlPrettyPrinter
+ .getForFile(destFile.getProjectRelativePath());
if (style != null) {
template = reformat(style, template);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/AddTranslationDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/AddTranslationDialog.java
index b1a0299..ce7e936 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/AddTranslationDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/AddTranslationDialog.java
@@ -26,6 +26,7 @@ import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.LocaleManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
import com.android.resources.ResourceType;
@@ -403,6 +404,7 @@ public class AddTranslationDialog extends Dialog implements ControlListener, Sel
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFolder folder = root.getFolder(parent.getFullPath());
manager.getResourceFolder(folder);
+ RenderPreviewManager.bumpRevision();
} catch (CoreException e) {
AdtPlugin.log(e, null);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ChooseConfigurationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ChooseConfigurationPage.java
index aec6b92..1d6467e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ChooseConfigurationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/ChooseConfigurationPage.java
@@ -121,6 +121,8 @@ public class ChooseConfigurationPage extends WizardPage {
});
setControl(composite);
+
+ mConfigSelector.setConfiguration(mValues.configuration);
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
index 68c7f4c..28fb8c0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileCreationPage.java
@@ -304,6 +304,7 @@ class NewXmlFileCreationPage extends WizardPage {
if (SCROLL_VIEW.equals(root) || HORIZONTAL_SCROLL_VIEW.equals(root)) {
return " <LinearLayout " //$NON-NLS-1$
+ getDefaultAttrs(project, root).replace('\n', ' ')
+ + " android:orientation=\"vertical\"" //$NON-NLS-1$
+ "></LinearLayout>\n"; //$NON-NLS-1$
}
return null;
@@ -733,6 +734,7 @@ class NewXmlFileCreationPage extends WizardPage {
}
String[] folderSegments = targetWsFolderPath.split(RES_QUALIFIER_SEP);
if (folderSegments.length > 0) {
+ mValues.configuration = FolderConfiguration.getConfig(folderSegments);
String folderName = folderSegments[0];
selectTypeFromFolder(folderName);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java
index 22e2325..16cd7b3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newxmlfile/NewXmlFileWizard.java
@@ -21,13 +21,14 @@ import static com.android.SdkConstants.GRID_LAYOUT;
import com.android.SdkConstants;
import com.android.ide.common.resources.configuration.FolderConfiguration;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.RenderPreviewManager;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.project.SupportLibraryHelper;
@@ -249,14 +250,14 @@ public class NewXmlFileWizard extends Wizard implements INewWizard {
sb.append("</").append(root).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
- XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
+ EclipseXmlFormatPreferences formatPrefs = EclipseXmlFormatPreferences.create();
String fileContents;
if (!autoFormat) {
fileContents = sb.toString();
} else {
- XmlFormatStyle style = XmlFormatStyle.getForFolderType(folderType);
- fileContents = XmlPrettyPrinter.prettyPrint(sb.toString(), formatPrefs,
- style, null /*lineSeparator*/);
+ XmlFormatStyle style = EclipseXmlPrettyPrinter.getForFolderType(folderType);
+ fileContents = EclipseXmlPrettyPrinter.prettyPrint(sb.toString(), formatPrefs,
+ style, null /*lineSeparator*/);
}
// Remove marker tokens and replace them with whitespace
@@ -275,6 +276,15 @@ public class NewXmlFileWizard extends Wizard implements INewWizard {
}
file.create(stream, true /*force*/, null /*progress*/);
IRegion region = caretOffset != -1 ? new Region(caretOffset, 0) : null;
+
+ // If you introduced a new locale, or new screen variations etc, ensure that
+ // the list of render previews is updated if necessary
+ if (file.getParent().getName().indexOf('-') != -1
+ && (folderType == ResourceFolderType.LAYOUT
+ || folderType == ResourceFolderType.VALUES)) {
+ RenderPreviewManager.bumpRevision();
+ }
+
return Pair.of(file, region);
} catch (UnsupportedEncodingException e) {
error = e.getMessage();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java
index 9d2dd9a..ba4aedc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java
@@ -17,11 +17,13 @@ package com.android.ide.eclipse.adt.internal.wizards.templates;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.CATEGORY_ACTIVITIES;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.CATEGORY_OTHER;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.IS_LAUNCHER;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_PADDING;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_WIDTH;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
+import com.google.common.collect.Lists;
import com.google.common.io.Files;
import org.eclipse.core.runtime.IStatus;
@@ -61,6 +63,7 @@ class ActivityPage extends WizardPage implements SelectionListener {
private Label mDescription;
private boolean mOnlyActivities;
private boolean mAskCreate;
+ private boolean mLauncherActivitiesOnly;
/**
* Create the wizard.
@@ -84,6 +87,11 @@ class ActivityPage extends WizardPage implements SelectionListener {
}
}
+ /** Sets whether the activity page should only offer launcher activities */
+ void setLauncherActivitiesOnly(boolean launcherActivitiesOnly) {
+ mLauncherActivitiesOnly = launcherActivitiesOnly;
+ }
+
@Override
public void createControl(Composite parent) {
Composite container = new Composite(parent, SWT.NULL);
@@ -108,18 +116,31 @@ class ActivityPage extends WizardPage implements SelectionListener {
TemplateManager manager = mValues.template.getManager();
- mTemplates = manager.getTemplates(CATEGORY_ACTIVITIES);
+ java.util.List<File> templates = manager.getTemplates(CATEGORY_ACTIVITIES);
+
if (!mOnlyActivities) {
- mTemplates.addAll(manager.getTemplates(CATEGORY_OTHER));
+ templates.addAll(manager.getTemplates(CATEGORY_OTHER));
}
- java.util.List<String> names = new ArrayList<String>(mTemplates.size());
+ java.util.List<String> names = new ArrayList<String>(templates.size());
File current = mValues.activityValues.getTemplateLocation();
+ mTemplates = Lists.newArrayListWithExpectedSize(templates.size());
int index = -1;
- for (int i = 0, n = mTemplates.size(); i < n; i++) {
- File template = mTemplates.get(i);
- names.add(template.getName());
+ for (int i = 0, n = templates.size(); i < n; i++) {
+ File template = templates.get(i);
+ TemplateMetadata metadata = manager.getTemplate(template);
+ if (metadata == null) {
+ continue;
+ }
+ if (mLauncherActivitiesOnly) {
+ Parameter parameter = metadata.getParameter(IS_LAUNCHER);
+ if (parameter == null) {
+ continue;
+ }
+ }
+ mTemplates.add(template);
+ names.add(metadata.getTitle());
if (template.equals(current)) {
- index = i;
+ index = names.size();
}
}
String[] items = names.toArray(new String[names.size()]);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
index 7e5866e..ffcfa3e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmEscapeXmlStringMethod.java
@@ -15,7 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.wizards.templates;
-import com.android.ide.eclipse.adt.internal.refactorings.extractstring.ExtractStringRefactoring;
+import com.android.ide.common.resources.ValueResourceParser;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateMethodModel;
@@ -38,6 +38,6 @@ public class FmEscapeXmlStringMethod implements TemplateMethodModel {
throw new TemplateModelException("Wrong arguments");
}
String string = args.get(0).toString();
- return new SimpleScalar(ExtractStringRefactoring.escapeString(string));
+ return new SimpleScalar(ValueResourceParser.escapeResourceString(string));
}
} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java
index e698ac1..0b003f3 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java
@@ -546,9 +546,6 @@ public class NewProjectPage extends WizardPage
Object source = e.getSource();
if (source == mMinSdkCombo) {
mValues.minSdk = getSelectedMinSdk();
- // If higher than build target, adjust build target
- // TODO: implement
-
Integer minSdk = mMinNameToApi.get(mValues.minSdk);
if (minSdk == null) {
try {
@@ -559,6 +556,49 @@ public class NewProjectPage extends WizardPage
}
mValues.iconState.minSdk = minSdk.intValue();
mValues.minSdkLevel = minSdk.intValue();
+
+ // If higher than build target, adjust build target
+ if (mValues.minSdkLevel > mValues.getBuildApi()) {
+ // Try to find a build target with an adequate build API
+ IAndroidTarget[] targets = (IAndroidTarget[]) mBuildSdkCombo.getData();
+ IAndroidTarget best = null;
+ int bestApi = Integer.MAX_VALUE;
+ int bestTargetIndex = -1;
+ for (int i = 0; i < targets.length; i++) {
+ IAndroidTarget target = targets[i];
+ if (!target.isPlatform()) {
+ continue;
+ }
+ int api = target.getVersion().getApiLevel();
+ if (api >= mValues.minSdkLevel && api < bestApi) {
+ best = target;
+ bestApi = api;
+ bestTargetIndex = i;
+ }
+ }
+
+ if (best != null) {
+ assert bestTargetIndex != -1;
+ mValues.target = best;
+ try {
+ mIgnore = true;
+ mBuildSdkCombo.select(bestTargetIndex);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+
+ // If higher than targetSdkVersion, adjust targetSdkVersion
+ if (mValues.minSdkLevel > mValues.targetSdkLevel) {
+ mValues.targetSdkLevel = mValues.minSdkLevel;
+ try {
+ mIgnore = true;
+ setSelectedTargetSdk(mValues.targetSdkLevel);
+ } finally {
+ mIgnore = false;
+ }
+ }
} else if (source == mBuildSdkCombo) {
mValues.target = getSelectedBuildTarget();
@@ -614,6 +654,10 @@ public class NewProjectPage extends WizardPage
mMinSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1
}
+ private void setSelectedTargetSdk(int api) {
+ mTargetSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1
+ }
+
@Nullable
private IAndroidTarget getSelectedBuildTarget() {
IAndroidTarget[] targets = (IAndroidTarget[]) mBuildSdkCombo.getData();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java
index 72e9985..84de9a4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java
@@ -19,6 +19,7 @@ import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
+import com.android.annotations.VisibleForTesting;
import com.android.assetstudiolib.GraphicGenerator;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
@@ -58,8 +59,9 @@ import java.util.Set;
public class NewProjectWizard extends TemplateWizard {
private static final String PARENT_ACTIVITY_CLASS = "parentActivityClass"; //$NON-NLS-1$
private static final String ACTIVITY_TITLE = "activityTitle"; //$NON-NLS-1$
- private static final String IS_LAUNCHER = "isLauncher"; //$NON-NLS-1$
+ static final String IS_LAUNCHER = "isLauncher"; //$NON-NLS-1$
static final String IS_NEW_PROJECT = "isNewProject"; //$NON-NLS-1$
+ static final String IS_LIBRARY_PROJECT = "isLibraryProject"; //$NON-NLS-1$
static final String ATTR_COPY_ICONS = "copyIcons"; //$NON-NLS-1$
static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$
static final String ATTR_MIN_API = "minApi"; //$NON-NLS-1$
@@ -98,6 +100,7 @@ public class NewProjectWizard extends TemplateWizard {
mContentsPage = new ProjectContentsPage(mValues);
mContentsPage.init(selection, AdtUtils.getActivePart());
mActivityPage = new ActivityPage(mValues, true, true);
+ mActivityPage.setLauncherActivitiesOnly(true);
}
@Override
@@ -240,6 +243,16 @@ public class NewProjectWizard extends TemplateWizard {
return mValues.template.getFilesToOpen();
}
+ @VisibleForTesting
+ NewProjectWizardState getValues() {
+ return mValues;
+ }
+
+ @VisibleForTesting
+ void setValues(NewProjectWizardState values) {
+ mValues = values;
+ }
+
@Override
protected List<Change> computeChanges() {
final TemplateHandler template = mValues.template;
@@ -374,6 +387,7 @@ public class NewProjectWizard extends TemplateWizard {
addProjectInfo(parameters);
parameters.put(IS_NEW_PROJECT, true);
+ parameters.put(IS_LIBRARY_PROJECT, mValues.isLibrary);
// Ensure that activities created as part of a new project are marked as
// launcher activities
parameters.put(IS_LAUNCHER, true);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java
index 1cf5daf..57cf5c8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java
@@ -811,7 +811,7 @@ public class NewTemplatePage extends WizardPage
scope = SearchEngine.createJavaSearchScope(classes, IJavaSearchScope.SOURCES);
}
- Shell parent = AdtPlugin.getDisplay().getActiveShell();
+ Shell parent = AdtPlugin.getShell();
final SelectionDialog dialog = JavaUI.createTypeDialog(
parent,
new ProgressMonitorDialog(parent),
@@ -870,7 +870,6 @@ public class NewTemplatePage extends WizardPage
p.type == Parameter.Type.SEPARATOR) {
continue;
}
- p.suggest.indexOf(id);
if (!p.suggest.contains(id)) {
continue;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java
index 1d1eb1d..805399b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java
@@ -22,6 +22,7 @@ import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectW
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API_LEVEL;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_TARGET_API;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.IS_LIBRARY_PROJECT;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.IS_NEW_PROJECT;
import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard.BLANK_ACTIVITY;
@@ -30,6 +31,7 @@ import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.internal.assetstudio.ConfigureAssetSetPage;
import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
@@ -155,6 +157,9 @@ public class NewTemplateWizardState {
parameters.put(ATTR_TARGET_API, manifest.getTargetSdkVersion());
parameters.put(ATTR_BUILD_API, getBuildApi());
parameters.put(ATTR_COPY_ICONS, mIconState == null);
+ ProjectState projectState = Sdk.getProjectState(project);
+ parameters.put(IS_LIBRARY_PROJECT,
+ projectState != null ? projectState.isLibrary() : false);
List<Change> changes = getTemplateHandler().render(project, parameters);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java
index a9a3f33..3139451 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.wizards.templates;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_CONSTRAINTS;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_HELP;
@@ -26,6 +27,8 @@ import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage;
import com.android.resources.ResourceFolderType;
@@ -33,7 +36,10 @@ import com.android.resources.ResourceType;
import com.google.common.base.Splitter;
import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IType;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.fieldassist.ControlDecoration;
import org.eclipse.swt.widgets.Control;
@@ -146,6 +152,9 @@ class Parameter {
}
}
+ /** The template defining the parameter */
+ public final TemplateMetadata template;
+
/** The type of parameter */
@NonNull
public final Type type;
@@ -208,7 +217,8 @@ class Parameter {
/** Project associated with this validator */
private IProject mValidatorProject;
- Parameter(@NonNull Element parameter) {
+ Parameter(@NonNull TemplateMetadata template, @NonNull Element parameter) {
+ this.template = template;
element = parameter;
String typeName = parameter.getAttribute(TemplateHandler.ATTR_TYPE);
@@ -244,7 +254,12 @@ class Parameter {
}
}
- Parameter(@NonNull Type type, @NonNull String id, @NonNull String initialValue) {
+ Parameter(
+ @NonNull TemplateMetadata template,
+ @NonNull Type type,
+ @NonNull String id,
+ @NonNull String initialValue) {
+ this.template = template;
this.type = type;
this.id = id;
this.value = initialValue;
@@ -265,7 +280,7 @@ class Parameter {
}
@Nullable
- public IInputValidator getValidator(@Nullable IProject project) {
+ public IInputValidator getValidator(@Nullable final IProject project) {
if (mNoValidator) {
return null;
}
@@ -341,6 +356,37 @@ class Parameter {
return status.getMessage();
}
+ // Uniqueness
+ if (project != null && constraints.contains(Constraint.UNIQUE)) {
+ try {
+ // Determine the package.
+ // If there is a package info
+
+ IJavaProject p = BaseProjectHelper.getJavaProject(project);
+ if (p != null) {
+ String fqcn = newText;
+ if (fqcn.indexOf('.') == -1) {
+ String pkg = null;
+ Parameter parameter = template.getParameter(
+ ATTR_PACKAGE_NAME);
+ if (parameter != null && parameter.value != null) {
+ pkg = parameter.value.toString();
+ } else {
+ pkg = ManifestInfo.get(project).getPackage();
+ }
+ fqcn = pkg.isEmpty() ? newText : pkg + '.' + newText;
+ }
+
+ IType t = p.findType(fqcn);
+ if (t != null && t.exists()) {
+ return String.format("%1$s already exists", newText);
+ }
+ }
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
return null;
}
};
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
index 53b6c3c..bd9c0fa 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.wizards.templates;
+import static com.android.SdkConstants.ATTR_PACKAGE;
import static com.android.SdkConstants.DOT_AIDL;
import static com.android.SdkConstants.DOT_FTL;
import static com.android.SdkConstants.DOT_JAVA;
@@ -24,18 +25,20 @@ import static com.android.SdkConstants.DOT_TXT;
import static com.android.SdkConstants.DOT_XML;
import static com.android.SdkConstants.EXT_XML;
import static com.android.SdkConstants.FD_NATIVE_LIBS;
+import static com.android.SdkConstants.XMLNS_PREFIX;
import static com.android.ide.eclipse.adt.internal.wizards.templates.InstallDependencyPage.SUPPORT_LIBRARY_NAME;
import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateManager.getTemplateRootFolder;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.xml.XmlFormatStyle;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.actions.AddSupportJarAction;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
-import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.EclipseXmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.sdk.AdtManifestMergeCallback;
@@ -58,6 +61,7 @@ import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
@@ -66,6 +70,7 @@ import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.ToolFactory;
import org.eclipse.jdt.core.formatter.CodeFormatter;
import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.NullChange;
@@ -77,8 +82,10 @@ import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
+import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
@@ -95,6 +102,7 @@ import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -389,6 +397,14 @@ class TemplateHandler {
}
}
+ /**
+ * Most recent thrown exception during template instantiation. This should
+ * basically always be null. Used by unit tests to see if any template
+ * instantiation recorded a failure.
+ */
+ @VisibleForTesting
+ public static Exception sMostRecentException;
+
/** Read the given FreeMarker file and process the variable definitions */
private void processVariables(final Configuration freemarker,
String file, final Map<String, Object> paramMap) {
@@ -469,6 +485,7 @@ class TemplateHandler {
}
});
} catch (Exception e) {
+ sMostRecentException = e;
AdtPlugin.log(e, null);
}
}
@@ -579,12 +596,14 @@ class TemplateHandler {
System.err.println("WARNING: Unknown template directive " + name);
}
} catch (Exception e) {
+ sMostRecentException = e;
AdtPlugin.log(e, null);
}
}
});
} catch (Exception e) {
+ sMostRecentException = e;
AdtPlugin.log(e, null);
}
}
@@ -657,26 +676,28 @@ class TemplateHandler {
}
}
- Document currentManifest = DomUtilities.parseStructuredDocument(currentXml);
+ Document currentDocument = DomUtilities.parseStructuredDocument(currentXml);
+ assert currentDocument != null : currentXml;
Document fragment = DomUtilities.parseStructuredDocument(xml);
+ assert fragment != null : xml;
XmlFormatStyle formatStyle = XmlFormatStyle.MANIFEST;
boolean modified;
boolean ok;
String fileName = to.getName();
if (fileName.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
- modified = ok = mergeManifest(currentManifest, fragment);
+ modified = ok = mergeManifest(currentDocument, fragment);
} else {
// Merge plain XML files
String parentFolderName = to.getParent().getName();
ResourceFolderType folderType = ResourceFolderType.getFolderType(parentFolderName);
if (folderType != null) {
- formatStyle = XmlFormatStyle.getForFile(toPath);
+ formatStyle = EclipseXmlPrettyPrinter.getForFile(toPath);
} else {
formatStyle = XmlFormatStyle.FILE;
}
- modified = mergeResourceFile(currentManifest, fragment, folderType, paramMap);
+ modified = mergeResourceFile(currentDocument, fragment, folderType, paramMap);
ok = true;
}
@@ -684,11 +705,9 @@ class TemplateHandler {
String contents = null;
if (ok) {
if (modified) {
- XmlPrettyPrinter printer = new XmlPrettyPrinter(
- XmlFormatPreferences.create(), formatStyle, null);
- StringBuilder sb = new StringBuilder(2 );
- printer.prettyPrint(-1, currentManifest, null, null, sb, false /*openTagOnly*/);
- contents = sb.toString();
+ contents = EclipseXmlPrettyPrinter.prettyPrint(currentDocument,
+ EclipseXmlFormatPreferences.create(), formatStyle, null,
+ currentXml.endsWith("\n")); //$NON-NLS-1$
}
} else {
// Just insert into file along with comment, using the "standard" conflict
@@ -714,10 +733,22 @@ class TemplateHandler {
/** Merges the given resource file contents into the given resource file
* @param paramMap */
- private boolean mergeResourceFile(Document currentManifest, Document fragment,
+ private static boolean mergeResourceFile(Document currentDocument, Document fragment,
ResourceFolderType folderType, Map<String, Object> paramMap) {
boolean modified = false;
+ // Copy namespace declarations
+ NamedNodeMap attributes = fragment.getDocumentElement().getAttributes();
+ if (attributes != null) {
+ for (int i = 0, n = attributes.getLength(); i < n; i++) {
+ Attr attribute = (Attr) attributes.item(i);
+ if (attribute.getName().startsWith(XMLNS_PREFIX)) {
+ currentDocument.getDocumentElement().setAttribute(attribute.getName(),
+ attribute.getValue());
+ }
+ }
+ }
+
// For layouts for example, I want to *append* inside the root all the
// contents of the new file.
// But for resources for example, I want to combine elements which specify
@@ -733,8 +764,9 @@ class TemplateHandler {
nodes.add(child);
root.removeChild(child);
}
+ Collections.reverse(nodes);
- root = currentManifest.getDocumentElement();
+ root = currentDocument.getDocumentElement();
if (folderType == ResourceFolderType.VALUES) {
// Try to merge items of the same name
@@ -794,15 +826,31 @@ class TemplateHandler {
}
/** Merges the given manifest fragment into the given manifest file */
- private boolean mergeManifest(Document currentManifest, Document fragment) {
+ private static boolean mergeManifest(Document currentManifest, Document fragment) {
// TODO change MergerLog.wrapSdkLog by a custom IMergerLog that will create
// and maintain error markers.
+
+ // Transfer package element from manifest to merged in root; required by
+ // manifest merger
+ Element fragmentRoot = fragment.getDocumentElement();
+ Element manifestRoot = currentManifest.getDocumentElement();
+ if (fragmentRoot == null || manifestRoot == null) {
+ return false;
+ }
+ String pkg = fragmentRoot.getAttribute(ATTR_PACKAGE);
+ if (pkg == null || pkg.isEmpty()) {
+ pkg = manifestRoot.getAttribute(ATTR_PACKAGE);
+ if (pkg != null && !pkg.isEmpty()) {
+ fragmentRoot.setAttribute(ATTR_PACKAGE, pkg);
+ }
+ }
+
ManifestMerger merger = new ManifestMerger(
MergerLog.wrapSdkLog(AdtPlugin.getDefault()),
- new AdtManifestMergeCallback());
+ new AdtManifestMergeCallback()).setExtractPackagePrefix(true);
return currentManifest != null &&
- fragment != null &&
- merger.process(currentManifest, fragment);
+ fragment != null &&
+ merger.process(currentManifest, fragment);
}
/**
@@ -856,7 +904,7 @@ class TemplateHandler {
contents = format(mProject, contents, to);
IFile targetFile = getTargetFile(to);
- TextFileChange change = createTextChange(targetFile);
+ TextFileChange change = createNewFileChange(targetFile);
MultiTextEdit rootEdit = new MultiTextEdit();
rootEdit.addChild(new InsertEdit(0, contents));
change.setEdit(rootEdit);
@@ -867,9 +915,9 @@ class TemplateHandler {
private static String format(IProject project, String contents, IPath to) {
String name = to.lastSegment();
if (name.endsWith(DOT_XML)) {
- XmlFormatStyle formatStyle = XmlFormatStyle.getForFile(to);
- XmlFormatPreferences prefs = XmlFormatPreferences.create();
- return XmlPrettyPrinter.prettyPrint(contents, prefs, formatStyle, null);
+ XmlFormatStyle formatStyle = EclipseXmlPrettyPrinter.getForFile(to);
+ EclipseXmlFormatPreferences prefs = EclipseXmlFormatPreferences.create();
+ return EclipseXmlPrettyPrinter.prettyPrint(contents, prefs, formatStyle, null);
} else if (name.endsWith(DOT_JAVA)) {
Map<?, ?> options = null;
if (project != null && project.isAccessible()) {
@@ -908,7 +956,7 @@ class TemplateHandler {
return contents;
}
- private static TextFileChange createTextChange(IFile targetFile) {
+ private static TextFileChange createNewFileChange(IFile targetFile) {
String fileName = targetFile.getName();
String message;
if (targetFile.exists()) {
@@ -917,7 +965,29 @@ class TemplateHandler {
message = String.format("Create %1$s", fileName);
}
- TextFileChange change = new TextFileChange(message, targetFile);
+ TextFileChange change = new TextFileChange(message, targetFile) {
+ @Override
+ protected IDocument acquireDocument(IProgressMonitor pm) throws CoreException {
+ IDocument document = super.acquireDocument(pm);
+
+ // In our case, we know we *always* use this TextFileChange
+ // to *create* files, we're not appending to existing files.
+ // However, due to the following bug we can end up with cached
+ // contents of previously deleted files that happened to have the
+ // same file name:
+ // https://bugs.eclipse.org/bugs/show_bug.cgi?id=390402
+ // Therefore, as a workaround, wipe out the cached contents here
+ if (document.getLength() > 0) {
+ try {
+ document.replace(0, document.getLength(), "");
+ } catch (BadLocationException e) {
+ // pass
+ }
+ }
+
+ return document;
+ }
+ };
change.setTextType(fileName.substring(fileName.lastIndexOf('.') + 1));
return change;
}
@@ -989,7 +1059,7 @@ class TemplateHandler {
String newFile = Files.toString(src, Charsets.UTF_8);
newFile = format(mProject, newFile, path);
- TextFileChange addFile = createTextChange(file);
+ TextFileChange addFile = createNewFileChange(file);
addFile.setEdit(new InsertEdit(0, newFile));
mTextChanges.add(addFile);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java
index 13c3f00..cb184b8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java
@@ -86,7 +86,7 @@ class TemplateMetadata {
mParameterMap = new HashMap<String, Parameter>(parameters.getLength());
for (int index = 0, max = parameters.getLength(); index < max; index++) {
Element element = (Element) parameters.item(index);
- Parameter parameter = new Parameter(element);
+ Parameter parameter = new Parameter(this, element);
mParameters.add(parameter);
if (parameter.id != null) {
mParameterMap.put(parameter.id, parameter);
@@ -221,7 +221,7 @@ class TemplateMetadata {
if (mIconState.outputName != null) {
// Register parameter such that if it is referencing other values, it gets
// updated when other values are edited
- Parameter outputParameter = new Parameter(
+ Parameter outputParameter = new Parameter(this,
Parameter.Type.STRING, "_iconname", mIconState.outputName); //$NON-NLS-1$
getParameters().add(outputParameter);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/messages.properties
index b37b809..80a2f45 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/messages.properties
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/messages.properties
@@ -1,7 +1,7 @@
Could_Not_Find_Folder=Could not find SDK folder '%1$s'.
Could_Not_Find_Folder_In_SDK=Could not find folder '%1$s' inside SDK '%2$s'.
Could_Not_Find=Could not find %1$s\!
-VersionCheck_Tools_Too_Old=This version of ADT requires Android SDK Tools in revision %1$s or above.\n\nCurrent revision is %2$s.\n\nPlease update your SDK Tools to the latest version.
+VersionCheck_Tools_Too_Old=This version of ADT requires Android SDK Tools revision %1$s or above.\n\nCurrent revision is %2$s.\n\nPlease update your SDK Tools to the latest version.
VersionCheck_Plugin_Version_Failed=Failed to get the required ADT version number from the SDK.\n\nThe Android Developer Toolkit may not work properly.
VersionCheck_Unable_To_Parse_Version_s=Unable to parse sdk build version: %1$s
VersionCheck_Plugin_Too_Old=This Android SDK requires Android Developer Toolkit version %1$d.%2$d.%3$d or above.\n\nCurrent version is %4$s.\n\nPlease update ADT to the latest version.