From 9ca1c0b33cc3fc28c21a915730797ec01e71a152 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:04:04 -0800 Subject: Code drop from //branches/cupcake/...@124589 --- .../plugins/com.android.ide.eclipse.adt/.classpath | 7 + .../META-INF/MANIFEST.MF | 57 +- .../com.android.ide.eclipse.adt/build.properties | 9 +- .../com.android.ide.eclipse.adt/icons/add.png | Bin 0 -> 146 bytes .../com.android.ide.eclipse.adt/icons/az_sort.png | Bin 0 -> 363 bytes .../com.android.ide.eclipse.adt/icons/delete.png | Bin 0 -> 107 bytes .../icons/dimension.png | Bin 0 -> 320 bytes .../com.android.ide.eclipse.adt/icons/down.png | Bin 0 -> 157 bytes .../com.android.ide.eclipse.adt/icons/dpi.png | Bin 0 -> 302 bytes .../com.android.ide.eclipse.adt/icons/error.png | Bin 0 -> 194 bytes .../com.android.ide.eclipse.adt/icons/keyboard.png | Bin 0 -> 307 bytes .../com.android.ide.eclipse.adt/icons/language.png | Bin 0 -> 287 bytes .../com.android.ide.eclipse.adt/icons/match.png | Bin 0 -> 138 bytes .../com.android.ide.eclipse.adt/icons/mcc.png | Bin 0 -> 463 bytes .../com.android.ide.eclipse.adt/icons/mnc.png | Bin 0 -> 265 bytes .../com.android.ide.eclipse.adt/icons/navpad.png | Bin 0 -> 308 bytes .../icons/orientation.png | Bin 0 -> 325 bytes .../com.android.ide.eclipse.adt/icons/region.png | Bin 0 -> 445 bytes .../icons/text_input.png | Bin 0 -> 321 bytes .../com.android.ide.eclipse.adt/icons/touch.png | Bin 0 -> 344 bytes .../com.android.ide.eclipse.adt/icons/up.png | Bin 0 -> 137 bytes .../com.android.ide.eclipse.adt/icons/warning.png | Bin 0 -> 147 bytes .../plugins/com.android.ide.eclipse.adt/plugin.xml | 187 +- .../com/android/ide/eclipse/adt/AdtConstants.java | 7 + .../src/com/android/ide/eclipse/adt/AdtPlugin.java | 670 ++++-- .../com/android/ide/eclipse/adt/VersionCheck.java | 71 +- .../android/ide/eclipse/adt/build/ApkBuilder.java | 86 +- .../ide/eclipse/adt/build/ApkDeltaVisitor.java | 20 + .../android/ide/eclipse/adt/build/BaseBuilder.java | 6 - .../android/ide/eclipse/adt/build/DexWrapper.java | 4 +- .../ide/eclipse/adt/build/PreCompilerBuilder.java | 45 +- .../eclipse/adt/build/PreCompilerDeltaVisitor.java | 41 +- .../eclipse/adt/build/ResourceManagerBuilder.java | 3 - .../debug/launching/AndroidLaunchController.java | 210 +- .../adt/debug/launching/LaunchConfigDelegate.java | 11 +- .../eclipse/adt/debug/ui/EmulatorConfigTab.java | 91 +- .../ide/eclipse/adt/debug/ui/SkinRepository.java | 152 -- .../adt/editors/java/ReadOnlyJavaEditor.java | 49 - .../adt/preferences/BuildPreferencePage.java | 111 +- .../adt/project/CreateAidlImportAction.java | 5 +- .../ide/eclipse/adt/project/FolderDecorator.java | 104 + .../adt/project/NewXmlFileWizardAction.java | 57 + .../ide/eclipse/adt/project/ProjectHelper.java | 35 +- .../eclipse/adt/project/export/ExportWizard.java | 345 ++- .../adt/project/export/FinalExportPage.java | 275 --- .../eclipse/adt/project/export/KeyCheckPage.java | 291 +++ .../adt/project/export/KeyCreationPage.java | 332 +++ .../adt/project/export/KeySelectionPage.java | 266 +++ .../adt/project/export/KeystoreSelectionPage.java | 260 +++ .../eclipse/adt/project/export/PreExportPage.java | 318 --- .../adt/project/export/ProjectCheckPage.java | 323 +++ .../adt/project/export/SigningExportPage.java | 218 -- .../internal/AndroidClasspathContainer.java | 7 +- .../AndroidClasspathContainerInitializer.java | 179 +- .../project/internal/NewProjectCreationPage.java | 1060 --------- .../adt/project/internal/NewProjectWizard.java | 706 ------ .../project/properties/AndroidPropertyPage.java | 98 + .../eclipse/adt/resources/AndroidJarLoader.java | 425 ---- .../adt/resources/FrameworkResourceParser.java | 576 ----- .../adt/resources/FrameworkResourceRepository.java | 76 - .../ide/eclipse/adt/resources/IAndroidLoader.java | 65 - .../eclipse/adt/resources/LayoutParamsParser.java | 380 ---- .../eclipse/adt/resources/WidgetListLoader.java | 333 --- .../ide/eclipse/adt/sdk/AndroidJarLoader.java | 430 ++++ .../ide/eclipse/adt/sdk/AndroidTargetData.java | 285 +++ .../ide/eclipse/adt/sdk/AndroidTargetParser.java | 651 ++++++ .../adt/sdk/FrameworkResourceRepository.java | 76 + .../ide/eclipse/adt/sdk/IAndroidClassLoader.java | 81 + .../ide/eclipse/adt/sdk/LayoutParamsParser.java | 372 ++++ .../android/ide/eclipse/adt/sdk/LoadStatus.java | 24 + .../src/com/android/ide/eclipse/adt/sdk/Sdk.java | 282 +++ .../ide/eclipse/adt/sdk/WidgetClassLoader.java | 334 +++ .../wizards/newproject/NewProjectCreationPage.java | 1116 ++++++++++ .../adt/wizards/newproject/NewProjectWizard.java | 714 ++++++ .../ide/eclipse/common/AndroidConstants.java | 287 +++ .../ide/eclipse/common/EclipseUiHelper.java | 64 + .../com/android/ide/eclipse/common/Messages.java | 21 + .../android/ide/eclipse/common/SdkStatsHelper.java | 39 + .../android/ide/eclipse/common/StreamHelper.java | 63 + .../android/ide/eclipse/common/messages.properties | 2 + .../common/preferences/UsagePreferencePage.java | 123 + .../common/project/AndroidManifestHelper.java | 241 ++ .../common/project/AndroidManifestParser.java | 666 ++++++ .../common/project/AndroidXPathFactory.java | 89 + .../eclipse/common/project/BaseProjectHelper.java | 419 ++++ .../ide/eclipse/common/project/ExportHelper.java | 188 ++ .../common/project/ProjectChooserHelper.java | 110 + .../eclipse/common/project/XmlErrorHandler.java | 112 + .../eclipse/common/resources/AttrsXmlParser.java | 461 ++++ .../common/resources/DeclareStyleableInfo.java | 163 ++ .../eclipse/common/resources/IIdResourceItem.java | 28 + .../common/resources/IPathChangedListener.java | 29 + .../common/resources/IResourceRepository.java | 47 + .../ide/eclipse/common/resources/ResourceItem.java | 48 + .../ide/eclipse/common/resources/ResourceType.java | 111 + .../eclipse/common/resources/ViewClassInfo.java | 159 ++ .../ide/eclipse/editors/AndroidContentAssist.java | 767 +++++++ .../android/ide/eclipse/editors/AndroidEditor.java | 820 +++++++ .../eclipse/editors/AndroidSourceViewerConfig.java | 116 + .../ide/eclipse/editors/FirstElementParser.java | 164 ++ .../android/ide/eclipse/editors/IconFactory.java | 255 +++ .../editors/descriptors/AttributeDescriptor.java | 95 + .../AttributeDescriptorLabelProvider.java | 69 + .../descriptors/BooleanAttributeDescriptor.java | 33 + .../editors/descriptors/DescriptorsUtils.java | 809 +++++++ .../editors/descriptors/DocumentDescriptor.java | 57 + .../editors/descriptors/ElementDescriptor.java | 318 +++ .../descriptors/EnumAttributeDescriptor.java | 41 + .../descriptors/FlagAttributeDescriptor.java | 85 + .../editors/descriptors/IDescriptorProvider.java | 24 + .../descriptors/ListAttributeDescriptor.java | 71 + .../descriptors/ReferenceAttributeDescriptor.java | 84 + .../descriptors/SeparatorAttributeDescriptor.java | 45 + .../descriptors/TextAttributeDescriptor.java | 133 ++ .../editors/descriptors/TextValueDescriptor.java | 48 + .../descriptors/XmlnsAttributeDescriptor.java | 81 + .../ide/eclipse/editors/layout/BasePullParser.java | 220 ++ .../editors/layout/GraphicalLayoutEditor.java | 2355 ++++++++++++++++++++ .../eclipse/editors/layout/LayoutConstants.java | 65 + .../editors/layout/LayoutContentAssist.java | 33 + .../editors/layout/LayoutCreatorDialog.java | 140 ++ .../ide/eclipse/editors/layout/LayoutEditor.java | 411 ++++ .../editors/layout/LayoutReloadMonitor.java | 214 ++ .../editors/layout/LayoutSourceViewerConfig.java | 30 + .../eclipse/editors/layout/MatchingStrategy.java | 64 + .../ide/eclipse/editors/layout/PaletteFactory.java | 93 + .../eclipse/editors/layout/ProjectCallback.java | 161 ++ .../editors/layout/UiContentOutlinePage.java | 615 +++++ .../editors/layout/UiElementPullParser.java | 244 ++ .../editors/layout/UiPropertySheetPage.java | 139 ++ .../eclipse/editors/layout/WidgetPullParser.java | 142 ++ .../descriptors/CustomViewDescriptorService.java | 274 +++ .../layout/descriptors/LayoutDescriptors.java | 203 ++ .../layout/descriptors/ViewElementDescriptor.java | 139 ++ .../eclipse/editors/layout/parts/DropFeedback.java | 761 +++++++ .../editors/layout/parts/ElementCreateCommand.java | 98 + .../editors/layout/parts/ElementFigure.java | 75 + .../eclipse/editors/layout/parts/LayoutFigure.java | 151 ++ .../editors/layout/parts/UiDocumentEditPart.java | 212 ++ .../layout/parts/UiDocumentTreeEditPart.java | 37 + .../editors/layout/parts/UiElementEditPart.java | 345 +++ .../layout/parts/UiElementTreeEditPart.java | 74 + .../layout/parts/UiElementTreeEditPartFactory.java | 47 + .../layout/parts/UiElementsEditPartFactory.java | 55 + .../editors/layout/parts/UiLayoutEditPart.java | 115 + .../editors/layout/parts/UiLayoutTreeEditPart.java | 39 + .../editors/layout/parts/UiViewEditPart.java | 55 + .../editors/layout/parts/UiViewTreeEditPart.java | 30 + .../editors/layout/uimodel/UiViewElementNode.java | 129 ++ .../editors/manifest/ManifestContentAssist.java | 33 + .../eclipse/editors/manifest/ManifestEditor.java | 387 ++++ .../manifest/ManifestEditorContributor.java | 100 + .../manifest/ManifestSourceViewerConfig.java | 30 + .../descriptors/AndroidManifestDescriptors.java | 538 +++++ .../ApplicationAttributeDescriptor.java | 44 + .../descriptors/ClassAttributeDescriptor.java | 88 + .../InstrumentationAttributeDescriptor.java | 46 + .../descriptors/ManifestElementDescriptor.java | 96 + .../descriptors/PackageAttributeDescriptor.java | 41 + .../descriptors/PostActivityCreationAction.java | 88 + .../descriptors/PostReceiverCreationAction.java | 88 + .../descriptors/ThemeAttributeDescriptor.java | 42 + .../manifest/model/UiClassAttributeNode.java | 624 ++++++ .../manifest/model/UiManifestElementNode.java | 92 + .../manifest/model/UiPackageAttributeNode.java | 319 +++ .../manifest/pages/ApplicationAttributesPart.java | 174 ++ .../editors/manifest/pages/ApplicationPage.java | 126 ++ .../editors/manifest/pages/ApplicationToggle.java | 313 +++ .../manifest/pages/InstrumentationPage.java | 92 + .../editors/manifest/pages/OverviewExportPart.java | 87 + .../editors/manifest/pages/OverviewInfoPart.java | 222 ++ .../editors/manifest/pages/OverviewLinksPart.java | 125 ++ .../editors/manifest/pages/OverviewPage.java | 90 + .../editors/manifest/pages/PermissionPage.java | 101 + .../eclipse/editors/menu/MenuContentAssist.java | 33 + .../ide/eclipse/editors/menu/MenuEditor.java | 184 ++ .../editors/menu/MenuSourceViewerConfig.java | 30 + .../ide/eclipse/editors/menu/MenuTreePage.java | 62 + .../editors/menu/descriptors/MenuDescriptors.java | 196 ++ .../editors/resources/ResourcesContentAssist.java | 33 + .../eclipse/editors/resources/ResourcesEditor.java | 164 ++ .../resources/ResourcesSourceViewerConfig.java | 30 + .../editors/resources/ResourcesTreePage.java | 85 + .../configurations/CountryCodeQualifier.java | 144 ++ .../configurations/FolderConfiguration.java | 499 +++++ .../configurations/KeyboardStateQualifier.java | 180 ++ .../configurations/LanguageQualifier.java | 145 ++ .../configurations/NavigationMethodQualifier.java | 183 ++ .../configurations/NetworkCodeQualifier.java | 156 ++ .../configurations/PixelDensityQualifier.java | 144 ++ .../resources/configurations/RegionQualifier.java | 145 ++ .../configurations/ResourceQualifier.java | 86 + .../configurations/ScreenDimensionQualifier.java | 148 ++ .../configurations/ScreenOrientationQualifier.java | 178 ++ .../configurations/TextInputMethodQualifier.java | 182 ++ .../configurations/TouchScreenQualifier.java | 180 ++ .../descriptors/ColorValueDescriptor.java | 41 + .../descriptors/ItemElementDescriptor.java | 55 + .../descriptors/ResourcesDescriptors.java | 283 +++ .../resources/explorer/ResourceExplorerView.java | 333 +++ .../manager/CompiledResourcesMonitor.java | 211 ++ .../manager/ConfigurableResourceItem.java | 82 + .../resources/manager/FolderTypeRelationship.java | 164 ++ .../editors/resources/manager/IdResourceItem.java | 54 + .../editors/resources/manager/IntArrayWrapper.java | 50 + .../resources/manager/MultiResourceFile.java | 174 ++ .../resources/manager/ProjectClassLoader.java | 254 +++ .../resources/manager/ProjectResourceItem.java | 91 + .../resources/manager/ProjectResources.java | 810 +++++++ .../editors/resources/manager/Resource.java | 46 + .../editors/resources/manager/ResourceFile.java | 100 + .../editors/resources/manager/ResourceFolder.java | 251 +++ .../resources/manager/ResourceFolderType.java | 73 + .../editors/resources/manager/ResourceManager.java | 493 ++++ .../editors/resources/manager/ResourceMonitor.java | 348 +++ .../resources/manager/SingleResourceFile.java | 147 ++ .../resources/manager/files/FileWrapper.java | 87 + .../resources/manager/files/FolderWrapper.java | 73 + .../resources/manager/files/IAbstractFile.java | 44 + .../resources/manager/files/IAbstractFolder.java | 38 + .../resources/manager/files/IAbstractResource.java | 34 + .../resources/manager/files/IFileWrapper.java | 68 + .../resources/manager/files/IFolderWrapper.java | 74 + .../resources/uimodel/UiColorValueNode.java | 80 + .../resources/uimodel/UiItemElementNode.java | 58 + .../editors/ui/EditableDialogCellEditor.java | 458 ++++ .../eclipse/editors/ui/ErrorImageComposite.java | 47 + .../eclipse/editors/ui/FlagValueCellEditor.java | 58 + .../eclipse/editors/ui/ListValueCellEditor.java | 76 + .../editors/ui/ResourceValueCellEditor.java | 59 + .../ide/eclipse/editors/ui/SectionHelper.java | 348 +++ .../eclipse/editors/ui/TextValueCellEditor.java | 43 + .../ide/eclipse/editors/ui/UiElementPart.java | 283 +++ .../ide/eclipse/editors/ui/tree/CopyCutAction.java | 220 ++ .../ide/eclipse/editors/ui/tree/ICommitXml.java | 28 + .../editors/ui/tree/NewItemSelectionDialog.java | 230 ++ .../ide/eclipse/editors/ui/tree/PasteAction.java | 124 ++ .../ide/eclipse/editors/ui/tree/UiActions.java | 385 ++++ .../eclipse/editors/ui/tree/UiElementDetail.java | 486 ++++ .../ui/tree/UiModelTreeContentProvider.java | 115 + .../editors/ui/tree/UiModelTreeLabelProvider.java | 100 + .../ide/eclipse/editors/ui/tree/UiTreeBlock.java | 868 ++++++++ .../editors/uimodel/IUiSettableAttributeNode.java | 32 + .../eclipse/editors/uimodel/IUiUpdateListener.java | 47 + .../uimodel/UiAbstractTextAttributeNode.java | 119 + .../eclipse/editors/uimodel/UiAttributeNode.java | 155 ++ .../eclipse/editors/uimodel/UiDocumentNode.java | 135 ++ .../ide/eclipse/editors/uimodel/UiElementNode.java | 1500 +++++++++++++ .../editors/uimodel/UiFlagAttributeNode.java | 308 +++ .../editors/uimodel/UiListAttributeNode.java | 200 ++ .../editors/uimodel/UiResourceAttributeNode.java | 161 ++ .../editors/uimodel/UiSeparatorAttributeNode.java | 142 ++ .../editors/uimodel/UiTextAttributeNode.java | 190 ++ .../eclipse/editors/uimodel/UiTextValueNode.java | 118 + .../editors/wizards/ConfigurationSelector.java | 1279 +++++++++++ .../editors/wizards/NewXmlFileCreationPage.java | 1061 +++++++++ .../eclipse/editors/wizards/NewXmlFileWizard.java | 226 ++ .../editors/wizards/ReferenceChooserDialog.java | 267 +++ .../eclipse/editors/wizards/ResourceChooser.java | 193 ++ .../editors/wizards/ResourceContentProvider.java | 110 + .../editors/wizards/ResourceLabelProvider.java | 138 ++ .../ide/eclipse/editors/xml/XmlContentAssist.java | 33 + .../android/ide/eclipse/editors/xml/XmlEditor.java | 201 ++ .../eclipse/editors/xml/XmlSourceViewerConfig.java | 30 + .../ide/eclipse/editors/xml/XmlTreePage.java | 62 + .../editors/xml/descriptors/XmlDescriptors.java | 314 +++ .../com.android.ide.eclipse.common/.classpath | 9 - .../com.android.ide.eclipse.common/.project | 28 - .../META-INF/MANIFEST.MF | 22 - .../MODULE_LICENSE_EPL | 0 .../plugins/com.android.ide.eclipse.common/NOTICE | 224 -- .../build.properties | 7 - .../com.android.ide.eclipse.common/plugin.xml | 44 - .../ide/eclipse/common/AndroidConstants.java | 340 --- .../android/ide/eclipse/common/CommonPlugin.java | 165 -- .../ide/eclipse/common/EclipseUiHelper.java | 64 - .../com/android/ide/eclipse/common/Messages.java | 21 - .../android/ide/eclipse/common/SdkStatsHelper.java | 39 - .../android/ide/eclipse/common/StreamHelper.java | 63 - .../android/ide/eclipse/common/messages.properties | 2 - .../common/preferences/UsagePreferencePage.java | 123 - .../common/project/AndroidManifestHelper.java | 241 -- .../common/project/AndroidManifestParser.java | 632 ------ .../common/project/AndroidXPathFactory.java | 89 - .../eclipse/common/project/BaseProjectHelper.java | 420 ---- .../ide/eclipse/common/project/ExportHelper.java | 188 -- .../common/project/ProjectChooserHelper.java | 110 - .../eclipse/common/project/XmlErrorHandler.java | 102 - .../eclipse/common/resources/AttrsXmlParser.java | 461 ---- .../common/resources/DeclareStyleableInfo.java | 163 -- .../common/resources/FrameworkResourceManager.java | 318 --- .../eclipse/common/resources/IIdResourceItem.java | 28 - .../common/resources/IPathChangedListener.java | 29 - .../common/resources/IResourceRepository.java | 47 - .../ide/eclipse/common/resources/ResourceItem.java | 48 - .../ide/eclipse/common/resources/ResourceType.java | 102 - .../eclipse/common/resources/ViewClassInfo.java | 159 -- .../META-INF/MANIFEST.MF | 12 +- .../com.android.ide.eclipse.editors/.classpath | 15 - .../com.android.ide.eclipse.editors/.project | 28 - .../META-INF/MANIFEST.MF | 56 - .../MODULE_LICENSE_EPL | 0 .../plugins/com.android.ide.eclipse.editors/NOTICE | 224 -- .../build.properties | 10 - .../com.android.ide.eclipse.editors/icons/add.png | Bin 146 -> 0 bytes .../icons/android.png | Bin 3609 -> 0 bytes .../icons/android_large.png | Bin 6094 -> 0 bytes .../icons/az_sort.png | Bin 363 -> 0 bytes .../icons/delete.png | Bin 107 -> 0 bytes .../icons/dimension.png | Bin 320 -> 0 bytes .../com.android.ide.eclipse.editors/icons/down.png | Bin 157 -> 0 bytes .../com.android.ide.eclipse.editors/icons/dpi.png | Bin 302 -> 0 bytes .../icons/error.png | Bin 194 -> 0 bytes .../icons/keyboard.png | Bin 307 -> 0 bytes .../icons/language.png | Bin 287 -> 0 bytes .../icons/match.png | Bin 138 -> 0 bytes .../com.android.ide.eclipse.editors/icons/mnc.png | Bin 265 -> 0 bytes .../icons/navpad.png | Bin 308 -> 0 bytes .../icons/orientation.png | Bin 325 -> 0 bytes .../icons/region.png | Bin 321 -> 0 bytes .../icons/text_input.png | Bin 321 -> 0 bytes .../icons/touch.png | Bin 344 -> 0 bytes .../com.android.ide.eclipse.editors/icons/up.png | Bin 137 -> 0 bytes .../icons/warning.png | Bin 147 -> 0 bytes .../icons/world.png | Bin 562 -> 0 bytes .../com.android.ide.eclipse.editors/plugin.xml | 107 - .../ide/eclipse/editors/AndroidContentAssist.java | 733 ------ .../android/ide/eclipse/editors/AndroidEditor.java | 655 ------ .../eclipse/editors/AndroidSourceViewerConfig.java | 116 - .../android/ide/eclipse/editors/EditorsPlugin.java | 672 ------ .../ide/eclipse/editors/FirstElementParser.java | 164 -- .../android/ide/eclipse/editors/IconFactory.java | 258 --- .../editors/descriptors/AttributeDescriptor.java | 119 - .../AttributeDescriptorLabelProvider.java | 69 - .../descriptors/BooleanAttributeDescriptor.java | 33 - .../editors/descriptors/DescriptorsUtils.java | 751 ------- .../editors/descriptors/DocumentDescriptor.java | 57 - .../editors/descriptors/ElementDescriptor.java | 318 --- .../descriptors/EnumAttributeDescriptor.java | 41 - .../descriptors/FlagAttributeDescriptor.java | 85 - .../descriptors/ListAttributeDescriptor.java | 71 - .../descriptors/ReferenceAttributeDescriptor.java | 84 - .../descriptors/SeparatorAttributeDescriptor.java | 45 - .../descriptors/TextAttributeDescriptor.java | 133 -- .../editors/descriptors/TextValueDescriptor.java | 48 - .../descriptors/XmlnsAttributeDescriptor.java | 81 - .../editors/layout/GraphicalLayoutEditor.java | 1963 ---------------- .../editors/layout/LayoutContentAssist.java | 33 - .../editors/layout/LayoutCreatorDialog.java | 140 -- .../ide/eclipse/editors/layout/LayoutEditor.java | 369 --- .../editors/layout/LayoutReloadMonitor.java | 214 -- .../editors/layout/LayoutSourceViewerConfig.java | 30 - .../ide/eclipse/editors/layout/LayoutTreePage.java | 87 - .../eclipse/editors/layout/MatchingStrategy.java | 64 - .../ide/eclipse/editors/layout/PaletteFactory.java | 124 -- .../eclipse/editors/layout/ProjectCallback.java | 155 -- .../editors/layout/UiContentOutlinePage.java | 568 ----- .../editors/layout/UiElementPullParser.java | 405 ---- .../editors/layout/UiPropertySheetPage.java | 139 -- .../descriptors/CustomViewDescriptorService.java | 262 --- .../layout/descriptors/LayoutDescriptors.java | 200 -- .../layout/descriptors/ViewElementDescriptor.java | 139 -- .../editors/layout/parts/UiDocumentEditPart.java | 152 -- .../layout/parts/UiDocumentTreeEditPart.java | 37 - .../editors/layout/parts/UiElementEditPart.java | 199 -- .../layout/parts/UiElementTreeEditPart.java | 74 - .../layout/parts/UiElementTreeEditPartFactory.java | 47 - .../layout/parts/UiElementsEditPartFactory.java | 55 - .../editors/layout/parts/UiLayoutEditPart.java | 87 - .../editors/layout/parts/UiLayoutTreeEditPart.java | 39 - .../editors/layout/parts/UiViewEditPart.java | 56 - .../editors/layout/parts/UiViewTreeEditPart.java | 30 - .../editors/layout/uimodel/UiViewElementNode.java | 109 - .../editors/manifest/ManifestContentAssist.java | 34 - .../eclipse/editors/manifest/ManifestEditor.java | 327 --- .../manifest/ManifestEditorContributor.java | 100 - .../manifest/ManifestSourceViewerConfig.java | 30 - .../descriptors/AndroidManifestDescriptors.java | 497 ----- .../ApplicationAttributeDescriptor.java | 44 - .../descriptors/ClassAttributeDescriptor.java | 88 - .../InstrumentationAttributeDescriptor.java | 46 - .../descriptors/ManifestElementDescriptor.java | 96 - .../descriptors/PackageAttributeDescriptor.java | 41 - .../descriptors/PostActivityCreationAction.java | 88 - .../descriptors/PostReceiverCreationAction.java | 88 - .../descriptors/ThemeAttributeDescriptor.java | 42 - .../manifest/model/UiClassAttributeNode.java | 624 ------ .../manifest/model/UiManifestElementNode.java | 89 - .../manifest/model/UiPackageAttributeNode.java | 319 --- .../manifest/pages/ApplicationAttributesPart.java | 174 -- .../editors/manifest/pages/ApplicationPage.java | 120 - .../editors/manifest/pages/ApplicationToggle.java | 304 --- .../manifest/pages/InstrumentationPage.java | 65 - .../editors/manifest/pages/OverviewExportPart.java | 84 - .../editors/manifest/pages/OverviewInfoPart.java | 208 -- .../editors/manifest/pages/OverviewLinksPart.java | 94 - .../editors/manifest/pages/OverviewPage.java | 83 - .../editors/manifest/pages/PermissionPage.java | 74 - .../eclipse/editors/menu/MenuContentAssist.java | 33 - .../ide/eclipse/editors/menu/MenuEditor.java | 176 -- .../editors/menu/MenuSourceViewerConfig.java | 30 - .../ide/eclipse/editors/menu/MenuTreePage.java | 62 - .../editors/menu/descriptors/MenuDescriptors.java | 205 -- .../editors/resources/ResourcesContentAssist.java | 34 - .../eclipse/editors/resources/ResourcesEditor.java | 152 -- .../resources/ResourcesSourceViewerConfig.java | 30 - .../editors/resources/ResourcesTreePage.java | 85 - .../configurations/CountryCodeQualifier.java | 144 -- .../configurations/FolderConfiguration.java | 499 ----- .../configurations/KeyboardStateQualifier.java | 180 -- .../configurations/LanguageQualifier.java | 145 -- .../configurations/NavigationMethodQualifier.java | 183 -- .../configurations/NetworkCodeQualifier.java | 156 -- .../configurations/PixelDensityQualifier.java | 144 -- .../resources/configurations/RegionQualifier.java | 145 -- .../configurations/ResourceQualifier.java | 86 - .../configurations/ScreenDimensionQualifier.java | 148 -- .../configurations/ScreenOrientationQualifier.java | 178 -- .../configurations/TextInputMethodQualifier.java | 182 -- .../configurations/TouchScreenQualifier.java | 180 -- .../descriptors/ColorValueDescriptor.java | 41 - .../descriptors/ItemElementDescriptor.java | 55 - .../descriptors/ResourcesDescriptors.java | 208 -- .../resources/explorer/ResourceExplorerView.java | 331 --- .../manager/CompiledResourcesMonitor.java | 205 -- .../manager/ConfigurableResourceItem.java | 82 - .../resources/manager/FolderTypeRelationship.java | 164 -- .../editors/resources/manager/IdResourceItem.java | 54 - .../editors/resources/manager/IntArrayWrapper.java | 50 - .../resources/manager/MultiResourceFile.java | 174 -- .../resources/manager/ProjectClassLoader.java | 260 --- .../resources/manager/ProjectResourceItem.java | 91 - .../resources/manager/ProjectResources.java | 810 ------- .../editors/resources/manager/Resource.java | 46 - .../editors/resources/manager/ResourceFile.java | 100 - .../editors/resources/manager/ResourceFolder.java | 251 --- .../resources/manager/ResourceFolderType.java | 73 - .../editors/resources/manager/ResourceManager.java | 499 ----- .../editors/resources/manager/ResourceMonitor.java | 334 --- .../resources/manager/SingleResourceFile.java | 147 -- .../resources/manager/files/FileWrapper.java | 87 - .../resources/manager/files/FolderWrapper.java | 73 - .../resources/manager/files/IAbstractFile.java | 44 - .../resources/manager/files/IAbstractFolder.java | 38 - .../resources/manager/files/IAbstractResource.java | 34 - .../resources/manager/files/IFileWrapper.java | 68 - .../resources/manager/files/IFolderWrapper.java | 74 - .../resources/uimodel/UiColorValueNode.java | 80 - .../resources/uimodel/UiItemElementNode.java | 58 - .../editors/ui/EditableDialogCellEditor.java | 458 ---- .../eclipse/editors/ui/ErrorImageComposite.java | 47 - .../eclipse/editors/ui/FlagValueCellEditor.java | 58 - .../eclipse/editors/ui/ListValueCellEditor.java | 76 - .../editors/ui/ResourceValueCellEditor.java | 59 - .../ide/eclipse/editors/ui/SectionHelper.java | 348 --- .../eclipse/editors/ui/TextValueCellEditor.java | 43 - .../ide/eclipse/editors/ui/UiElementPart.java | 283 --- .../ide/eclipse/editors/ui/tree/CopyCutAction.java | 157 -- .../ide/eclipse/editors/ui/tree/ICommitXml.java | 28 - .../editors/ui/tree/NewItemSelectionDialog.java | 229 -- .../ide/eclipse/editors/ui/tree/PasteAction.java | 124 -- .../ide/eclipse/editors/ui/tree/UiActions.java | 298 --- .../eclipse/editors/ui/tree/UiElementDetail.java | 480 ---- .../ui/tree/UiModelTreeContentProvider.java | 115 - .../editors/ui/tree/UiModelTreeLabelProvider.java | 100 - .../ide/eclipse/editors/ui/tree/UiTreeBlock.java | 733 ------ .../editors/uimodel/IUiSettableAttributeNode.java | 32 - .../eclipse/editors/uimodel/IUiUpdateListener.java | 47 - .../uimodel/UiAbstractTextAttributeNode.java | 119 - .../eclipse/editors/uimodel/UiAttributeNode.java | 155 -- .../eclipse/editors/uimodel/UiDocumentNode.java | 135 -- .../ide/eclipse/editors/uimodel/UiElementNode.java | 1434 ------------ .../editors/uimodel/UiFlagAttributeNode.java | 302 --- .../editors/uimodel/UiListAttributeNode.java | 193 -- .../editors/uimodel/UiResourceAttributeNode.java | 167 -- .../editors/uimodel/UiSeparatorAttributeNode.java | 142 -- .../editors/uimodel/UiTextAttributeNode.java | 190 -- .../eclipse/editors/uimodel/UiTextValueNode.java | 118 - .../editors/wizards/ConfigurationSelector.java | 1272 ----------- .../editors/wizards/NewXmlFileCreationPage.java | 1009 --------- .../eclipse/editors/wizards/NewXmlFileWizard.java | 226 -- .../editors/wizards/ReferenceChooserDialog.java | 266 --- .../eclipse/editors/wizards/ResourceChooser.java | 193 -- .../editors/wizards/ResourceContentProvider.java | 110 - .../editors/wizards/ResourceLabelProvider.java | 138 -- .../ide/eclipse/editors/xml/XmlContentAssist.java | 33 - .../android/ide/eclipse/editors/xml/XmlEditor.java | 184 -- .../eclipse/editors/xml/XmlSourceViewerConfig.java | 30 - .../ide/eclipse/editors/xml/XmlTreePage.java | 62 - .../editors/xml/descriptors/XmlDescriptors.java | 292 --- .../com.android.ide.eclipse.platform/.classpath | 7 - .../com.android.ide.eclipse.platform/.project | 28 - .../META-INF/MANIFEST.MF | 15 - .../MODULE_LICENSE_EPL | 0 .../com.android.ide.eclipse.platform/NOTICE | 224 -- .../build.properties | 6 - .../icons/android_project.png | Bin 138 -> 0 bytes .../com.android.ide.eclipse.platform/plugin.xml | 67 - .../eclipse/platform/AndroidPlatformPlugin.java | 204 -- .../preferences/AndroidPreferencePage.java | 64 - .../platform/project/ConvertToPlatformAction.java | 149 -- .../PlatformClasspathContainerInitializer.java | 62 - .../eclipse/platform/project/PlatformNature.java | 98 - .../META-INF/MANIFEST.MF | 7 +- .../internal/StubSampleProjectCreationPage.java | 73 - .../project/internal/StubSampleProjectWizard.java | 105 - .../newproject/StubSampleProjectCreationPage.java | 71 + .../newproject/StubSampleProjectWizard.java | 102 + .../sampleProjects/SampleProjectTest.java | 5 +- .../adt/resources/AndroidJarLoaderTest.java | 163 -- .../adt/resources/LayoutParamsParserTest.java | 180 -- .../ide/eclipse/adt/sdk/AndroidJarLoaderTest.java | 161 ++ .../eclipse/adt/sdk/LayoutParamsParserTest.java | 180 ++ .../editors/layout/UiElementPullParserTest.java | 3 - 514 files changed, 46309 insertions(+), 43691 deletions(-) create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/add.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/az_sort.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/delete.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/dimension.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/down.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/dpi.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/error.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/keyboard.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/language.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/match.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/mcc.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/mnc.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/navpad.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/orientation.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/region.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/text_input.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/touch.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/up.png create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/icons/warning.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/SkinRepository.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/java/ReadOnlyJavaEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/NewXmlFileWizardAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/FinalExportPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCreationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeySelectionPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeystoreSelectionPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/PreExportPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/SigningExportPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectCreationPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectWizard.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/AndroidJarLoader.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceRepository.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/IAndroidLoader.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/LayoutParamsParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/WidgetListLoader.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/FrameworkResourceRepository.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LayoutParamsParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LoadStatus.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/EclipseUiHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/Messages.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/StreamHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/messages.properties create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ExportHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/IIdResourceItem.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/IPathChangedListener.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/IResourceRepository.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ResourceItem.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ResourceType.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/FirstElementParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/IconFactory.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/IDescriptorProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutConstants.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/WidgetPullParser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/DropFeedback.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementCreateCommand.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementFigure.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/LayoutFigure.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/Resource.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/SectionHelper.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/UiElementPart.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlEditor.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/.classpath delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/.project delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/META-INF/MANIFEST.MF delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/MODULE_LICENSE_EPL delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/NOTICE delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/build.properties delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/plugin.xml delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/AndroidConstants.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/CommonPlugin.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/EclipseUiHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/Messages.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/SdkStatsHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/StreamHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/messages.properties delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ExportHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/DeclareStyleableInfo.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/FrameworkResourceManager.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IIdResourceItem.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IPathChangedListener.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IResourceRepository.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceItem.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceType.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/.classpath delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/.project delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/META-INF/MANIFEST.MF delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/MODULE_LICENSE_EPL delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/NOTICE delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/build.properties delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/add.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/android.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/android_large.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/az_sort.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/delete.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/dimension.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/down.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/dpi.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/error.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/keyboard.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/language.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/match.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/mnc.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/navpad.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/orientation.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/region.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/text_input.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/touch.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/up.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/warning.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/icons/world.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/plugin.xml delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/EditorsPlugin.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/FirstElementParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/IconFactory.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutTreePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/Resource.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/SectionHelper.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/UiElementPart.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlEditor.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/.classpath delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/.project delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/META-INF/MANIFEST.MF delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/MODULE_LICENSE_EPL delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/NOTICE delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/build.properties delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/icons/android_project.png delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/plugin.xml delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/AndroidPlatformPlugin.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/preferences/AndroidPreferencePage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/ConvertToPlatformAction.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformClasspathContainerInitializer.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformNature.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectCreationPage.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectWizard.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/AndroidJarLoaderTest.java delete mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/LayoutParamsParserTest.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java create mode 100644 eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java (limited to 'eclipse/plugins') diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath index 4dd4cac..bbcdff9 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath +++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath @@ -5,5 +5,12 @@ + + + + + + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF index 9926074..a464d5c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF @@ -2,14 +2,20 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Android Development Toolkit Bundle-SymbolicName: com.android.ide.eclipse.adt;singleton:=true -Bundle-Version: 0.8.1.qualifier +Bundle-Version: 0.9.0.qualifier Bundle-ClassPath: ., jarutils.jar, - androidprefs.jar + androidprefs.jar, + sdkstats.jar, + kxml2-2.3.0.jar, + layoutlib_api.jar, + ninepatch.jar, + layoutlib_utils.jar, + sdklib.jar, + sdkuilib.jar Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin Bundle-Vendor: The Android Open Source Project -Require-Bundle: com.android.ide.eclipse.common, - com.android.ide.eclipse.ddms, +Require-Bundle: com.android.ide.eclipse.ddms, org.eclipse.core.runtime, org.eclipse.core.resources, org.eclipse.debug.core, @@ -26,11 +32,48 @@ Require-Bundle: com.android.ide.eclipse.common, org.eclipse.core.filesystem, org.eclipse.ui, org.eclipse.ui.ide, - org.eclipse.ui.forms + org.eclipse.ui.forms, + org.eclipse.gef, + org.eclipse.ui.browser, + org.eclipse.ui.views, + org.eclipse.wst.sse.core, + org.eclipse.wst.sse.ui, + org.eclipse.wst.xml.core, + org.eclipse.wst.xml.ui Eclipse-LazyStart: true -Export-Package: com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", +Export-Package: com.android.ide.eclipse.adt, + com.android.ide.eclipse.adt.build;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.project;x-friends:="com.android.ide.eclipse.tests", com.android.ide.eclipse.adt.project.internal;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.adt.resources;x-friends:="com.android.ide.eclipse.tests" + com.android.ide.eclipse.adt.sdk;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.adt.wizards.newproject;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.common, + com.android.ide.eclipse.common.project, + com.android.ide.eclipse.common.resources, + com.android.ide.eclipse.editors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.descriptors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.layout;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.layout.parts;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.manifest;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.manifest.model;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.manifest.pages;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.menu;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.menu.descriptors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.configurations;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.descriptors;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.explorer;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.manager;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.manager.files;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.resources.uimodel;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests", + com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests" diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/build.properties b/eclipse/plugins/com.android.ide.eclipse.adt/build.properties index 6d6c2e8..c7eb749 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/build.properties +++ b/eclipse/plugins/com.android.ide.eclipse.adt/build.properties @@ -5,6 +5,13 @@ bin.includes = plugin.xml,\ templates/,\ about.ini,\ jarutils.jar,\ - androidprefs.jar + androidprefs.jar,\ + sdkstats.jar,\ + kxml2-2.3.0.jar,\ + layoutlib_api.jar,\ + layoutlib_utils.jar,\ + ninepatch.jar,\ + sdklib.jar,\ + sdkuilib.jar source.. = src/ output.. = bin/ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/add.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/add.png new file mode 100644 index 0000000..eefc2ca Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/add.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/az_sort.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/az_sort.png new file mode 100644 index 0000000..5d92f76 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/az_sort.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/delete.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/delete.png new file mode 100644 index 0000000..db5fab8 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/delete.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/dimension.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/dimension.png new file mode 100644 index 0000000..10057c8 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/dimension.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/down.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/down.png new file mode 100644 index 0000000..36cd223 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/down.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/dpi.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/dpi.png new file mode 100644 index 0000000..fae5e96 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/dpi.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/error.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error.png new file mode 100644 index 0000000..1eecf2c Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/keyboard.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/keyboard.png new file mode 100644 index 0000000..7911a85 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/keyboard.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/language.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/language.png new file mode 100644 index 0000000..a727dd5 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/language.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/match.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match.png new file mode 100644 index 0000000..7e939c2 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/mcc.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/mcc.png new file mode 100644 index 0000000..4dc95d7 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/mcc.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/mnc.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/mnc.png new file mode 100644 index 0000000..aefffe4 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/mnc.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/navpad.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/navpad.png new file mode 100644 index 0000000..c2bb79a Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/navpad.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/orientation.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/orientation.png new file mode 100644 index 0000000..423c3cd Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/orientation.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/region.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/region.png new file mode 100644 index 0000000..9608cd6 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/region.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/text_input.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/text_input.png new file mode 100644 index 0000000..b4ddc87 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/text_input.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/touch.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/touch.png new file mode 100644 index 0000000..6536576 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/touch.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/up.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/up.png new file mode 100644 index 0000000..35b9a46 Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/up.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning.png new file mode 100644 index 0000000..ca3b6ed Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning.png differ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 51b1291..ade4646 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -2,6 +2,38 @@ + + + + + + + + + + + + + + + + + + + + @@ -47,7 +79,7 @@ + + @@ -174,6 +218,13 @@ label="Create Aidl preprocess file for Parcelable classes" menubarPath="com.android.ide.eclipse.adt.AndroidTools/group1"/> + + + + - - - + + + + @@ -305,4 +358,110 @@ keyConfigurationId="org.eclipse.ui.defaultAcceleratorConfiguration"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 6d52aa5..61b3f4d 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 @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt; + /** * Constant definition class.
*
@@ -39,6 +40,12 @@ public class AdtConstants { /** Generic marker for ADT errors. */ public final static String MARKER_ADT = AdtPlugin.PLUGIN_ID + ".adtProblem"; //$NON-NLS-1$ + /** Marker for Android Target errors. + * This is not cleared on each like other markers. Instead, it's cleared + * when a ContainerClasspathInitialized has succeeded in creating an + * {@link AndroidClasspathContainer}*/ + public final static String MARKER_TARGET = AdtPlugin.PLUGIN_ID + ".targetProblem"; //$NON-NLS-1$ + /** Build verbosity "Always". Those messages are always displayed. */ public final static int BUILD_ALWAYS = 0; 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 9b24b07..d9c18cf 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 @@ -22,34 +22,55 @@ import com.android.ddmuilib.console.DdmConsole; import com.android.ddmuilib.console.IDdmConsole; import com.android.ide.eclipse.adt.build.DexWrapper; import com.android.ide.eclipse.adt.debug.launching.AndroidLaunchController; -import com.android.ide.eclipse.adt.debug.ui.SkinRepository; import com.android.ide.eclipse.adt.preferences.BuildPreferencePage; import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.adt.project.export.ExportWizard; import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; -import com.android.ide.eclipse.adt.resources.FrameworkResourceParser; +import com.android.ide.eclipse.adt.sdk.AndroidTargetParser; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.CommonPlugin; import com.android.ide.eclipse.common.EclipseUiHelper; import com.android.ide.eclipse.common.SdkStatsHelper; import com.android.ide.eclipse.common.StreamHelper; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.ExportHelper; import com.android.ide.eclipse.common.project.ExportHelper.IExportCallback; -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; import com.android.ide.eclipse.ddms.DdmsPlugin; import com.android.ide.eclipse.ddms.ImageLoader; - +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.layout.LayoutEditor; +import com.android.ide.eclipse.editors.menu.MenuEditor; +import com.android.ide.eclipse.editors.resources.ResourcesEditor; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.xml.XmlEditor; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarkerDelta; import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Preferences; +import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.preference.IPreferenceStore; @@ -57,14 +78,21 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IEditorDescriptor; +import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.console.ConsolePlugin; +import org.eclipse.ui.console.IConsole; import org.eclipse.ui.console.IConsoleConstants; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.console.MessageConsoleStream; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; @@ -81,6 +109,8 @@ import java.io.PrintStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * The activator class controls the plug-in life cycle @@ -106,14 +136,17 @@ public class AdtPlugin extends AbstractUIPlugin { /** singleton instance */ private static AdtPlugin sPlugin; + private static Image sAndroidLogo; + private static ImageDescriptor sAndroidLogoDesc; + /** default store, provided by eclipse */ private IPreferenceStore mStore; /** cached location for the sdk folder */ private String mOsSdkLocation; - /** SDK Api Version */ - String mSdkApiVersion; + /** The global android console */ + private MessageConsole mAndroidConsole; /** Stream to write in the android console */ private MessageConsoleStream mAndroidConsoleStream; @@ -130,14 +163,14 @@ public class AdtPlugin extends AbstractUIPlugin { /** Color used in the error console */ private Color mRed; - private final ArrayList mPostDexProjects = new ArrayList(); + /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */ + private LoadStatus mSdkIsLoaded = LoadStatus.LOADING; + /** Project to update once the SDK is loaded. + * Any access MUST be in a synchronized(mPostLoadProjects) block */ + private final ArrayList mPostLoadProjects = new ArrayList(); - /** Boolean wrapper to run dialog in the UI thread, and still get the - * return code. - */ - private static final class BooleanWrapper { - public boolean b; - } + private ResourceMonitor mResourceMonitor; + private ArrayList mResourceRefreshListener = new ArrayList(); /** * Custom PrintStream for Dx output. This class overrides the method @@ -210,10 +243,14 @@ public class AdtPlugin extends AbstractUIPlugin { Display display = getDisplay(); + // set the default android console. + mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$ + ConsolePlugin.getDefault().getConsoleManager().addConsoles( + new IConsole[] { mAndroidConsole }); + // get the stream to write in the android console. - MessageConsole androidConsole = CommonPlugin.getDefault().getAndroidConsole(); - mAndroidConsoleStream = androidConsole.newMessageStream(); - mAndroidConsoleErrorStream = androidConsole.newMessageStream(); + mAndroidConsoleStream = mAndroidConsole.newMessageStream(); + mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream(); mRed = new Color(display, 0xFF, 0x00, 0x00); // because this can be run, in some cases, by a non ui thread, and beccause @@ -267,22 +304,19 @@ public class AdtPlugin extends AbstractUIPlugin { // get the SDK location and build id. if (checkSdkLocationAndId()) { - // if sdk if valid, reparse the skin folder - SkinRepository.getInstance().parseFolder(getOsSkinFolder()); + // if sdk if valid, reparse it + + // add the current Android project to the list of projects to be updated + // after the SDK is reloaded + synchronized (mPostLoadProjects) { + // get the project to refresh. + IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(); + mPostLoadProjects.addAll(Arrays.asList(androidProjects)); + } + + // parse the SDK resources at the new location + parseSdkContent(); } - - // parse the SDK resources at the new location - parseSdkContent(); - - // get the project to refresh. - IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(); - - // Setup the new container for each project. By providing new instances of - // AndroidClasspathContainer, this will force JDT to call - // IClasspathContainer#getClasspathEntries() again and receive the new - // path to the framework jar. - AndroidClasspathContainerInitializer.updateProjects(androidProjects); - } else if (PREFS_BUILD_VERBOSITY.equals(property)) { mBuildVerbosity = BuildPreferencePage.getBuildLevel( mStore.getString(PREFS_BUILD_VERBOSITY)); @@ -299,20 +333,11 @@ public class AdtPlugin extends AbstractUIPlugin { } // check the location of SDK - if (checkSdkLocationAndId()) { - // if sdk if valid, parse the skin folder - SkinRepository.getInstance().parseFolder(getOsSkinFolder()); - - // parse the SDK resources. - parseSdkContent(); - } + final boolean isSdkLocationValid = checkSdkLocationAndId(); mBuildVerbosity = BuildPreferencePage.getBuildLevel( mStore.getString(PREFS_BUILD_VERBOSITY)); - // Ping the usage start server. - pingUsageServer(); - // create the loader that's able to load the images mLoader = new ImageLoader(this); @@ -356,18 +381,31 @@ public class AdtPlugin extends AbstractUIPlugin { dialog.open(); } }); - - /* The Editors plugin must be started as soon as Android projects are opened or created, - * in order to properly set default editors on the layout/values XML files. - * - * This ensures that the default editors is really only set when a new XML file - * is added to the workspace (IResourceDelta.ADDED event), through project creation or - * manual add. - * Other methods would force to go through existing projects when the Editors plugin is - * started, and set the default editors for their XML files, possibly erasing user set - * default editors. - */ - startEditorsPlugin(); + + // initialize editors + startEditors(); + + // Ping the usage server and parse the SDK content. + // This is deferred in separate jobs to avoid blocking the bundle start. + // We also serialize them to avoid too many parallel jobs when Eclipse starts. + Job pingJob = createPingUsageServerJob(); + pingJob.addJobChangeListener(new JobChangeAdapter() { + @Override + public void done(IJobChangeEvent event) { + super.done(event); + + // Once the ping job is finished, start the SDK parser + if (isSdkLocationValid) { + // parse the SDK resources. + parseSdkContent(); + } + } + }); + // build jobs are run after other interactive jobs + pingJob.setPriority(Job.BUILD); + // Wait 2 seconds before starting the ping job. This leaves some time to the + // other bundles to initialize. + pingJob.schedule(2000 /*milliseconds*/); } /* @@ -379,6 +417,8 @@ public class AdtPlugin extends AbstractUIPlugin { public void stop(BundleContext context) throws Exception { super.stop(context); + stopEditors(); + DexWrapper.unloadDex(); mRed.dispose(); @@ -418,37 +458,22 @@ public class AdtPlugin extends AbstractUIPlugin { /** Returns the adb path relative to the sdk folder */ public static String getOsRelativeAdb() { - return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB; + return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_ADB; } /** Returns the aapt path relative to the sdk folder */ public static String getOsRelativeAapt() { - return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT; + return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AAPT; } /** Returns the emulator path relative to the sdk folder */ public static String getOsRelativeEmulator() { - return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR; + return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_EMULATOR; } /** Returns the aidl path relative to the sdk folder */ public static String getOsRelativeAidl() { - return AndroidConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL; - } - - /** Returns the framework jar path relative to the sdk folder */ - public static String getOsRelativeFramework() { - return AndroidConstants.FN_FRAMEWORK_LIBRARY; - } - - /** Returns the android sources path relative to the sdk folder */ - public static String getOsRelativeAndroidSources() { - return AndroidConstants.FD_ANDROID_SOURCES; - } - - /** Returns the framework jar path relative to the sdk folder */ - public static String getOsRelativeAttrsXml() { - return AndroidConstants.OS_SDK_LIBS_FOLDER + AndroidConstants.FN_ATTRS_XML; + return SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_AIDL; } /** Returns the absolute adb path */ @@ -458,7 +483,7 @@ public class AdtPlugin extends AbstractUIPlugin { /** Returns the absolute traceview path */ public static String getOsAbsoluteTraceview() { - return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER + + return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + AndroidConstants.FN_TRACEVIEW; } @@ -467,21 +492,6 @@ public class AdtPlugin extends AbstractUIPlugin { return getOsSdkFolder() + getOsRelativeAapt(); } - /** Returns the absolute sdk framework path */ - public static String getOsAbsoluteFramework() { - return getOsSdkFolder() + getOsRelativeFramework(); - } - - /** Returns the absolute android sources path in the sdk */ - public static String getOsAbsoluteAndroidSources() { - return getOsSdkFolder() + getOsRelativeAndroidSources(); - } - - /** Returns the absolute attrs.xml path */ - public static String getOsAbsoluteAttrsXml() { - return getOsSdkFolder() + getOsRelativeAttrsXml(); - } - /** Returns the absolute emulator path */ public static String getOsAbsoluteEmulator() { return getOsSdkFolder() + getOsRelativeEmulator(); @@ -492,16 +502,6 @@ public class AdtPlugin extends AbstractUIPlugin { return getOsSdkFolder() + getOsRelativeAidl(); } - /** Returns the absolute path to the aidl framework import file. */ - public static String getOsAbsoluteFrameworkAidl() { - return getOsSdkFolder() + AndroidConstants.OS_SDK_LIBS_FOLDER + - AndroidConstants.FN_FRAMEWORK_AIDL; - } - - public static String getOsSdkSamplesFolder() { - return getOsSdkFolder() + AndroidConstants.OS_SDK_SAMPLES_FOLDER; - } - /** * Returns a Url file path to the javaDoc folder. */ @@ -526,11 +526,7 @@ public class AdtPlugin extends AbstractUIPlugin { } public static String getOsSdkToolsFolder() { - return getOsSdkFolder() + AndroidConstants.OS_SDK_TOOLS_FOLDER; - } - - public static String getOsSkinFolder() { - return getOsSdkFolder() + AndroidConstants.OS_SDK_SKINS_FOLDER; + return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER; } public static synchronized boolean getAutoResRefresh() { @@ -540,18 +536,6 @@ public class AdtPlugin extends AbstractUIPlugin { return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH); } - /** - * Returns the SDK build id. - * @return a string containing the SDK build id, or null it it is unknownn. - */ - public static synchronized String getSdkApiVersion() { - if (sPlugin != null) { - return sPlugin.mSdkApiVersion; - } - - return null; - } - public static synchronized int getBuildVerbosity() { if (sPlugin != null) { return sPlugin.mBuildVerbosity; @@ -705,23 +689,25 @@ public class AdtPlugin extends AbstractUIPlugin { final Display display = getDisplay(); // we need to ask the user what he wants to do. - final BooleanWrapper wrapper = new BooleanWrapper(); + final boolean[] result = new boolean[1]; display.syncExec(new Runnable() { public void run() { Shell shell = display.getActiveShell(); - wrapper.b = MessageDialog.openQuestion(shell, title, message); + result[0] = MessageDialog.openQuestion(shell, title, message); } }); - return wrapper.b; + return result[0]; } /** * Logs a message to the default Eclipse log. * - * @param severity The severity code. Valid values are: {@link IStatus#OK}, {@link IStatus#ERROR}, - * {@link IStatus#INFO}, {@link IStatus#WARNING} or {@link IStatus#CANCEL}. + * @param severity The severity code. Valid values are: {@link IStatus#OK}, + * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or + * {@link IStatus#CANCEL}. * @param format The format string, like for {@link String#format(String, Object...)}. - * @param args The arguments for the format string, like for {@link String#format(String, Object...)}. + * @param args The arguments for the format string, like for + * {@link String#format(String, Object...)}. */ public static void log(int severity, String format, Object ... args) { String message = String.format(format, args); @@ -845,7 +831,7 @@ public class AdtPlugin extends AbstractUIPlugin { // now make sure it's not docked. ConsolePlugin.getDefault().getConsoleManager().showConsoleView( - CommonPlugin.getDefault().getAndroidConsole()); + AdtPlugin.getDefault().getAndroidConsole()); } /** @@ -883,21 +869,17 @@ public class AdtPlugin extends AbstractUIPlugin { } /** - * Adds a {@link IJavaProject} to a list of projects to be recompiled once dx.jar is loaded. - * @param javaProject + * Returns whether the Sdk has been loaded. If the SDK has not been loaded, the given + * project is added to a list of projects to recompile after the SDK is loaded. */ - public void addPostDexProject(IJavaProject javaProject) { - synchronized (mPostDexProjects) { - if (DexWrapper.getStatus() == DexWrapper.LoadStatus.LOADED) { - // Setup the new container for each project. By providing new instances of - // AndroidClasspathContainer, this will force JDT to call - // IClasspathContainer#getClasspathEntries() again and receive the new - // path to the framework jar, and the project will be recompiled. - AndroidClasspathContainerInitializer.updateProjects(new IJavaProject [] { - javaProject }); - } else { - mPostDexProjects.add(javaProject); + public LoadStatus getSdkLoadStatus(IJavaProject project) { + synchronized (mPostLoadProjects) { + // only add the project to the list, if we are still loading. + if (mSdkIsLoaded == LoadStatus.LOADING && project != null) { + mPostLoadProjects.add(project); } + + return mSdkIsLoaded; } } @@ -907,9 +889,6 @@ public class AdtPlugin extends AbstractUIPlugin { * @return false if the location is not correct. */ private boolean checkSdkLocationAndId() { - // Reset the sdk build first in case the SDK is invalid and we abort. - mSdkApiVersion = null; - if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) { displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup); return false; @@ -949,17 +928,16 @@ public class AdtPlugin extends AbstractUIPlugin { String.format(Messages.Could_Not_Find_Folder, osSdkLocation)); } - String osTools = osSdkLocation + AndroidConstants.OS_SDK_TOOLS_FOLDER; + String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; File toolsFolder = new File(osTools); if (toolsFolder.isDirectory() == false) { return errorHandler.handleError( String.format(Messages.Could_Not_Find_Folder_In_SDK, - AndroidConstants.FD_TOOLS, osSdkLocation)); + SdkConstants.FD_TOOLS, osSdkLocation)); } // check the path to various tools we use String[] filesToCheck = new String[] { - osSdkLocation + getOsRelativeFramework(), osSdkLocation + getOsRelativeAdb(), osSdkLocation + getOsRelativeAapt(), osSdkLocation + getOsRelativeAidl(), @@ -990,118 +968,106 @@ public class AdtPlugin extends AbstractUIPlugin { } /** - * Pings the usage start server. + * Creates a job than can ping the usage server. */ - private void pingUsageServer() { + private Job createPingUsageServerJob() { // In order to not block the plugin loading, so we spawn another thread. - new Thread("Ping!") { //$NON-NLS-1$ + Job job = new Job("Android SDK Ping") { // Job name, visible in progress view @Override - public void run() { - // get the version of the plugin - String versionString = (String) getBundle().getHeaders().get( - Constants.BUNDLE_VERSION); - Version version = new Version(versionString); - - SdkStatsHelper.pingUsageServer("adt", version); //$NON-NLS-1$ - } - }.start(); - } - - /** - * Starts the Editors plugin. - *

- * Since we do not want any dependencies between the plugins (Editors is an optional - * plugin not needed for Android development), we attempt to start the plugin through - * OSGi directly. - *

- * This is done in another thread to not delay the start of this plugin. - */ - private void startEditorsPlugin() { - new Thread() { - @Override - public void run() { + protected IStatus run(IProgressMonitor monitor) { try { - // look for the bundle of the Editors plugin - Bundle editorsBundle = Platform.getBundle(AndroidConstants.EDITORS_PLUGIN_ID); - if (editorsBundle != null) { - // we only start if the bundle is installed and not started. - // STARTING means that its start is pending a triggering. - int bundleState = editorsBundle.getState(); - if ((bundleState & (Bundle.RESOLVED | Bundle.INSTALLED - | Bundle.STARTING)) != 0) { - // Attempt to start it. - // START_TRANSIENT is used because we don't want - // to change the auto start value. - editorsBundle.start(Bundle.START_TRANSIENT); - } - } - } catch (Exception e) { - log(e, Messages.AdtPlugin_Failed_To_Start_s, AndroidConstants.EDITORS_PLUGIN_ID); + + // get the version of the plugin + String versionString = (String) getBundle().getHeaders().get( + Constants.BUNDLE_VERSION); + Version version = new Version(versionString); + + SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$ + + return Status.OK_STATUS; + } catch (Throwable t) { + log(t, "pingUsageServer failed"); //$NON-NLS-1$ + return new Status(IStatus.ERROR, PLUGIN_ID, + "pingUsageServer failed", t); } } - }.start(); + }; + return job; } /** - * Parses the SDK resources and set them in the {@link FrameworkResourceManager}. + * Parses the SDK resources. */ private void parseSdkContent() { // Perform the update in a thread (here an Eclipse runtime job) // since this should never block the caller (especially the start method) - new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) { + Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) { + @SuppressWarnings("unchecked") @Override protected IStatus run(IProgressMonitor monitor) { - try { - SubMonitor progress = null; - try { - progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 100); + try { + SubMonitor progress = SubMonitor.convert(monitor, + "Initialize SDK Manager", 100); + + Sdk sdk = Sdk.loadSdk(mOsSdkLocation); + + if (sdk != null) { - // load the values. - FrameworkResourceParser parser = new FrameworkResourceParser(); - parser.parse(mOsSdkLocation, FrameworkResourceManager.getInstance(), - progress); - - // set the location of the layout lib jar file. - FrameworkResourceManager.getInstance().setLayoutLibLocation( - mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_LAYOUTLIB_JAR); - FrameworkResourceManager.getInstance().setFrameworkResourcesLocation( - mOsSdkLocation + AndroidConstants.OS_SDK_RESOURCES_FOLDER); - FrameworkResourceManager.getInstance().setFrameworkFontLocation( - mOsSdkLocation + AndroidConstants.OS_SDK_FONTS_FOLDER); - } catch (Throwable e) { - AdtPlugin.log(e, "Android SDK Resource Parser failed"); //$NON-NLS-1$ - AdtPlugin.printErrorToConsole(Messages.AdtPlugin_Android_SDK_Resource_Parser, - Messages.AdtPlugin_Failed_To_Parse_s + e.getMessage()); + progress.setTaskName(Messages.AdtPlugin_Parsing_Resources); - return new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e); - } finally { - if (progress != null) { - progress.worked(100); + for (IAndroidTarget target : sdk.getTargets()) { + IStatus status = new AndroidTargetParser(target).run(progress); + if (status.getCode() != IStatus.OK) { + synchronized (mPostLoadProjects) { + mSdkIsLoaded = LoadStatus.FAILED; + mPostLoadProjects.clear(); + } + return status; + } } - } - - try { - progress = SubMonitor.convert(monitor, Messages.AdtPlugin_Parsing_Resources, 20); + + // FIXME: move this per platform, or somewhere else. + progress = SubMonitor.convert(monitor, + Messages.AdtPlugin_Parsing_Resources, 20); DexWrapper.unloadDex(); IStatus res = DexWrapper.loadDex( mOsSdkLocation + AndroidConstants.OS_SDK_LIBS_DX_JAR); if (res != Status.OK_STATUS) { + synchronized (mPostLoadProjects) { + mSdkIsLoaded = LoadStatus.FAILED; + mPostLoadProjects.clear(); + } return res; - } else { + } + + synchronized (mPostLoadProjects) { + mSdkIsLoaded = LoadStatus.LOADED; + // update the project that needs recompiling. - synchronized (mPostDexProjects) { - if (mPostDexProjects.size() > 0) { - IJavaProject[] array = mPostDexProjects.toArray( - new IJavaProject[mPostDexProjects.size()]); - AndroidClasspathContainerInitializer.updateProjects(array); - mPostDexProjects.clear(); - } + if (mPostLoadProjects.size() > 0) { + IJavaProject[] array = mPostLoadProjects.toArray( + new IJavaProject[mPostLoadProjects.size()]); + AndroidClasspathContainerInitializer.updateProjects(array); + mPostLoadProjects.clear(); } } - } finally { - if (progress != null) { - progress.worked(20); + } + + // Notify resource changed listeners + progress.subTask("Refresh UI"); + progress.setWorkRemaining(mResourceRefreshListener.size()); + + // Clone the list before iterating, to avoid Concurrent Modification + // exceptions + List listeners = (List)mResourceRefreshListener.clone(); + for (Runnable listener : listeners) { + try { + AdtPlugin.getDisplay().syncExec(listener); + } catch (Exception e) { + AdtPlugin.log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$ + } finally { + progress.worked(1); } } } finally { @@ -1112,6 +1078,252 @@ public class AdtPlugin extends AbstractUIPlugin { return Status.OK_STATUS; } - }.schedule(); + }; + job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs + job.schedule(); + } + + /** Returns the global android console */ + public MessageConsole getAndroidConsole() { + return mAndroidConsole; + } + + // ----- Methods for Editors ------- + + public void startEditors() { + sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID, + "/icons/android.png"); //$NON-NLS-1$ + sAndroidLogo = sAndroidLogoDesc.createImage(); + + // get the stream to write in the android console. + MessageConsole androidConsole = AdtPlugin.getDefault().getAndroidConsole(); + mAndroidConsoleStream = androidConsole.newMessageStream(); + + mAndroidConsoleErrorStream = androidConsole.newMessageStream(); + mRed = new Color(getDisplay(), 0xFF, 0x00, 0x00); + + // because this can be run, in some cases, by a non ui thread, and beccause + // changing the console properties update the ui, we need to make this change + // in the ui thread. + getDisplay().asyncExec(new Runnable() { + public void run() { + mAndroidConsoleErrorStream.setColor(mRed); + } + }); + + // Add a resource listener to handle compiled resources. + IWorkspace ws = ResourcesPlugin.getWorkspace(); + mResourceMonitor = ResourceMonitor.startMonitoring(ws); + + if (mResourceMonitor != null) { + try { + setupDefaultEditor(mResourceMonitor); + ResourceManager.setup(mResourceMonitor); + } catch (Throwable t) { + log(t, "ResourceManager.setup failed"); //$NON-NLS-1$ + } + } + } + + /** + * The AbstractUIPlugin implementation of this Plugin + * method saves this plug-in's preference and dialog stores and shuts down + * its image registry (if they are in use). Subclasses may extend this + * method, but must send super last. A try-finally statement should + * be used where necessary to ensure that super.shutdown() is + * always done. + * + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stopEditors() { + sAndroidLogo.dispose(); + + IconFactory.getInstance().Dispose(); + + // Remove the resource listener that handles compiled resources. + IWorkspace ws = ResourcesPlugin.getWorkspace(); + ResourceMonitor.stopMonitoring(ws); + + mRed.dispose(); + } + + /** + * Returns an Image for the small Android logo. + * + * Callers should not dispose it. + */ + public static Image getAndroidLogo() { + return sAndroidLogo; + } + + /** + * Returns an {@link ImageDescriptor} for the small Android logo. + * + * Callers should not dispose it. + */ + public static ImageDescriptor getAndroidLogoDesc() { + return sAndroidLogoDesc; + } + + /** + * Returns the ResourceMonitor object. + */ + public ResourceMonitor getResourceMonitor() { + return mResourceMonitor; + } + + /** + * Sets up the editor to register default editors for resource files when needed. + * + * This is called by the {@link AdtPlugin} during initialization. + * + * @param monitor The main Resource Monitor object. + */ + public void setupDefaultEditor(ResourceMonitor monitor) { + monitor.addFileListener(new IFileListener() { + + private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$ + + /* (non-Javadoc) + * Sent when a file changed. + * @param file The file that changed. + * @param markerDeltas The marker deltas for the file. + * @param kind The change kind. This is equivalent to + * {@link IResourceDelta#accept(IResourceDeltaVisitor)} + * + * @see IFileListener#fileChanged + */ + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { + // The resources files must have a file path similar to + // project/res/.../*.xml + // There is no support for sub folders, so the segment count must be 4 + if (file.getFullPath().segmentCount() == 4) { + // check if we are inside the res folder. + String segment = file.getFullPath().segment(1); + if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) { + // we are inside a res/ folder, get the actual ResourceFolder + ProjectResources resources = ResourceManager.getInstance(). + getProjectResources(file.getProject()); + + // This happens when importing old Android projects in Eclipse + // that lack the container (probably because resources fail to build + // properly.) + if (resources == null) { + log(IStatus.INFO, + "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$ + file.getFullPath().toOSString(), + file.getProject().getName()); + return; + } + + ResourceFolder resFolder = resources.getResourceFolder( + (IFolder)file.getParent()); + + if (resFolder != null) { + if (kind == IResourceDelta.ADDED) { + resourceAdded(file, resFolder.getType()); + } else if (kind == IResourceDelta.CHANGED) { + resourceChanged(file, resFolder.getType()); + } + } else { + // if the res folder is null, this means the name is invalid, + // in this case we remove whatever android editors that was set + // as the default editor. + IEditorDescriptor desc = IDE.getDefaultEditor(file); + String editorId = desc.getId(); + if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) { + // reset the default editor. + IDE.setDefaultEditor(file, null); + } + } + } + } + } + } + + private void resourceAdded(IFile file, ResourceFolderType type) { + // set the default editor based on the type. + if (type == ResourceFolderType.LAYOUT) { + IDE.setDefaultEditor(file, LayoutEditor.ID); + } else if (type == ResourceFolderType.DRAWABLE + || type == ResourceFolderType.VALUES) { + IDE.setDefaultEditor(file, ResourcesEditor.ID); + } else if (type == ResourceFolderType.MENU) { + IDE.setDefaultEditor(file, MenuEditor.ID); + } else if (type == ResourceFolderType.XML) { + if (XmlEditor.canHandleFile(file)) { + IDE.setDefaultEditor(file, XmlEditor.ID); + } else { + // set a property to determine later if the XML can be handled + QualifiedName qname = new QualifiedName( + AdtPlugin.PLUGIN_ID, + UNKNOWN_EDITOR); + try { + file.setPersistentProperty(qname, "1"); + } catch (CoreException e) { + // pass + } + } + } + } + + private void resourceChanged(IFile file, ResourceFolderType type) { + if (type == ResourceFolderType.XML) { + IEditorDescriptor ed = IDE.getDefaultEditor(file); + if (ed == null || ed.getId() != XmlEditor.ID) { + QualifiedName qname = new QualifiedName( + AdtPlugin.PLUGIN_ID, + UNKNOWN_EDITOR); + String prop = null; + try { + prop = file.getPersistentProperty(qname); + } catch (CoreException e) { + // pass + } + if (prop != null && XmlEditor.canHandleFile(file)) { + try { + // remove the property & set editor + file.setPersistentProperty(qname, null); + IWorkbenchPage page = PlatformUI.getWorkbench(). + getActiveWorkbenchWindow().getActivePage(); + + IEditorPart oldEditor = page.findEditor(new FileEditorInput(file)); + if (oldEditor != null && + AdtPlugin.displayPrompt("Android XML Editor", + String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?", + file.getFullPath()))) { + IDE.setDefaultEditor(file, XmlEditor.ID); + IEditorPart newEditor = page.openEditor( + new FileEditorInput(file), + XmlEditor.ID, + true, /* activate */ + IWorkbenchPage.MATCH_NONE); + + if (newEditor != null) { + page.closeEditor(oldEditor, true /* save */); + } + } + } catch (CoreException e) { + // setPersistentProperty or page.openEditor may have failed + } + } + } + } + } + + }, IResourceDelta.ADDED | IResourceDelta.CHANGED); + } + + public void addResourceChangedListener(Runnable resourceRefreshListener) { + mResourceRefreshListener.add(resourceRefreshListener); + } + + public void removeResourceChangedListener(Runnable resourceRefreshListener) { + mResourceRefreshListener.remove(resourceRefreshListener); + } + + public static synchronized OutputStream getErrorStream() { + return sPlugin.mAndroidConsoleErrorStream; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/VersionCheck.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/VersionCheck.java index bac940a..6d85af3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/VersionCheck.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/VersionCheck.java @@ -16,9 +16,8 @@ package com.android.ide.eclipse.adt; -import com.android.ddmlib.Device; import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler; -import com.android.ide.eclipse.common.AndroidConstants; +import com.android.sdklib.SdkConstants; import org.osgi.framework.Constants; import org.osgi.framework.Version; @@ -42,19 +41,6 @@ import java.util.regex.Pattern; * */ final class VersionCheck { - - /** Pattern to get the SDK build incremental version from the - * $SDK/tools/lib/build.prop file. */ - private final static Pattern sBuildVersionPattern = Pattern.compile( - "^" + Device.PROP_BUILD_VERSION + "=(.+)$"); //$NON-NLS-1$ - - /** - * Pattern to parse release type SDK version number. This parses the content read with - * sBuildIdPattern. - */ - private final static Pattern sSdkVersionPattern = Pattern.compile( - "^(\\d+)\\.(\\d+)_r(\\d+)$", Pattern.CASE_INSENSITIVE); //$NON-NLS-1$ - /** * Pattern to get the minimum plugin version supported by the SDK. This is read from * the file $SDK/tools/lib/plugin.prop. @@ -69,58 +55,7 @@ final class VersionCheck { */ public static boolean checkVersion(String osSdkPath, CheckSdkErrorHandler errorHandler) { AdtPlugin plugin = AdtPlugin.getDefault(); - String osLibs = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER; - - /* - * All plugins should work with all SDKs. Newer SDKs may require a newer plugin - * but this is handled below. - * Still, we need to grab the SDK version from this file. This is used - * to compare to running emulator/device when launching run/debug sessions. - */ - try { - FileReader reader = new FileReader(osLibs + AndroidConstants.FN_BUILD_PROP); - BufferedReader bReader = new BufferedReader(reader); - String line; - while ((line = bReader.readLine()) != null) { - Matcher m = sBuildVersionPattern.matcher(line); - if (m.matches()) { - plugin.mSdkApiVersion = m.group(1).trim(); - - /* - * No checks on the version at the moment. - */ - /* - if (plugin.mSdkBuildVersion != null) { - // attempt to get version number from the build id - m = sSdkVersionPattern.matcher(plugin.mSdkBuildVersion); - if (m.matches()) { - // get the platform version number - int platformMajor = Integer.parseInt(m.group(1)); - int platformMinor = Integer.parseInt(m.group(2)); - @SuppressWarnings("unused") //$NON-NLS-1$ - int sdkRelease = Integer.parseInt(m.group(3)); - - if (platformMajor != 0 || platformMinor != 9) { - return errorHandler.handleError(String.format( - "This version of ADT requires the Android SDK version 0.9\n\nCurrent version is %1$s.\n\nPlease update your SDK to the latest version.", - plugin.mSdkBuildVersion)); - } - } else { - // unknown version format. - AdtPlugin.printErrorToConsole( - (Object)String.format(Messages.VersionCheck_Unable_To_Parse_Version_s, - plugin.mSdkBuildVersion)); - } - } - */ - break; - } - } - } catch (FileNotFoundException e) { - // the build id will be null, and this is handled by the builders. - } catch (IOException e) { - // the build id will be null, and this is handled by the builders. - } + String osLibs = osSdkPath + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER; // get the plugin property file, and grab the minimum plugin version required // to work with the sdk @@ -128,7 +63,7 @@ final class VersionCheck { int minMinorVersion = -1; int minMicroVersion = -1; try { - FileReader reader = new FileReader(osLibs + AndroidConstants.FN_PLUGIN_PROP); + FileReader reader = new FileReader(osLibs + SdkConstants.FN_PLUGIN_PROP); BufferedReader bReader = new BufferedReader(reader); String line; while ((line = bReader.readLine()) != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java index 0b1b647..4d16120 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java @@ -19,6 +19,8 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.jarutils.DebugKeyProvider; @@ -28,6 +30,7 @@ import com.android.jarutils.DebugKeyProvider.IKeyGenOutput; import com.android.jarutils.DebugKeyProvider.KeytoolException; import com.android.jarutils.SignedJarBuilder.IZipEntryFilter; import com.android.prefs.AndroidLocation.AndroidLocationException; +import com.android.sdklib.IAndroidTarget; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -44,6 +47,7 @@ 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; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -296,6 +300,15 @@ public class ApkBuilder extends BaseBuilder { saveProjectBooleanProperty(PROPERTY_PACKAGE_RESOURCES, mPackageResources); saveProjectBooleanProperty(PROPERTY_BUILD_APK, mBuildFinalPackage); + // At this point, we can abort the build if we have to, as we have computed + // our resource delta and stored the result. + + // check if we have finished loading the SDK. + if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + // we exit silently + return referencedProjects; + } + // Now check the compiler compliance level, not displaying the error // message since this is not the first builder. if (ProjectHelper.checkCompilerCompliance(getProject()) @@ -502,7 +515,8 @@ public class ApkBuilder extends BaseBuilder { commandArray.add(osAssetsPath); } commandArray.add("-I"); //$NON-NLS-1$ - commandArray.add(AdtPlugin.getOsAbsoluteFramework()); + commandArray.add( + Sdk.getCurrent().getTarget(project).getPath(IAndroidTarget.ANDROID_JAR)); commandArray.add("-F"); //$NON-NLS-1$ commandArray.add(osOutFilePath); @@ -584,16 +598,9 @@ public class ApkBuilder extends BaseBuilder { DexWrapper wrapper = DexWrapper.getWrapper(); if (wrapper == null) { - if (DexWrapper.getStatus() == DexWrapper.LoadStatus.FAILED) { + if (DexWrapper.getStatus() == LoadStatus.FAILED) { throw new CoreException(new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, Messages.ApkBuilder_UnableBuild_Dex_Not_loaded)); - } else { - // means we haven't loaded the dex jar yet. - // We set the project to be recompiled after dex is loaded. - AdtPlugin.getDefault().addPostDexProject(javaProject); - - // and we exit silently - return false; } } @@ -677,7 +684,7 @@ public class ApkBuilder extends BaseBuilder { } // TODO: get the store type from somewhere else. - DebugKeyProvider provider = new DebugKeyProvider(null /* storeType */, + DebugKeyProvider provider = new DebugKeyProvider(osKeyPath, null /* storeType */, new IKeyGenOutput() { public void err(String message) { AdtPlugin.printErrorToConsole(javaProject.getProject(), @@ -747,9 +754,18 @@ public class ApkBuilder extends BaseBuilder { } } + // now write the native libraries. + // First look if the lib folder is there. + IResource libFolder = javaProject.getProject().findMember( + AndroidConstants.FD_NATIVE_LIBS); + if (libFolder != null && libFolder.exists() && + libFolder.getType() == IResource.FOLDER) { + // look inside and put .so in lib/* by keeping the relative folder path. + writeNativeLibraries(libFolder.getFullPath().segmentCount(), builder, libFolder); + } + // close the jar file and write the manifest and sign it. builder.close(); - } catch (GeneralSecurityException e1) { // mark project and return String msg = String.format(Messages.Final_Archive_Error_s, e1.getMessage()); @@ -784,6 +800,12 @@ public class ApkBuilder extends BaseBuilder { // and also output it in the console AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); + } catch (CoreException e) { + // mark project and return + String msg = String.format(Messages.Final_Archive_Error_s, e.getMessage()); + AdtPlugin.printErrorToConsole(javaProject.getProject(), msg); + markProject(AdtConstants.MARKER_ADT, msg, IMarker.SEVERITY_ERROR); + return false; } finally { if (fos != null) { try { @@ -798,6 +820,48 @@ public class ApkBuilder extends BaseBuilder { } /** + * Writes native libraries into a {@link SignedJarBuilder}. + *

This recursively go through folder and writes .so files. + * The path in the archive is based on the root folder containing the libraries in the project. + * Its segment count is passed to the method to compute the resources path relative to the root + * folder. + * Native libraries in the archive must be in a "lib" folder. Everything in the project native + * lib folder directly goes in this "lib" folder in the archive. + * + * + * @param rooSegmentCount The number of segment of the path of the folder containing the + * libraries. This is used to compute the path in the archive. + * @param jarBuilder the {@link SignedJarBuilder} used to create the archive. + * @param resource the IResource to write. + * @throws CoreException + * @throws IOException + */ + private void writeNativeLibraries(int rootSegmentCount, SignedJarBuilder jarBuilder, + IResource resource) throws CoreException, IOException { + if (resource.getType() == IResource.FILE) { + IPath path = resource.getFullPath(); + + // check the extension. + if (path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) { + // remove the first segment to build the path inside the archive. + path = path.removeFirstSegments(rootSegmentCount); + + // add it to the archive. + IPath apkPath = new Path(AndroidConstants.FD_APK_NATIVE_LIBS); + apkPath = apkPath.append(path); + + // writes the file in the apk. + jarBuilder.writeFile(resource.getLocation().toFile(), apkPath.toString()); + } + } else if (resource.getType() == IResource.FOLDER) { + IResource[] members = ((IFolder)resource).members(); + for (IResource member : members) { + writeNativeLibraries(rootSegmentCount, jarBuilder, member); + } + } + } + + /** * Writes the standard resources of a project and its referenced projects * into a {@link SignedJarBuilder}. * Standard resources are non java/aidl files placed in the java package folders. diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java index fb60fdb..47ef626 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkDeltaVisitor.java @@ -42,6 +42,7 @@ import java.util.ArrayList; *

  • Any change to the classes.dex inside the output folder
  • *
  • Any change to the packaged resources file inside the output folder
  • *
  • Any change to a non java/aidl file inside the source folders
  • + *
  • Any change to .so file inside the lib (native library) folder
  • * */ public class ApkDeltaVisitor extends BaseDeltaVisitor @@ -79,6 +80,8 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor private IPath mResPath; + private IPath mLibFolder; + /** * Builds the object with a specified output folder. * @param builder the xml builder using this object to visit the @@ -104,6 +107,11 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor if (resFolder != null) { mResPath = resFolder.getFullPath(); } + + IResource libFolder = builder.getProject().findMember(AndroidConstants.FD_NATIVE_LIBS); + if (libFolder != null) { + mLibFolder = libFolder.getFullPath(); + } } public boolean getConvertToDex() { @@ -161,6 +169,7 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor // check the other folders. if (mOutputPath != null && mOutputPath.isPrefixOf(path)) { + // a resource changed inside the output folder. if (type == IResource.FILE) { // just check this is a .class file. Any modification will // trigger a change in the classes.dex file @@ -217,6 +226,17 @@ public class ApkDeltaVisitor extends BaseDeltaVisitor mPackageResources = true; mMakeFinalPackage = true; return false; + } else if (mLibFolder != null && mLibFolder.isPrefixOf(path)) { + // inside the native library folder. Test if the changed resource is a .so file. + if (type == IResource.FILE && + path.getFileExtension().equalsIgnoreCase(AndroidConstants.EXT_NATIVE_LIB)) { + mMakeFinalPackage = true; + return false; // return false for file. + } + + // for folders, return true only if we don't already know we have to make the + // final package. + return mMakeFinalPackage == false; } else { // we are in a folder that is neither the resource folders, nor the output. // check against all the source folders, unless we already know we need to do diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java index f79be3d..f94bdc7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/BaseBuilder.java @@ -806,12 +806,6 @@ abstract class BaseBuilder extends IncrementalProjectBuilder { // get the IPath IPath path = e.getPath(); - // get the file name. if it's the framework jar, we ignore that file. - // since we now use classpath container, this is here for legacy purpose only. - if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) { - continue; - } - // check the name ends with .jar if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { boolean local = false; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java index 9ba4026..cba8ad7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/DexWrapper.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.LoadStatus; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; @@ -47,9 +48,6 @@ public final class DexWrapper { private static DexWrapper sWrapper; - /** Status for the Loading of the dex jar file */ - public enum LoadStatus { LOADING, LOADED, FAILED } - private static LoadStatus sLoadStatus = LoadStatus.LOADING; private Method mRunMethod; diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java index 850a79a..1a4aa8c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerBuilder.java @@ -18,14 +18,16 @@ package com.android.ide.eclipse.adt.build; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor; import com.android.ide.eclipse.adt.project.FixLaunchConfig; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; import com.android.ide.eclipse.common.AndroidConstants; import com.android.ide.eclipse.common.project.AndroidManifestHelper; import com.android.ide.eclipse.common.project.AndroidManifestParser; import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.common.project.XmlErrorHandler.BasicXmlErrorListener; +import com.android.sdklib.IAndroidTarget; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; @@ -44,7 +46,6 @@ import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; -import org.eclipse.ui.ide.IDE; import java.io.IOException; import java.util.ArrayList; @@ -130,7 +131,6 @@ public class PreCompilerBuilder extends BaseBuilder { mSource); try { mNewFile.setDerived(true); - IDE.setDefaultEditor(mNewFile, ReadOnlyJavaEditor.ID); } catch (CoreException e) { // This really shouldn't happen since we check that the resource exist. // Worst case scenario, the resource isn't marked as derived. @@ -266,8 +266,14 @@ public class PreCompilerBuilder extends BaseBuilder { // record the state mCompileResources |= dv.getCompileResources(); - mergeAidlFileModifications(dv.getAidlToCompile(), - dv.getAidlToRemove()); + + // handle aidl modification + if (dv.getFullAidlRecompilation()) { + buildAidlCompilationList(project, sourceList); + } else { + mergeAidlFileModifications(dv.getAidlToCompile(), + dv.getAidlToRemove()); + } // if there was some XML errors, we just return w/o doing // anything since we've put some markers in the files anyway. @@ -290,6 +296,12 @@ public class PreCompilerBuilder extends BaseBuilder { // At this point we have stored what needs to be build, so we can // do some high level test and abort if needed. + // check if we have finished loading the SDK. + if (AdtPlugin.getDefault().getSdkLoadStatus(javaProject) != LoadStatus.LOADED) { + // we exit silently + return null; + } + // check the compiler compliance level, not displaying the error message // since this is not the first builder. if (ProjectHelper.checkCompilerCompliance(getProject()) @@ -302,13 +314,19 @@ public class PreCompilerBuilder extends BaseBuilder { // Check that the SDK directory has been setup. String osSdkFolder = AdtPlugin.getOsSdkFolder(); - if (osSdkFolder.length() == 0) { + if (osSdkFolder == null || osSdkFolder.length() == 0) { AdtPlugin.printBuildToConsole(AdtConstants.BUILD_VERBOSE, project, Messages.No_SDK_Setup_Error); markProject(AdtConstants.MARKER_ADT, Messages.No_SDK_Setup_Error, IMarker.SEVERITY_ERROR); return null; } + + IAndroidTarget projectTarget = Sdk.getCurrent().getTarget(project); + if (projectTarget == null) { + // no target. error has been output by the container initializer: exit silently. + return null; + } // get the manifest file IFile manifest = AndroidManifestHelper.getManifest(project); @@ -448,7 +466,7 @@ public class PreCompilerBuilder extends BaseBuilder { array.add("-S"); //$NON-NLS-1$ array.add(osResPath); array.add("-I"); //$NON-NLS-1$ - array.add(AdtPlugin.getOsAbsoluteFramework()); + array.add(projectTarget.getPath(IAndroidTarget.ANDROID_JAR)); if (AdtPlugin.getBuildVerbosity() == AdtConstants.BUILD_VERBOSE) { StringBuilder sb = new StringBuilder(); @@ -563,6 +581,8 @@ public class PreCompilerBuilder extends BaseBuilder { deleteObsoleteGeneratedClass(AndroidConstants.FN_MANIFEST_CLASS, mManifestPackageSourceFolder, mManifestPackage); } + + // FIXME: delete all java generated from aidl. } @Override @@ -736,12 +756,14 @@ public class PreCompilerBuilder extends BaseBuilder { if (mAidlToCompile.size() == 0 && mAidlToRemove.size() == 0) { return false; } + // create the command line String[] command = new String[4 + sourceFolders.size() + (folderAidlPath != null ? 1 : 0)]; int index = 0; + int aidlIndex; command[index++] = AdtPlugin.getOsAbsoluteAidl(); - command[index++] = "-p" + AdtPlugin.getOsAbsoluteFrameworkAidl(); //$NON-NLS-1$ + command[aidlIndex = index++] = "-p"; //$NON-NLS-1$ if (folderAidlPath != null) { command[index++] = "-p" + folderAidlPath; //$NON-NLS-1$ } @@ -813,6 +835,8 @@ public class PreCompilerBuilder extends BaseBuilder { prepareFileForExternalModification(javaFile); // finish to set the command line. + command[aidlIndex] = "-p" + Sdk.getCurrent().getTarget(aidlFile.getProject()).getPath( + IAndroidTarget.ANDROID_AIDL); //$NON-NLS-1$ command[index] = osPath; command[index + 1] = osJavaPath; @@ -922,7 +946,8 @@ public class PreCompilerBuilder extends BaseBuilder { } /** - * Goes through the buildpath and fills the list of aidl files to compile. + * Goes through the build paths and fills the list of aidl files to compile + * ({@link #mAidlToCompile}). * @param project The project. * @param buildPaths The list of build paths. */ @@ -977,7 +1002,7 @@ public class PreCompilerBuilder extends BaseBuilder { scanContainerForAidl((IFolder)r); break; default: - // this would mean it's a project or the workspaceroot + // this would mean it's a project or the workspace root // which is unlikely to happen. we do nothing break; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java index 9c3bc5c..33d5fa6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/PreCompilerDeltaVisitor.java @@ -42,6 +42,7 @@ import java.util.ArrayList; class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements IResourceDeltaVisitor { + // Result fields. /** * Compile flag. This is set to true if one of the changed/added/removed * file is a resource file. Upon visiting all the delta resources, if @@ -50,6 +51,22 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements */ private boolean mCompileResources = false; + /** List of .aidl files found that are modified or new. */ + private final ArrayList mAidlToCompile = new ArrayList(); + + /** List of .aidl files that have been removed. */ + private final ArrayList mAidlToRemove = new ArrayList(); + + /** Aidl forced recompilation flag. This is set to true if project.aidl is modified. */ + private boolean mFullAidlCompilation = false; + + /** Manifest check/parsing flag. */ + private boolean mCheckedManifestXml = false; + + /** Application Pacakge, gathered from the parsing of the manifest */ + private String mJavaPackage = null; + + // Internal usage fields. /** * In Resource folder flag. This allows us to know if we're in the * resource folder. @@ -65,14 +82,6 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements /** List of source folders. */ private ArrayList mSourceFolders; - /** List of .aidl files found that are modified or new. */ - private final ArrayList mAidlToCompile = new ArrayList(); - - /** List of .aidl files that have been removed. */ - private final ArrayList mAidlToRemove = new ArrayList(); - - private boolean mCheckedManifestXml = false; - private String mJavaPackage = null; public PreCompilerDeltaVisitor(BaseBuilder builder, ArrayList sourceFolders) { super(builder); @@ -90,6 +99,10 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements public ArrayList getAidlToRemove() { return mAidlToRemove; } + + public boolean getFullAidlRecompilation() { + return mFullAidlCompilation; + } /** * Returns whether the manifest file was parsed/checked for error during the resource delta @@ -100,7 +113,7 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements } /** - * Returns the manifest package if the manifest was checked. + * Returns the manifest package if the manifest was checked/parsed. *

    * This can return null in two cases: *

      @@ -169,11 +182,14 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements // we don't want to go to the children, not like they are // any for this resource anyway. return false; + } else if (AndroidConstants.FN_PROJECT_AIDL.equalsIgnoreCase(segments[1])) { + // need to force recompilation of all the aidl files + mFullAidlCompilation = true; } } // at this point we can either be in the source folder or in the - // resource foler or in a different folder that contains a source + // resource folder or in a different folder that contains a source // folder. // This is due to not all source folder being src/. Some could be // something/somethingelse/src/ @@ -227,7 +243,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements PreCompilerBuilder.PROPERTY_ANDROID_CONFLICT, true); } - // we add it anyway so that we can try to compile it every time. + // we add it anyway so that we can try to compile it at every compilation + // until the conflict is fixed. mAidlToCompile.add(file); } else { @@ -245,6 +262,8 @@ class PreCompilerDeltaVisitor extends BaseDeltaVisitor implements boolean outputMessage = false; + // Special case of R.java/Manifest.java. + // FIXME: This does not check the package. Any modification of R.java/Manifest.java in another project will trigger a new recompilation of the resources. if (AndroidConstants.FN_RESOURCE_CLASS.equals(fileName) || AndroidConstants.FN_MANIFEST_CLASS.equals(fileName)) { // if it was removed, there's a possibility that it was removed due to a diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java index 1de1438..1219aac 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ResourceManagerBuilder.java @@ -62,13 +62,10 @@ public class ResourceManagerBuilder extends IncrementalProjectBuilder { switch (res) { case ProjectHelper.COMPILER_COMPLIANCE_LEVEL: errorMessage = Messages.Requires_Compiler_Compliance_5; - return null; case ProjectHelper.COMPILER_COMPLIANCE_SOURCE: errorMessage = Messages.Requires_Source_Compatibility_5; - return null; case ProjectHelper.COMPILER_COMPLIANCE_CODEGEN_TARGET: errorMessage = Messages.Requires_Class_Compatibility_5; - return null; } if (errorMessage != null) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java index 10be077..48ec7c3 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/AndroidLaunchController.java @@ -30,9 +30,9 @@ import com.android.ddmlib.SyncService.SyncResult; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.debug.launching.DeviceChooserDialog.DeviceChooserResponse; import com.android.ide.eclipse.adt.debug.ui.EmulatorConfigTab; -import com.android.ide.eclipse.adt.debug.ui.SkinRepository; import com.android.ide.eclipse.adt.project.ProjectHelper; import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -102,6 +102,9 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener /** Debuggable attribute of the manifest file. */ Boolean mDebuggable = null; + /** Required ApiVersionNumber by the app. 0 means no requirements */ + int mRequiredApiVersionNumber = 0; + InstallRetryMode mRetryMode = InstallRetryMode.NEVER; /** @@ -126,8 +129,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener /** Basic constructor with activity and package info. */ public DelayedLaunchInfo(IProject project, String packageName, String activity, - IFile pack, Boolean debuggable, int launchAction, AndroidLaunch launch, - IProgressMonitor monitor) { + IFile pack, Boolean debuggable, int requiredApiVersionNumber, int launchAction, + AndroidLaunch launch, IProgressMonitor monitor) { mProject = project; mPackageName = packageName; mActivity = activity; @@ -136,6 +139,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener mLaunch = launch; mMonitor = monitor; mDebuggable = debuggable; + mRequiredApiVersionNumber = requiredApiVersionNumber; } } @@ -256,13 +260,10 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener try { mSkin = config.getAttribute(LaunchConfigDelegate.ATTR_SKIN, mSkin); if (mSkin == null) { - mSkin = SkinRepository.getInstance().checkSkin( - LaunchConfigDelegate.DEFAULT_SKIN); - } else { - mSkin = SkinRepository.getInstance().checkSkin(mSkin); + mSkin = SdkConstants.SKIN_DEFAULT; } } catch (CoreException e) { - mSkin = SkinRepository.getInstance().checkSkin(LaunchConfigDelegate.DEFAULT_SKIN); + mSkin = SdkConstants.SKIN_DEFAULT; } int index = LaunchConfigDelegate.DEFAULT_SPEED; @@ -539,7 +540,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener LaunchConfigDelegate.DEFAULT_DELAY); // default skin - wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, LaunchConfigDelegate.DEFAULT_SKIN); + wc.setAttribute(LaunchConfigDelegate.ATTR_SKIN, SdkConstants.SKIN_DEFAULT); // default wipe data mode wc.setAttribute(LaunchConfigDelegate.ATTR_WIPE_DATA, @@ -599,13 +600,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * defined by ILaunchManager - RUN_MODE or * DEBUG_MODE. * @param apk the resource to the apk to launch. - * @param debuggable + * @param debuggable the debuggable value of the app, or null if not set. + * @param requiredApiVersionNumber the api version required by the app, or -1 if none. * @param activity the class to provide to am to launch * @param config the launch configuration * @param launch the launch object */ public void launch(final IProject project, String mode, IFile apk, - String packageName, Boolean debuggable, String activity, + String packageName, Boolean debuggable, int requiredApiVersionNumber, String activity, final AndroidLaunchConfiguration config, final AndroidLaunch launch, IProgressMonitor monitor) { @@ -619,7 +621,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // create the launch info final DelayedLaunchInfo launchInfo = new DelayedLaunchInfo(project, packageName, - activity, apk, debuggable, config.mLaunchAction, launch, monitor); + activity, apk, debuggable, requiredApiVersionNumber, config.mLaunchAction, + launch, monitor); // set the debug mode launchInfo.mDebugMode = mode.equals(ILaunchManager.DEBUG_MODE); @@ -711,6 +714,7 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // stop the launch and return mWaitingForEmulatorLaunches.remove(launchInfo); + AdtPlugin.printErrorToConsole(project, "Launch canceled!"); launch.stopLaunch(); return; } @@ -761,31 +765,63 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } - private void checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) { + /** + * Checks the build information, and returns whether the launch should continue. + *

      The value tested are: + *

        + *
      • Minimum API version requested by the application. If the target device does not match, + * the launch is canceled.
      • + *
      • Debuggable attribute of the application and whether or not the device requires it. If + * the device requires it and it is not set in the manifest, the launch will be forced to + * "release" mode instead of "debug"
      • + *
          + * @param launchInfo + * @param device + * @return + */ + private boolean checkBuildInfo(DelayedLaunchInfo launchInfo, Device device) { if (device != null) { - // get the SDK build - String sdkBuild = AdtPlugin.getSdkApiVersion(); - - // can only complain if the sdkBuild is known - if (sdkBuild != null) { - - String deviceVersion = device.getProperty(Device.PROP_BUILD_VERSION); + // check the app required API level versus the target device API level + + String deviceApiVersionName = device.getProperty(Device.PROP_BUILD_VERSION); + String value = device.getProperty(Device.PROP_BUILD_VERSION_NUMBER); + int deviceApiVersionNumber = 0; + try { + deviceApiVersionNumber = Integer.parseInt(value); + } catch (NumberFormatException e) { + // pass, we'll keep the deviceVersionNumber value at 0. + } + + if (launchInfo.mRequiredApiVersionNumber == 0) { + // warn the API level requirement is not set. + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "WARNING: Application does not specify an API level requirement!"); - if (deviceVersion == null) { - AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown device API version!"); + // and display the target device API level (if known) + if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "WARNING: Unknown device API version!"); } else { - if (sdkBuild.equals(deviceVersion) == false) { - // TODO do a proper check, including testing the content of the uses-sdk string in the manifest to detect real incompatibility. - String msg = String.format( - "WARNING: Device API version (%1$s) does not match SDK API version (%2$s)", - deviceVersion, sdkBuild); - AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); - } + AdtPlugin.printErrorToConsole(launchInfo.mProject, String.format( + "Device API version is %1$d (Android %2$s)", deviceApiVersionNumber, + deviceApiVersionName)); + } + } else { // app requires a specific API level + if (deviceApiVersionName == null || deviceApiVersionNumber == 0) { + AdtPlugin.printToConsole(launchInfo.mProject, + "WARNING: Unknown device API version!"); + } else if (deviceApiVersionNumber < launchInfo.mRequiredApiVersionNumber) { + String msg = String.format( + "ERROR: Application requires API version %1$d. Device API version is %2$d (Android %3$s).", + launchInfo.mRequiredApiVersionNumber, deviceApiVersionNumber, + deviceApiVersionName); + AdtPlugin.printErrorToConsole(launchInfo.mProject, msg); + + // abort the launch + return false; } - } else { - AdtPlugin.printToConsole(launchInfo.mProject, "WARNING: Unknown SDK API version!"); } - + // now checks that the device/app can be debugged (if needed) if (device.isEmulator() == false && launchInfo.mDebugMode) { String debuggableDevice = device.getProperty(Device.PROP_DEBUGGABLE); @@ -818,6 +854,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } } + + return true; } /** @@ -829,10 +867,16 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener * @return true if succeed */ private boolean simpleLaunch(DelayedLaunchInfo launchInfo, Device device) { - checkBuildInfo(launchInfo, device); + // API level check + if (checkBuildInfo(launchInfo, device) == false) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + return false; + } // sync the app if (syncApp(launchInfo, device) == false) { + AdtPlugin.printErrorToConsole(launchInfo.mProject, "Launch canceled!"); launchInfo.mLaunch.stopLaunch(); return false; } @@ -1127,6 +1171,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } catch (IOException e) { // something went wrong trying to launch the app. // lets stop the Launch + AdtPlugin.printErrorToConsole(info.mProject, + String.format("Launch error: %s", e.getMessage())); info.mLaunch.stopLaunch(); // and remove it from the list of app waiting for debuggers @@ -1139,25 +1185,20 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener private boolean launchEmulator(AndroidLaunchConfiguration config) { // split the custom command line in segments - String[] segs; + ArrayList customArgs = new ArrayList(); boolean has_wipe_data = false; if (config.mEmulatorCommandLine != null && config.mEmulatorCommandLine.length() > 0) { - segs = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$ + String[] segments = config.mEmulatorCommandLine.split("\\s+"); //$NON-NLS-1$ // we need to remove the empty strings - ArrayList array = new ArrayList(); - for (String s : segs) { + for (String s : segments) { if (s.length() > 0) { - array.add(s); + customArgs.add(s); if (!has_wipe_data && s.equals(FLAG_WIPE_DATA)) { has_wipe_data = true; } } } - - segs = array.toArray(new String[array.size()]); - } else { - segs = new String[0]; } boolean needs_wipe_data = config.mWipeData && !has_wipe_data; @@ -1167,30 +1208,38 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } - boolean needs_no_boot_anim = config.mNoBootAnim; + // build the command line based on the available parameters. + ArrayList list = new ArrayList(); + + list.add(AdtPlugin.getOsAbsoluteEmulator()); + if (config.mSkin != null) { + list.add(FLAG_SKIN); + list.add(config.mSkin); + } - // get the command line - String[] command = new String[7 + segs.length + - (needs_wipe_data ? 1 : 0) + - (needs_no_boot_anim ? 1 : 0)]; - int index = 0; - command[index++] = AdtPlugin.getOsAbsoluteEmulator(); - command[index++] = FLAG_SKIN; //$NON-NLS-1$ - command[index++] = config.mSkin; - command[index++] = FLAG_NETSPEED; //$NON-NLS-1$ - command[index++] = config.mNetworkSpeed; - command[index++] = FLAG_NETDELAY; //$NON-NLS-1$ - command[index++] = config.mNetworkDelay; - if (needs_wipe_data) { - command[index++] = FLAG_WIPE_DATA; + if (config.mNetworkSpeed != null) { + list.add(FLAG_NETSPEED); + list.add(config.mNetworkSpeed); } - if (needs_no_boot_anim) { - command[index++] = FLAG_NO_BOOT_ANIM; + + if (config.mNetworkDelay != null) { + list.add(FLAG_NETDELAY); + list.add(config.mNetworkDelay); + } + + if (needs_wipe_data) { + list.add(FLAG_WIPE_DATA); } - for (String s : segs) { - command[index++] = s; + + if (config.mNoBootAnim) { + list.add(FLAG_NO_BOOT_ANIM); } + list.addAll(customArgs); + + // convert the list into an array for the call to exec. + String[] command = list.toArray(new String[list.size()]); + // launch the emulator try { Process process = Runtime.getRuntime().exec(command); @@ -1278,12 +1327,11 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener /** * Launch a new thread that connects a remote debugger on the specified port. * @param debugPort The port to connect the debugger to - * @param launch The associated AndroidLaunch object. + * @param androidLaunch The associated AndroidLaunch object. * @param monitor A Progress monitor * @see connectRemoveDebugger() */ - public static void launchRemoteDebugger(final ILaunchConfiguration config, - final int debugPort, final AndroidLaunch androidLaunch, + public static void launchRemoteDebugger( final int debugPort, final AndroidLaunch androidLaunch, final IProgressMonitor monitor) { new Thread("Debugger connection") { //$NON-NLS-1$ @Override @@ -1342,13 +1390,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener synchronized (sListLock) { // look if there's an app waiting for a device if (mWaitingForEmulatorLaunches.size() > 0) { - // remove first item from the list + // get/remove first launch item from the list + // FIXME: what if we have multiple launches waiting? DelayedLaunchInfo launchInfo = mWaitingForEmulatorLaunches.get(0); mWaitingForEmulatorLaunches.remove(0); - - // give it its device + + // give the launch item its device for later use. launchInfo.mDevice = device; - + // and move it to the other list mWaitingForReadyEmulatorList.add(launchInfo); @@ -1433,12 +1482,23 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener // look for application waiting for home synchronized (sListLock) { - boolean foundMatch = false; for (int i = 0 ; i < mWaitingForReadyEmulatorList.size() ;) { DelayedLaunchInfo launchInfo = mWaitingForReadyEmulatorList.get(i); if (launchInfo.mDevice == device) { // it's match, remove from the list mWaitingForReadyEmulatorList.remove(i); + + // We couldn't check earlier the API level of the device + // (it's asynchronous when the device boot, and usually + // deviceConnected is called before it's queried for its build info) + // so we check now + if (checkBuildInfo(launchInfo, device) == false) { + // device is not the proper API! + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Launch canceled!"); + launchInfo.mLaunch.stopLaunch(); + return; + } AdtPlugin.printToConsole(launchInfo.mProject, String.format("HOME is up on device '%1$s'", @@ -1448,16 +1508,14 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener if (syncApp(launchInfo, device)) { // application package is sync'ed, lets attempt to launch it. launchApp(launchInfo, device); - - // if we haven't checked the device build info, lets do it here - if (foundMatch == false) { - foundMatch = true; - checkBuildInfo(launchInfo, device); - } } else { // failure! Cancel and return + AdtPlugin.printErrorToConsole(launchInfo.mProject, + "Launch canceled!"); launchInfo.mLaunch.stopLaunch(); } + + break; } else { i++; } @@ -1530,6 +1588,8 @@ public final class AndroidLaunchController implements IDebugBridgeChangeListener } } catch (CoreException e) { // well something went wrong. + AdtPlugin.printErrorToConsole(launchInfo.mProject, + String.format("Launch error: %s", e.getMessage())); // stop the launch launchInfo.mLaunch.stopLaunch(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java index e5ccb2b..5d3e349 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/launching/LaunchConfigDelegate.java @@ -83,11 +83,6 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { /** Skin to be used to launch the emulator */ public static final String ATTR_SKIN = AdtPlugin.PLUGIN_ID + ".skin"; //$NON-NLS-1$ - /** - * Name of the default Skin. - */ - public static final String DEFAULT_SKIN = "HVGA"; //$NON-NLS-1$ - public static final String ATTR_SPEED = AdtPlugin.PLUGIN_ID + ".speed"; //$NON-NLS-1$ /** @@ -138,8 +133,7 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { // if we have a valid debug port, this means we're debugging an app // that's already launched. if (debugPort != INVALID_DEBUG_PORT) { - AndroidLaunchController.launchRemoteDebugger(configuration, - debugPort, androidLaunch, monitor); + AndroidLaunchController.launchRemoteDebugger(debugPort, androidLaunch, monitor); return; } @@ -302,7 +296,8 @@ public class LaunchConfigDelegate extends LaunchConfigurationDelegate { // everything seems fine, we ask the launch controller to handle // the rest controller.launch(project, mode, applicationPackage, manifestParser.getPackage(), - manifestParser.getDebuggable(), activityName, config, androidLaunch, monitor); + manifestParser.getDebuggable(), manifestParser.getApiLevelRequirement(), + activityName, config, androidLaunch, monitor); } @Override diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java index 1d36add..c7b340c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/EmulatorConfigTab.java @@ -18,13 +18,19 @@ package com.android.ide.eclipse.adt.debug.ui; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.debug.launching.LaunchConfigDelegate; -import com.android.ide.eclipse.adt.debug.ui.SkinRepository.Skin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.project.BaseProjectHelper; import com.android.ide.eclipse.ddms.DdmsPlugin; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.SdkConstants; +import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; @@ -82,6 +88,8 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { private Button mNoBootAnimButton; + private IAndroidTarget mTarget; + /** * Returns the emulator ready speed option value. * @param value The index of the combo selection. @@ -169,12 +177,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Screen Size:"); mSkinCombo = new Combo(mEmulatorOptionsGroup, SWT.READ_ONLY); - Skin[] skins = SkinRepository.getInstance().getSkins(); - if (skins != null) { - for (Skin skin : skins) { - mSkinCombo.add(skin.getDescription()); - } - } mSkinCombo.addSelectionListener(new SelectionAdapter() { // called when selection changes @Override @@ -182,7 +184,6 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { updateLaunchConfigurationDialog(); } }); - mSkinCombo.pack(); // network options new Label(mEmulatorOptionsGroup, SWT.NONE).setText("Network Speed:"); @@ -287,6 +288,42 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { } mAutoTargetButton.setSelection(value); mManualTargetButton.setSelection(!value); + + // look for the project name to get its target. + String projectName = ""; + try { + projectName = configuration.getAttribute( + IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, projectName); + } catch (CoreException ce) { + } + + IProject project = null; + + // get the list of existing Android projects from the workspace. + IJavaProject[] projects = BaseProjectHelper.getAndroidProjects(); + if (projects != null) { + // look for the project whose name we read from the configuration. + for (IJavaProject p : projects) { + if (p.getElementName().equals(projectName)) { + project = p.getProject(); + break; + } + } + } + + mSkinCombo.removeAll(); + if (project != null) { + mTarget = Sdk.getCurrent().getTarget(project); + if (mTarget != null) { + String[] skins = mTarget.getSkins(); + if (skins != null) { + for (String skin : skins) { + mSkinCombo.add(skin); + } + mSkinCombo.pack(); + } + } + } value = LaunchConfigDelegate.DEFAULT_WIPE_DATA; try { @@ -307,16 +344,18 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { int index = -1; try { String skin = configuration.getAttribute(LaunchConfigDelegate.ATTR_SKIN, (String)null); - if (skin != null) { - index = getSkinIndex(skin); + if (skin == null) { + skin = SdkConstants.SKIN_DEFAULT; } + + index = getSkinIndex(skin); } catch (CoreException e) { - index = getSkinIndex(SkinRepository.getInstance().checkSkin( - LaunchConfigDelegate.DEFAULT_SKIN)); + index = getSkinIndex(SdkConstants.SKIN_DEFAULT); } if (index == -1) { - mSkinCombo.clearSelection(); + mSkinCombo.select(0); + updateLaunchConfigurationDialog(); } else { mSkinCombo.select(index); } @@ -387,7 +426,7 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { configuration.setAttribute(LaunchConfigDelegate.ATTR_TARGET_MODE, LaunchConfigDelegate.DEFAULT_TARGET_MODE); configuration.setAttribute(LaunchConfigDelegate.ATTR_SKIN, - LaunchConfigDelegate.DEFAULT_SKIN); + SdkConstants.SKIN_DEFAULT); configuration.setAttribute(LaunchConfigDelegate.ATTR_SPEED, LaunchConfigDelegate.DEFAULT_SPEED); configuration.setAttribute(LaunchConfigDelegate.ATTR_DELAY, @@ -403,11 +442,31 @@ public class EmulatorConfigTab extends AbstractLaunchConfigurationTab { } private String getSkinNameByIndex(int index) { - return SkinRepository.getInstance().getSkinNameByIndex(index); + if (mTarget != null && index > 0) { + String[] skins = mTarget.getSkins(); + if (skins != null && index < skins.length) { + return skins[index]; + } + } + + return null; } private int getSkinIndex(String name) { - return SkinRepository.getInstance().getSkinIndex(name); + if (mTarget != null) { + String[] skins = mTarget.getSkins(); + if (skins != null) { + int index = 0; + for (String skin : skins) { + if (skin.equalsIgnoreCase(name)) { + return index; + } + index++; + } + } + } + + return -1; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/SkinRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/SkinRepository.java deleted file mode 100644 index a813026..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/debug/ui/SkinRepository.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2007 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.debug.ui; - -import com.android.ide.eclipse.common.AndroidConstants; - -import java.io.File; -import java.util.ArrayList; - -/** - * Repository for the emulator skins. This class is responsible for parsing the skin folder. - */ -public class SkinRepository { - - private final static SkinRepository sInstance = new SkinRepository(); - - private Skin[] mSkins; - - public static class Skin { - - String mName; - - public Skin(String name) { - mName = name; - } - - public String getName() { - return mName; - } - - /** - * Returns the human readable description of the skin. - */ - public String getDescription() { - // TODO: parse the skin and output the description. - return mName; - } - } - - /** - * Returns the singleton instance. - */ - public static SkinRepository getInstance() { - return sInstance; - } - - /** - * Parse the skin folder and build the skin list. - * @param osPath The path of the skin folder. - */ - public void parseFolder(String osPath) { - File skinFolder = new File(osPath); - - if (skinFolder.isDirectory()) { - ArrayList skinList = new ArrayList(); - - File[] files = skinFolder.listFiles(); - - for (File skin : files) { - if (skin.isDirectory()) { - // check for layout file - File layout = new File(skin.getPath() + File.separator - + AndroidConstants.FN_LAYOUT); - - if (layout.isFile()) { - // for now we don't parse the content of the layout and - // simply add the directory to the list. - skinList.add(new Skin(skin.getName())); - } - } - } - - mSkins = skinList.toArray(new Skin[skinList.size()]); - } else { - mSkins = new Skin[0]; - } - } - - public Skin[] getSkins() { - return mSkins; - } - - /** - * Returns a valid skin folder name. If skin is valid, then it is returned, - * otherwise the first valid skin name is returned. - * @param skin the Skin name to check - * @return a valid skin name or null if there aren't any. - */ - public String checkSkin(String skin) { - if (mSkins != null) { - for (Skin s : mSkins) { - if (s.getName().equals(skin)) { - return skin; - } - } - - if (mSkins.length > 0) { - return mSkins[0].getName(); - } - } - - return null; - } - - - /** - * Returns the name of a skin by index. - * @param index The index of the skin to return - * @return the skin name of null if the index is invalid. - */ - public String getSkinNameByIndex(int index) { - if (mSkins != null) { - if (index >= 0 && index < mSkins.length) { - return mSkins[index].getName(); - } - } - return null; - } - - /** - * Returns the index (0 based) of the skin matching the name. - * @param name The name of the skin to look for. - * @return the index of the skin or -1 if the skin was not found. - */ - public int getSkinIndex(String name) { - if (mSkins != null) { - int count = mSkins.length; - for (int i = 0 ; i < count ; i++) { - Skin s = mSkins[i]; - if (s.mName.equals(name)) { - return i; - } - } - } - - return -1; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/java/ReadOnlyJavaEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/java/ReadOnlyJavaEditor.java deleted file mode 100644 index 1c9a569..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/java/ReadOnlyJavaEditor.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.java; - -import org.eclipse.jdt.ui.PreferenceConstants; -import org.eclipse.jdt.ui.text.IJavaPartitions; -import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration; -import org.eclipse.jdt.ui.text.JavaTextTools; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor; - -/** - * Read only java editors. This looks like the regular Java editor, except that it - * prevents editing the files. This is used for automatically generated java classes. - */ -public class ReadOnlyJavaEditor extends AbstractDecoratedTextEditor { - - public final static String ID = - "com.android.ide.eclipse.adt.editors.java.ReadOnlyJavaEditor"; //$NON-NLS-1$ - - public ReadOnlyJavaEditor() { - IPreferenceStore javaUiStore = PreferenceConstants.getPreferenceStore(); - JavaTextTools jtt = new JavaTextTools(javaUiStore); - - JavaSourceViewerConfiguration jsvc = new JavaSourceViewerConfiguration( - jtt.getColorManager(), javaUiStore, this, IJavaPartitions.JAVA_PARTITIONING); - - setSourceViewerConfiguration(jsvc); - } - - @Override - public boolean isEditable() { - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java index 136c0f3..434269c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/preferences/BuildPreferencePage.java @@ -18,6 +18,9 @@ package com.android.ide.eclipse.adt.preferences; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.jarutils.DebugKeyProvider; +import com.android.jarutils.DebugKeyProvider.KeytoolException; +import com.android.prefs.AndroidLocation.AndroidLocationException; import org.eclipse.jface.preference.BooleanFieldEditor; import org.eclipse.jface.preference.FieldEditorPreferencePage; @@ -29,6 +32,13 @@ import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPreferencePage; +import java.io.File; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Date; + /** * Preference page for build options. * @@ -75,7 +85,7 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements addField(new ReadOnlyFieldEditor(AdtPlugin.PREFS_DEFAULT_DEBUG_KEYSTORE, Messages.BuildPreferencePage_Default_KeyStore, getFieldEditorParent())); - addField(new FileFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE, + addField(new KeystoreFieldEditor(AdtPlugin.PREFS_CUSTOM_DEBUG_KEYSTORE, Messages.BuildPreferencePage_Custom_Keystore, getFieldEditorParent())); } @@ -105,4 +115,103 @@ public class BuildPreferencePage extends FieldEditorPreferencePage implements control.setEditable(false); } } + + /** + * Custom {@link FileFieldEditor} that checks that the keystore is valid. + */ + private static class KeystoreFieldEditor extends FileFieldEditor { + public KeystoreFieldEditor(String name, String label, Composite parent) { + super(name, label, parent); + setValidateStrategy(VALIDATE_ON_KEY_STROKE); + } + + @Override + protected boolean checkState() { + String fileName = getTextControl().getText(); + fileName = fileName.trim(); + + // empty values are considered ok. + if (fileName.length() > 0) { + File file = new File(fileName); + if (file.isFile()) { + // attempt to load the debug key. + try { + DebugKeyProvider provider = new DebugKeyProvider(fileName, + null /* storeType */, null /* key gen output */); + PrivateKey key = provider.getDebugKey(); + X509Certificate certificate = (X509Certificate)provider.getCertificate(); + + if (key == null || certificate == null) { + showErrorMessage("Unable to find debug key in keystore!"); + return false; + } + + Date today = new Date(); + if (certificate.getNotAfter().compareTo(today) < 0) { + showErrorMessage("Certificate is expired!"); + return false; + } + + if (certificate.getNotBefore().compareTo(today) > 0) { + showErrorMessage("Certificate validity is in the future!"); + return false; + } + + // we're good! + clearErrorMessage(); + return true; + } catch (GeneralSecurityException e) { + handleException(e); + return false; + } catch (IOException e) { + handleException(e); + return false; + } catch (KeytoolException e) { + handleException(e); + return false; + } catch (AndroidLocationException e) { + handleException(e); + return false; + } + + + } else { + // file does not exist. + showErrorMessage("Not a valid keystore path."); + return false; // Apply/OK must be disabled + } + } + + clearErrorMessage(); + return true; + } + + @Override + public Text getTextControl(Composite parent) { + setValidateStrategy(VALIDATE_ON_KEY_STROKE); + return super.getTextControl(parent); + } + + /** + * Set the error message from a {@link Throwable}. If the exception has no message, try + * to get the message from the cause. + * @param t the Throwable. + */ + private void handleException(Throwable t) { + String msg = t.getMessage(); + if (t == null) { + Throwable cause = t.getCause(); + if (cause != null) { + handleException(cause); + } else { + setErrorMessage("Uknown error when getting the debug key!"); + } + + return; + } + + // valid text, display it. + showErrorMessage(msg); + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java index efbbf94..a1b3c38 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/CreateAidlImportAction.java @@ -17,6 +17,7 @@ package com.android.ide.eclipse.adt.project; import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -117,7 +118,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate { // create the file with the parcelables if (parcelables.size() > 0) { IPath path = project.getLocation(); - path = path.append("/project.aidl"); //$NON-NLS-1$ + path = path.append(AndroidConstants.FN_PROJECT_AIDL); File f = new File(path.toOSString()); if (f.exists() == false) { @@ -194,7 +195,7 @@ public class CreateAidlImportAction implements IObjectActionDelegate { IType[] superInterfaces = typeHierarchy.getAllSuperInterfaces(type); for (IType superInterface : superInterfaces) { - if ("android.os.Parcelable".equals(superInterface.getFullyQualifiedName())) { //$NON-NLS-1$ + if (AndroidConstants.CLASS_PARCELABLE.equals(superInterface.getFullyQualifiedName())) { parcelables.add(type.getFullyQualifiedName()); } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java new file mode 100644 index 0000000..1ca89cd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/FolderDecorator.java @@ -0,0 +1,104 @@ +/* + * 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.project; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; + +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.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IDecoration; +import org.eclipse.jface.viewers.ILabelDecorator; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ILightweightLabelDecorator; + +/** + * A {@link ILabelDecorator} associated with an org.eclipse.ui.decorators extension. + * This is used to add android icons in some special folders in the package explorer. + */ +public class FolderDecorator implements ILightweightLabelDecorator { + + private ImageDescriptor mDescriptor; + + public FolderDecorator() { + mDescriptor = AdtPlugin.getImageDescriptor("/icons/android_project.png"); + } + + public void decorate(Object element, IDecoration decoration) { + if (element instanceof IFolder) { + IFolder folder = (IFolder)element; + + // get the project and make sure this is an android project + IProject project = folder.getProject(); + + try { + if (project.hasNature(AndroidConstants.NATURE)) { + // check the folder is directly under the project. + if (folder.getParent().getType() == IResource.PROJECT) { + String name = folder.getName(); + if (name.equals(AndroidConstants.FD_ASSETS)) { + decorate(decoration, " [Android assets]"); + decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); + } else if (name.equals(AndroidConstants.FD_RESOURCES)) { + decorate(decoration, " [Android resources]"); + decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); + } else if (name.equals(AndroidConstants.FD_NATIVE_LIBS)) { + decorate(decoration, " [Native Libraries]"); + } + } + } + } catch (CoreException e) { + // log the error + AdtPlugin.log(e, "Unable to get nature of project '%s'.", project.getName()); + } + } + } + + public void decorate(IDecoration decoration, String suffix) { + decoration.addOverlay(mDescriptor, IDecoration.TOP_RIGHT); + + // this is broken as it changes the color of the whole text, not only of the decoration. + // TODO: figure out how to change the color of the decoration only. +// decoration.addSuffix(suffix); +// ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); +// ColorRegistry registry = theme.getColorRegistry(); +// decoration.setForegroundColor(registry.get("org.eclipse.jdt.ui.ColoredLabels.decorations")); + + } + + public boolean isLabelProperty(Object element, String property) { + // at this time return false. + return false; + } + + public void addListener(ILabelProviderListener listener) { + // No state change will affect the rendering. + } + + + + public void removeListener(ILabelProviderListener listener) { + // No state change will affect the rendering. + } + + public void dispose() { + // nothind to dispose + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/NewXmlFileWizardAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/NewXmlFileWizardAction.java new file mode 100644 index 0000000..c117b4e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/NewXmlFileWizardAction.java @@ -0,0 +1,57 @@ +/* + * 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.project; + +import com.android.ide.eclipse.editors.wizards.NewXmlFileWizard; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPart; + +public class NewXmlFileWizardAction implements IObjectActionDelegate { + + private ISelection mSelection; + private IWorkbench mWorkbench; + + /** + * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) + */ + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + mWorkbench = targetPart.getSite().getWorkbenchWindow().getWorkbench(); + } + + public void run(IAction action) { + if (mSelection instanceof IStructuredSelection) { + IStructuredSelection selection = (IStructuredSelection)mSelection; + + // call the new xml file wizard on the current selection. + NewXmlFileWizard wizard = new NewXmlFileWizard(); + wizard.init(mWorkbench, selection); + WizardDialog dialog = new WizardDialog(mWorkbench.getDisplay().getActiveShell(), + wizard); + dialog.open(); + } + } + + public void selectionChanged(IAction action, ISelection selection) { + this.mSelection = selection; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java index 24132b6..8678923 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java @@ -33,7 +33,6 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.jdt.core.IClasspathAttribute; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; @@ -178,31 +177,6 @@ public final class ProjectHelper { } /** - * Check the validity of the javadoc attributes in a classpath entry. - * @param frameworkEntry the classpath entry to check. - * @return true if the javadoc attributes is valid, false otherwise. - */ - public static boolean checkJavaDocPath(IClasspathEntry frameworkEntry) { - // get the list of extra attributes - IClasspathAttribute[] attributes = frameworkEntry.getExtraAttributes(); - - // and search for the one about the javadoc - for (IClasspathAttribute att : attributes) { - if (IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME. - equals(att.getName())) { - // we found a javadoc attribute. Now we test its value. - // get the expect value - String validJavaDoc = getJavaDocPath(AdtPlugin.getOsAbsoluteFramework()); - - // now compare and return - return validJavaDoc.equals(att.getValue()); - } - } - - return false; - } - - /** * Fix the project. This checks the SDK location. * @param project The project to fix. * @throws JavaModelException @@ -264,14 +238,7 @@ public final class ProjectHelper { continue; } } else if (kind == IClasspathEntry.CPE_CONTAINER) { - IPath containerPath = entry.getPath(); - - if (AndroidClasspathContainerInitializer.checkOldPath(containerPath)) { - entries = ProjectHelper.removeEntryFromClasspath(entries, i); - - // continue, to skip the i++; - continue; - } else if (AndroidClasspathContainerInitializer.checkPath(containerPath)) { + if (AndroidClasspathContainerInitializer.checkPath(entry.getPath())) { foundContainer = true; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java index 1f51d09..de4b339 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java @@ -18,7 +18,10 @@ package com.android.ide.eclipse.adt.project.export; import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.jarutils.KeystoreHelper; import com.android.jarutils.SignedJarBuilder; +import com.android.jarutils.DebugKeyProvider.IKeyGenOutput; +import com.android.jarutils.DebugKeyProvider.KeytoolException; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; @@ -27,28 +30,38 @@ import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IExportWizard; import org.eclipse.ui.IWorkbench; +import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyStore; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.KeyStore.PrivateKeyEntry; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; /** * Export wizard to export an apk signed with a release key/certificate. */ -public class ExportWizard extends Wizard implements IExportWizard { +public final class ExportWizard extends Wizard implements IExportWizard { private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ - private static final String PAGE_PRE = "preExportPage"; //$NON-NLS-1$ - private static final String PAGE_SIGNING = "signingExportPage"; //$NON-NLS-1$ - private static final String PAGE_FINAL = "finalExportPage"; //$NON-NLS-1$ + private static final String PAGE_PROJECT_CHECK = "Page_ProjectCheck"; //$NON-NLS-1$ + private static final String PAGE_KEYSTORE_SELECTION = "Page_KeystoreSelection"; //$NON-NLS-1$ + private static final String PAGE_KEY_CREATION = "Page_KeyCreation"; //$NON-NLS-1$ + private static final String PAGE_KEY_SELECTION = "Page_KeySelection"; //$NON-NLS-1$ + private static final String PAGE_KEY_CHECK = "Page_KeyCheck"; //$NON-NLS-1$ static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$ static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$ @@ -59,7 +72,41 @@ public class ExportWizard extends Wizard implements IExportWizard { */ static abstract class ExportWizardPage extends WizardPage { - protected boolean mNewProjectReference = true; + /** bit mask constant for project data change event */ + protected static final int DATA_PROJECT = 0x001; + /** bit mask constant for keystore data change event */ + protected static final int DATA_KEYSTORE = 0x002; + /** bit mask constant for key data change event */ + protected static final int DATA_KEY = 0x004; + + protected static final VerifyListener sPasswordVerifier = new VerifyListener() { + public void verifyText(VerifyEvent e) { + // verify the characters are valid for password. + int len = e.text.length(); + + // first limit to 127 characters max + if (len + ((Text)e.getSource()).getText().length() > 127) { + e.doit = false; + return; + } + + // now only take non control characters + for (int i = 0 ; i < len ; i++) { + if (e.text.charAt(i) < 32) { + e.doit = false; + return; + } + } + } + }; + + /** + * Bit mask indicating what changed while the page was hidden. + * @see #DATA_PROJECT + * @see #DATA_KEYSTORE + * @see #DATA_KEY + */ + protected int mProjectDataChanged = 0; ExportWizardPage(String name) { super(name); @@ -72,23 +119,38 @@ public class ExportWizard extends Wizard implements IExportWizard { super.setVisible(visible); if (visible) { onShow(); - mNewProjectReference = false; + mProjectDataChanged = 0; } } - void newProjectReference() { - mNewProjectReference = true; + final void projectDataChanged(int changeMask) { + mProjectDataChanged |= changeMask; + } + + /** + * Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a + * {@link Throwable} object. + */ + protected final void onException(Throwable t) { + String message = getExceptionMessage(t); + + setErrorMessage(message); + setPageComplete(false); } } - private ExportWizardPage mPages[] = new ExportWizardPage[3]; + private ExportWizardPage mPages[] = new ExportWizardPage[5]; private IProject mProject; private String mKeystore; + private String mKeystorePassword; + private boolean mKeystoreCreationMode; + private String mKeyAlias; - private char[] mKeystorePassword; - private char[] mKeyPassword; + private String mKeyPassword; + private int mValidity; + private String mDName; private PrivateKey mPrivateKey; private X509Certificate mCertificate; @@ -97,6 +159,15 @@ public class ExportWizard extends Wizard implements IExportWizard { private String mApkFilePath; private String mApkFileName; + private ExportWizardPage mKeystoreSelectionPage; + private ExportWizardPage mKeyCreationPage; + private ExportWizardPage mKeySelectionPage; + private ExportWizardPage mKeyCheckPage; + + private boolean mKeyCreationMode; + + private List mExistingAliases; + public ExportWizard() { setHelpAvailable(false); // TODO have help setWindowTitle("Export Android Application"); @@ -105,46 +176,101 @@ public class ExportWizard extends Wizard implements IExportWizard { @Override public void addPages() { - addPage(mPages[0] = new PreExportPage(this, PAGE_PRE)); - addPage(mPages[1] = new SigningExportPage(this, PAGE_SIGNING)); - addPage(mPages[2] = new FinalExportPage(this, PAGE_FINAL)); + addPage(mPages[0] = new ProjectCheckPage(this, PAGE_PROJECT_CHECK)); + addPage(mKeystoreSelectionPage = mPages[1] = new KeystoreSelectionPage(this, + PAGE_KEYSTORE_SELECTION)); + addPage(mKeyCreationPage = mPages[2] = new KeyCreationPage(this, PAGE_KEY_CREATION)); + addPage(mKeySelectionPage = mPages[3] = new KeySelectionPage(this, PAGE_KEY_SELECTION)); + addPage(mKeyCheckPage = mPages[4] = new KeyCheckPage(this, PAGE_KEY_CHECK)); } @Override public boolean performFinish() { + // first we make sure export is fine if the destination file already exists + File f = new File(mDestinationPath); + if (f.isFile()) { + if (AdtPlugin.displayPrompt("Export Wizard", + "File already exists. Do you want to overwrite it?") == false) { + return false; + } + } + // save the properties ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore); ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias); ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath); try { - FileOutputStream fos = new FileOutputStream(mDestinationPath); - SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate); - - // get the input file. - FileInputStream fis = new FileInputStream(mApkFilePath); - try { - builder.writeZip(fis, null /* filter */); - } finally { + if (mKeystoreCreationMode || mKeyCreationMode) { + final ArrayList output = new ArrayList(); + if (KeystoreHelper.createNewStore( + mKeystore, + null /*storeType*/, + mKeystorePassword, + mKeyAlias, + mKeyPassword, + mDName, + mValidity, + new IKeyGenOutput() { + public void err(String message) { + output.add(message); + } + public void out(String message) { + output.add(message); + } + }) == false) { + // keystore creation error! + displayError(output.toArray(new String[output.size()])); + return false; + } + + // keystore is created, now load the private key and certificate. + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream fis = new FileInputStream(mKeystore); + keyStore.load(fis, mKeystorePassword.toCharArray()); fis.close(); + PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry( + mKeyAlias, new KeyStore.PasswordProtection(mKeyPassword.toCharArray())); + + if (entry != null) { + mPrivateKey = entry.getPrivateKey(); + mCertificate = (X509Certificate)entry.getCertificate(); + } else { + // this really shouldn't happen since we now let the user choose the key + // from a list read from the store. + displayError("Could not find key"); + return false; + } } - - builder.close(); - fos.close(); - return true; + // check the private key/certificate again since it may have been created just above. + if (mPrivateKey != null && mCertificate != null) { + FileOutputStream fos = new FileOutputStream(mDestinationPath); + SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate); + + // get the input file. + FileInputStream fis = new FileInputStream(mApkFilePath); + try { + builder.writeZip(fis, null /* filter */); + } finally { + fis.close(); + } + + builder.close(); + fos.close(); + + return true; + } } catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + displayError(e); } catch (NoSuchAlgorithmException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + displayError(e); } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + displayError(e); } catch (GeneralSecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + displayError(e); + } catch (KeytoolException e) { + displayError(e); } return false; @@ -152,8 +278,13 @@ public class ExportWizard extends Wizard implements IExportWizard { @Override public boolean canFinish() { + // check if we have the apk to resign, the destination location, and either + // a private key/certificate or the creation mode. In creation mode, unless + // all the key/keystore info is valid, the user cannot reach the last page, so there's + // no need to check them again here. return mApkFilePath != null && - mPrivateKey != null && mCertificate != null && + ((mPrivateKey != null && mCertificate != null) + || mKeystoreCreationMode || mKeyCreationMode) && mDestinationPath != null; } @@ -175,6 +306,22 @@ public class ExportWizard extends Wizard implements IExportWizard { } } + ExportWizardPage getKeystoreSelectionPage() { + return mKeystoreSelectionPage; + } + + ExportWizardPage getKeyCreationPage() { + return mKeyCreationPage; + } + + ExportWizardPage getKeySelectionPage() { + return mKeySelectionPage; + } + + ExportWizardPage getKeyCheckPage() { + return mKeyCheckPage; + } + /** * Returns an image descriptor for the wizard logo. */ @@ -192,10 +339,7 @@ public class ExportWizard extends Wizard implements IExportWizard { mApkFilePath = apkFilePath; mApkFileName = filename; - // indicate to the page that the project was changed. - for (ExportWizardPage page : mPages) { - page.newProjectReference(); - } + updatePageOnChange(ExportWizardPage.DATA_PROJECT); } String getApkFilename() { @@ -206,42 +350,95 @@ public class ExportWizard extends Wizard implements IExportWizard { mKeystore = path; mPrivateKey = null; mCertificate = null; + + updatePageOnChange(ExportWizardPage.DATA_KEYSTORE); } String getKeystore() { return mKeystore; } - void setKeyAlias(String name) { - mKeyAlias = name; - mPrivateKey = null; - mCertificate = null; + void setKeystoreCreationMode(boolean createStore) { + mKeystoreCreationMode = createStore; + updatePageOnChange(ExportWizardPage.DATA_KEYSTORE); } - String getKeyAlias() { - return mKeyAlias; + boolean getKeystoreCreationMode() { + return mKeystoreCreationMode; } - void setKeystorePassword(char[] password) { + + void setKeystorePassword(String password) { mKeystorePassword = password; mPrivateKey = null; mCertificate = null; + + updatePageOnChange(ExportWizardPage.DATA_KEYSTORE); } - char[] getKeystorePassword() { + String getKeystorePassword() { return mKeystorePassword; } + + void setKeyCreationMode(boolean createKey) { + mKeyCreationMode = createKey; + updatePageOnChange(ExportWizardPage.DATA_KEY); + } + + boolean getKeyCreationMode() { + return mKeyCreationMode; + } + + void setExistingAliases(List aliases) { + mExistingAliases = aliases; + } - void setKeyPassword(char[] password) { + List getExistingAliases() { + return mExistingAliases; + } + + void setKeyAlias(String name) { + mKeyAlias = name; + mPrivateKey = null; + mCertificate = null; + + updatePageOnChange(ExportWizardPage.DATA_KEY); + } + + String getKeyAlias() { + return mKeyAlias; + } + + void setKeyPassword(String password) { mKeyPassword = password; mPrivateKey = null; mCertificate = null; + + updatePageOnChange(ExportWizardPage.DATA_KEY); } - char[] getKeyPassword() { + String getKeyPassword() { return mKeyPassword; } + void setValidity(int validity) { + mValidity = validity; + updatePageOnChange(ExportWizardPage.DATA_KEY); + } + + int getValidity() { + return mValidity; + } + + void setDName(String dName) { + mDName = dName; + updatePageOnChange(ExportWizardPage.DATA_KEY); + } + + String getDName() { + return mDName; + } + void setSigningInfo(PrivateKey privateKey, X509Certificate certificate) { mPrivateKey = privateKey; mCertificate = certificate; @@ -251,4 +448,54 @@ public class ExportWizard extends Wizard implements IExportWizard { mDestinationPath = path; } + void updatePageOnChange(int changeMask) { + for (ExportWizardPage page : mPages) { + page.projectDataChanged(changeMask); + } + } + + private void displayError(String... messages) { + String message = null; + if (messages.length == 1) { + message = messages[0]; + } else { + StringBuilder sb = new StringBuilder(messages[0]); + for (int i = 1; i < messages.length; i++) { + sb.append('\n'); + sb.append(messages[i]); + } + + message = sb.toString(); + } + + AdtPlugin.displayError("Export Wizard", message); + } + + private void displayError(Exception e) { + String message = getExceptionMessage(e); + displayError(message); + + AdtPlugin.log(e, "Export Wizard Error"); + } + + /** + * Returns the {@link Throwable#getMessage()}. If the {@link Throwable#getMessage()} returns + * null, the method is called again on the cause of the Throwable object. + *

          If no Throwable in the chain has a valid message, the canonical name of the first + * exception is returned. + */ + private static String getExceptionMessage(Throwable t) { + String message = t.getMessage(); + if (message == null) { + Throwable cause = t.getCause(); + if (cause != null) { + return getExceptionMessage(cause); + } + + // no more cause and still no message. display the first exception. + return cause.getClass().getCanonicalName(); + } + + return message; + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/FinalExportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/FinalExportPage.java deleted file mode 100644 index 206e3aa..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/FinalExportPage.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * 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.project.export; - -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; - -import org.eclipse.core.resources.IProject; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.forms.widgets.FormText; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.UnrecoverableEntryException; -import java.security.KeyStore.PrivateKeyEntry; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Calendar; - -public class FinalExportPage extends ExportWizardPage { - - private final ExportWizard mWizard; - private PrivateKey mPrivateKey; - private X509Certificate mCertificate; - private Text mDestination; - private Button mBrowseButton; - private boolean mFatalSigningError; - private FormText mDetailText; - - protected FinalExportPage(ExportWizard wizard, String pageName) { - super(pageName); - mWizard = wizard; - - setTitle("Application Export"); - setDescription("Export the signed Application package."); - } - - public void createControl(Composite parent) { - setErrorMessage(null); - setMessage(null); - - // build the ui. - Composite composite = new Composite(parent, SWT.NULL); - composite.setLayoutData(new GridData(GridData.FILL_BOTH)); - GridLayout gl = new GridLayout(3, false); - gl.verticalSpacing *= 3; - composite.setLayout(gl); - - GridData gd; - - new Label(composite, SWT.NONE).setText("Destination APK file:"); - mDestination = new Text(composite, SWT.BORDER); - mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - mDestination.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onDestinationChange(); - } - }); - mBrowseButton = new Button(composite, SWT.PUSH); - mBrowseButton.setText("Browse..."); - mBrowseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - FileDialog fileDialog = new FileDialog(mBrowseButton.getShell(), SWT.SAVE); - - fileDialog.setText("Destination file name"); - fileDialog.setFileName(mWizard.getApkFilename()); - - String saveLocation = fileDialog.open(); - if (saveLocation != null) { - mDestination.setText(saveLocation); - } - } - }); - - mDetailText = new FormText(composite, SWT.NONE); - mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); - gd.horizontalSpan = 3; - - setControl(composite); - } - - @Override - void onShow() { - // fill the texts with information loaded from the project. - if (mNewProjectReference) { - // reset the destination from the content of the project - IProject project = mWizard.getProject(); - - String destination = ProjectHelper.loadStringProperty(project, - ExportWizard.PROPERTY_DESTINATION); - if (destination != null) { - mDestination.setText(destination); - } - } - - // reset the wizard with no key/cert to make it not finishable, unless a valid - // key/cert is found. - mWizard.setSigningInfo(null, null); - - try { - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - FileInputStream fis = new FileInputStream(mWizard.getKeystore()); - keyStore.load(fis, mWizard.getKeystorePassword()); - fis.close(); - PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry( - mWizard.getKeyAlias(), - new KeyStore.PasswordProtection(mWizard.getKeyPassword())); - - if (entry != null) { - mPrivateKey = entry.getPrivateKey(); - mCertificate = (X509Certificate)entry.getCertificate(); - } else { - setErrorMessage("Unable to find key"); - - setPageComplete(false); - } - } catch (FileNotFoundException e) { - // this was checked at the first previous step and will not happen here, unless - // the file was removed during the export wizard execution. - onException(e); - } catch (KeyStoreException e) { - onException(e); - } catch (NoSuchAlgorithmException e) { - onException(e); - } catch (UnrecoverableEntryException e) { - onException(e); - } catch (CertificateException e) { - onException(e); - } catch (IOException e) { - onException(e); - } - - if (mPrivateKey != null && mCertificate != null) { - mFatalSigningError = false; - - Calendar expirationCalendar = Calendar.getInstance(); - expirationCalendar.setTime(mCertificate.getNotAfter()); - Calendar today = Calendar.getInstance(); - - if (expirationCalendar.before(today)) { - mDetailText.setText(String.format("

          Certificate expired on %s

          ", - mCertificate.getNotAfter().toString()), - true /* parseTags */, true /* expandURLs */); - - // fatal error = nothing can make the page complete. - mFatalSigningError = true; - - setErrorMessage("Certificate is expired!"); - setPageComplete(false); - } else { - // valid, key/cert: put it in the wizard so that it can be finished - mWizard.setSigningInfo(mPrivateKey, mCertificate); - - StringBuilder sb = new StringBuilder(String.format("

          Certificate expires on %s.

          ", - mCertificate.getNotAfter().toString())); - - int expirationYear = expirationCalendar.get(Calendar.YEAR); - int thisYear = today.get(Calendar.YEAR); - - if (thisYear + 25 < expirationYear) { - // do nothing - } else { - if (expirationYear == thisYear) { - sb.append("

          The certificate expires this year!

          "); - } else { - int count = expirationYear-thisYear; - sb.append(String.format("

          The Certificate expires in %1$s %2$s!

          ", - count, count == 1 ? "year" : "years")); - } - - sb.append("

          Make sure the certificate is valid for the planned lifetime of the product.

          "); - sb.append("

          If the certificate expires, you will be forced to sign your application with a different one.

          "); - sb.append("

          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.

          "); - sb.append("

          Android Market currently requires certificates to be valid until 2033.

          "); - - sb.append("
          "); - } - - mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */); - } - mDetailText.getParent().layout(); - } else { - // fatal error = nothing can make the page complete. - mFatalSigningError = true; - } - - onDestinationChange(); - } - - private void onDestinationChange() { - if (mFatalSigningError == false) { - String path = mDestination.getText().trim(); - if (path.length() == 0) { - setErrorMessage("Enter destination for the APK file."); - mWizard.setDestination(null); // this is to reset canFinish in the wizard - setPageComplete(false); - } else { - File file = new File(path); - if (file.isDirectory()) { - setErrorMessage("Destination is a directory!"); - mWizard.setDestination(null); // this is to reset canFinish in the wizard - setPageComplete(false); - } else { - File parentFile = file.getParentFile(); - if (parentFile == null || parentFile.isDirectory() == false) { - setErrorMessage("Not a valid directory."); - mWizard.setDestination(null); // this is to reset canFinish in the wizard - setPageComplete(false); - } else { - mWizard.setDestination(path); - setErrorMessage(null); - setPageComplete(true); - } - } - } - } - } - - /** - * Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a - * {@link Throwable} object. If the {@link Throwable#getMessage()} returns null, - * the method is called again on the cause of the Throwable. - */ - private void onException(Throwable t) { - String message = t.getMessage(); - if (message == null) { - Throwable cause = t.getCause(); - if (cause != null) { - onException(cause); - } else { - // no more cause and still no message. display the first exception. - setErrorMessage(cause.getClass().getCanonicalName()); - setPageComplete(false); - } - return; - } - - setErrorMessage(message); - setPageComplete(false); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java new file mode 100644 index 0000000..c64bf10 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java @@ -0,0 +1,291 @@ +/* + * 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.project.export; + +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; + +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.widgets.FormText; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.UnrecoverableEntryException; +import java.security.KeyStore.PrivateKeyEntry; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Calendar; + +/** + * Final page of the wizard that checks the key and ask for the ouput location. + */ +final class KeyCheckPage extends ExportWizardPage { + + private final ExportWizard mWizard; + private PrivateKey mPrivateKey; + private X509Certificate mCertificate; + private Text mDestination; + private boolean mFatalSigningError; + private FormText mDetailText; + + protected KeyCheckPage(ExportWizard wizard, String pageName) { + super(pageName); + mWizard = wizard; + + setTitle("Destination and key/certificate checks"); + setDescription(""); // TODO + } + + public void createControl(Composite parent) { + setErrorMessage(null); + setMessage(null); + + // build the ui. + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + GridLayout gl = new GridLayout(3, false); + gl.verticalSpacing *= 3; + composite.setLayout(gl); + + GridData gd; + + new Label(composite, SWT.NONE).setText("Destination APK file:"); + mDestination = new Text(composite, SWT.BORDER); + mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mDestination.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onDestinationChange(); + } + }); + final Button browseButton = new Button(composite, SWT.PUSH); + browseButton.setText("Browse..."); + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE); + + fileDialog.setText("Destination file name"); + fileDialog.setFileName(mWizard.getApkFilename()); + + String saveLocation = fileDialog.open(); + if (saveLocation != null) { + mDestination.setText(saveLocation); + } + } + }); + + mDetailText = new FormText(composite, SWT.NONE); + mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); + gd.horizontalSpan = 3; + + setControl(composite); + } + + @Override + void onShow() { + // fill the texts with information loaded from the project. + if ((mProjectDataChanged & DATA_PROJECT) != 0) { + // reset the destination from the content of the project + IProject project = mWizard.getProject(); + + String destination = ProjectHelper.loadStringProperty(project, + ExportWizard.PROPERTY_DESTINATION); + if (destination != null) { + mDestination.setText(destination); + } + } + + // if anything change we basically reload the data. + if (mProjectDataChanged != 0) { + mFatalSigningError = false; + + // reset the wizard with no key/cert to make it not finishable, unless a valid + // key/cert is found. + mWizard.setSigningInfo(null, null); + + if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) { + int validity = mWizard.getValidity(); + StringBuilder sb = new StringBuilder( + String.format("

          Certificate expires in %d years.

          ", + validity)); + + if (validity < 25) { + sb.append("

          Make sure the certificate is valid for the planned lifetime of the product.

          "); + sb.append("

          If the certificate expires, you will be forced to sign your application with a different one.

          "); + sb.append("

          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.

          "); + sb.append("

          Android Market currently requires certificates to be valid until 2033.

          "); + } + + sb.append("
          "); + mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */); + } else { + try { + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream fis = new FileInputStream(mWizard.getKeystore()); + keyStore.load(fis, mWizard.getKeystorePassword().toCharArray()); + fis.close(); + PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry( + mWizard.getKeyAlias(), + new KeyStore.PasswordProtection( + mWizard.getKeyPassword().toCharArray())); + + if (entry != null) { + mPrivateKey = entry.getPrivateKey(); + mCertificate = (X509Certificate)entry.getCertificate(); + } else { + setErrorMessage("Unable to find key."); + + setPageComplete(false); + } + } catch (FileNotFoundException e) { + // this was checked at the first previous step and will not happen here, unless + // the file was removed during the export wizard execution. + onException(e); + } catch (KeyStoreException e) { + onException(e); + } catch (NoSuchAlgorithmException e) { + onException(e); + } catch (UnrecoverableEntryException e) { + onException(e); + } catch (CertificateException e) { + onException(e); + } catch (IOException e) { + onException(e); + } + + if (mPrivateKey != null && mCertificate != null) { + Calendar expirationCalendar = Calendar.getInstance(); + expirationCalendar.setTime(mCertificate.getNotAfter()); + Calendar today = Calendar.getInstance(); + + if (expirationCalendar.before(today)) { + mDetailText.setText(String.format( + "

          Certificate expired on %s

          ", + mCertificate.getNotAfter().toString()), + true /* parseTags */, true /* expandURLs */); + + // fatal error = nothing can make the page complete. + mFatalSigningError = true; + + setErrorMessage("Certificate is expired."); + setPageComplete(false); + } else { + // valid, key/cert: put it in the wizard so that it can be finished + mWizard.setSigningInfo(mPrivateKey, mCertificate); + + StringBuilder sb = new StringBuilder(String.format( + "

          Certificate expires on %s.

          ", + mCertificate.getNotAfter().toString())); + + int expirationYear = expirationCalendar.get(Calendar.YEAR); + int thisYear = today.get(Calendar.YEAR); + + if (thisYear + 25 < expirationYear) { + // do nothing + } else { + if (expirationYear == thisYear) { + sb.append("

          The certificate expires this year.

          "); + } else { + int count = expirationYear-thisYear; + sb.append(String.format( + "

          The Certificate expires in %1$s %2$s.

          ", + count, count == 1 ? "year" : "years")); + } + + sb.append("

          Make sure the certificate is valid for the planned lifetime of the product.

          "); + sb.append("

          If the certificate expires, you will be forced to sign your application with a different one.

          "); + sb.append("

          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.

          "); + sb.append("

          Android Market currently requires certificates to be valid until 2033.

          "); + } + + sb.append("
          "); + + mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */); + } + mDetailText.getParent().layout(); + } else { + // fatal error = nothing can make the page complete. + mFatalSigningError = true; + } + } + } + + onDestinationChange(); + } + + private void onDestinationChange() { + if (mFatalSigningError == false) { + // reset messages for now. + setErrorMessage(null); + setMessage(null); + + String path = mDestination.getText().trim(); + + if (path.length() == 0) { + setErrorMessage("Enter destination for the APK file."); + mWizard.setDestination(null); // this is to reset canFinish in the wizard + setPageComplete(false); + return; + } + + File file = new File(path); + if (file.isDirectory()) { + setErrorMessage("Destination is a directory."); + mWizard.setDestination(null); // this is to reset canFinish in the wizard + setPageComplete(false); + return; + } + + File parentFile = file.getParentFile(); + if (parentFile == null || parentFile.isDirectory() == false) { + setErrorMessage("Not a valid directory."); + mWizard.setDestination(null); // this is to reset canFinish in the wizard + setPageComplete(false); + return; + } + + // no error, set the destination in the wizard. + mWizard.setDestination(path); + setPageComplete(true); + + // However, we should also test if the file already exists. + if (file.isFile()) { + setMessage("Destination file already exists.", WARNING); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCreationPage.java new file mode 100644 index 0000000..d7365f7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCreationPage.java @@ -0,0 +1,332 @@ +/* + * 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.project.export; + +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +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.Text; + +import java.util.List; + +/** + * Key creation page. + */ +final class KeyCreationPage extends ExportWizardPage { + + private final ExportWizard mWizard; + private Text mAlias; + private Text mKeyPassword; + private Text mKeyPassword2; + private Text mCnField; + private boolean mDisableOnChange = false; + private Text mOuField; + private Text mOField; + private Text mLField; + private Text mStField; + private Text mCField; + private String mDName; + private int mValidity = 0; + private List mExistingAliases; + + + protected KeyCreationPage(ExportWizard wizard, String pageName) { + super(pageName); + mWizard = wizard; + + setTitle("Key Creation"); + setDescription(""); // TODO? + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + GridLayout gl = new GridLayout(2, false); + composite.setLayout(gl); + + GridData gd; + + new Label(composite, SWT.NONE).setText("Alias:"); + mAlias = new Text(composite, SWT.BORDER); + mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("Password:"); + mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD); + mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mKeyPassword.addVerifyListener(sPasswordVerifier); + + new Label(composite, SWT.NONE).setText("Confirm:"); + mKeyPassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD); + mKeyPassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mKeyPassword2.addVerifyListener(sPasswordVerifier); + + new Label(composite, SWT.NONE).setText("Validity (years):"); + final Text validityText = new Text(composite, SWT.BORDER); + validityText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + validityText.addVerifyListener(new VerifyListener() { + public void verifyText(VerifyEvent e) { + // check for digit only. + for (int i = 0 ; i < e.text.length(); i++) { + char letter = e.text.charAt(i); + if (letter < '0' || letter > '9') { + e.doit = false; + return; + } + } + } + }); + + new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData( + gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 2; + + new Label(composite, SWT.NONE).setText("First and Last Name:"); + mCnField = new Text(composite, SWT.BORDER); + mCnField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("Organizational Unit:"); + mOuField = new Text(composite, SWT.BORDER); + mOuField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("Organization:"); + mOField = new Text(composite, SWT.BORDER); + mOField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("City or Locality:"); + mLField = new Text(composite, SWT.BORDER); + mLField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("State or Province:"); + mStField = new Text(composite, SWT.BORDER); + mStField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + new Label(composite, SWT.NONE).setText("Country Code (XX):"); + mCField = new Text(composite, SWT.BORDER); + mCField.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + mAlias.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mWizard.setKeyAlias(mAlias.getText().trim()); + onChange(); + } + }); + mKeyPassword.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mWizard.setKeyPassword(mKeyPassword.getText()); + onChange(); + } + }); + mKeyPassword2.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onChange(); + } + }); + + validityText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + try { + mValidity = Integer.parseInt(validityText.getText()); + } catch (NumberFormatException e2) { + // this should only happen if the text field is empty due to the verifyListener. + mValidity = 0; + } + mWizard.setValidity(mValidity); + onChange(); + } + }); + + ModifyListener dNameListener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + onDNameChange(); + } + }; + + mCnField.addModifyListener(dNameListener); + mOuField.addModifyListener(dNameListener); + mOField.addModifyListener(dNameListener); + mLField.addModifyListener(dNameListener); + mStField.addModifyListener(dNameListener); + mCField.addModifyListener(dNameListener); + } + + @Override + void onShow() { + // fill the texts with information loaded from the project. + if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) { + // reset the keystore/alias from the content of the project + IProject project = mWizard.getProject(); + + // disable onChange for now. we'll call it once at the end. + mDisableOnChange = true; + + String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS); + if (alias != null) { + mAlias.setText(alias); + } + + // get the existing list of keys if applicable + if (mWizard.getKeyCreationMode()) { + mExistingAliases = mWizard.getExistingAliases(); + } else { + mExistingAliases = null; + } + + // reset the passwords + mKeyPassword.setText(""); //$NON-NLS-1$ + mKeyPassword2.setText(""); //$NON-NLS-1$ + + // enable onChange, and call it to display errors and enable/disable pageCompleted. + mDisableOnChange = false; + onChange(); + } + } + + @Override + public IWizardPage getPreviousPage() { + if (mWizard.getKeyCreationMode()) { // this means we create a key from an existing store + return mWizard.getKeySelectionPage(); + } + + return mWizard.getKeystoreSelectionPage(); + } + + @Override + public IWizardPage getNextPage() { + return mWizard.getKeyCheckPage(); + } + + /** + * Handles changes and update the error message and calls {@link #setPageComplete(boolean)}. + */ + private void onChange() { + if (mDisableOnChange) { + return; + } + + setErrorMessage(null); + setMessage(null); + + if (mAlias.getText().trim().length() == 0) { + setErrorMessage("Enter key alias."); + setPageComplete(false); + return; + } else if (mExistingAliases != null) { + // we cannot use indexOf, because we need to do a case-insensitive check + String keyAlias = mAlias.getText().trim(); + for (String alias : mExistingAliases) { + if (alias.equalsIgnoreCase(keyAlias)) { + setErrorMessage("Key alias already exists in keystore."); + setPageComplete(false); + return; + } + } + } + + String value = mKeyPassword.getText(); + if (value.length() == 0) { + setErrorMessage("Enter key password."); + setPageComplete(false); + return; + } else if (value.length() < 6) { + setErrorMessage("Key password is too short - must be at least 6 characters."); + setPageComplete(false); + return; + } + + if (value.equals(mKeyPassword2.getText()) == false) { + setErrorMessage("Key passwords don't match."); + setPageComplete(false); + return; + } + + if (mValidity == 0) { + setErrorMessage("Key certificate validity is required."); + setPageComplete(false); + return; + } else if (mValidity < 25) { + setMessage("A 25 year certificate validity is recommended.", WARNING); + } else if (mValidity > 1000) { + setErrorMessage("Key certificate validity must be between 1 and 1000 years."); + setPageComplete(false); + return; + } + + if (mDName == null || mDName.length() == 0) { + setErrorMessage("At least one Certificate issuer field is required to be non-empty."); + setPageComplete(false); + return; + } + + setPageComplete(true); + } + + /** + * Handles changes in the DName fields. + */ + private void onDNameChange() { + StringBuilder sb = new StringBuilder(); + + buildDName("CN", mCnField, sb); + buildDName("OU", mOuField, sb); + buildDName("O", mOField, sb); + buildDName("L", mLField, sb); + buildDName("ST", mStField, sb); + buildDName("C", mCField, sb); + + mDName = sb.toString(); + mWizard.setDName(mDName); + + onChange(); + } + + /** + * Builds the distinguished name string with the provided {@link StringBuilder}. + * @param prefix the prefix of the entry. + * @param textField The {@link Text} field containing the entry value. + * @param sb the string builder containing the dname. + */ + private void buildDName(String prefix, Text textField, StringBuilder sb) { + if (textField != null) { + String value = textField.getText().trim(); + if (value.length() > 0) { + if (sb.length() > 0) { + sb.append(","); + } + + sb.append(prefix); + sb.append('='); + sb.append(value); + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeySelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeySelectionPage.java new file mode 100644 index 0000000..2fcd757 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeySelectionPage.java @@ -0,0 +1,266 @@ +/* + * 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.project.export; + +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.Enumeration; + +/** + * Key Selection Page. This is used when an existing keystore is used. + */ +final class KeySelectionPage extends ExportWizardPage { + + private final ExportWizard mWizard; + private Label mKeyAliasesLabel; + private Combo mKeyAliases; + private Label mKeyPasswordLabel; + private Text mKeyPassword; + private boolean mDisableOnChange = false; + private Button mUseExistingKey; + private Button mCreateKey; + + protected KeySelectionPage(ExportWizard wizard, String pageName) { + super(pageName); + mWizard = wizard; + + setTitle("Key alias selection"); + setDescription(""); // TODO + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + GridLayout gl = new GridLayout(3, false); + composite.setLayout(gl); + + GridData gd; + + mUseExistingKey = new Button(composite, SWT.RADIO); + mUseExistingKey.setText("Use existing key"); + mUseExistingKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + mUseExistingKey.setSelection(true); + + new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); + gd.heightHint = 0; + gd.widthHint = 50; + mKeyAliasesLabel = new Label(composite, SWT.NONE); + mKeyAliasesLabel.setText("Alias:"); + mKeyAliases = new Combo(composite, SWT.READ_ONLY); + mKeyAliases.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); + gd.heightHint = 0; + gd.widthHint = 50; + mKeyPasswordLabel = new Label(composite, SWT.NONE); + mKeyPasswordLabel.setText("Password:"); + mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD); + mKeyPassword.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mCreateKey = new Button(composite, SWT.RADIO); + mCreateKey.setText("Create new key"); + mCreateKey.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + mUseExistingKey.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mWizard.setKeyCreationMode(!mUseExistingKey.getSelection()); + enableWidgets(); + onChange(); + } + }); + + mKeyAliases.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + mWizard.setKeyAlias(mKeyAliases.getItem(mKeyAliases.getSelectionIndex())); + onChange(); + } + }); + + mKeyPassword.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mWizard.setKeyPassword(mKeyPassword.getText()); + onChange(); + } + }); + } + + @Override + void onShow() { + // fill the texts with information loaded from the project. + if ((mProjectDataChanged & (DATA_PROJECT | DATA_KEYSTORE)) != 0) { + // disable onChange for now. we'll call it once at the end. + mDisableOnChange = true; + + // reset the alias from the content of the project + try { + // reset to using a key + mWizard.setKeyCreationMode(false); + mUseExistingKey.setSelection(true); + mCreateKey.setSelection(false); + enableWidgets(); + + // remove the content of the alias combo always and first, in case the + // keystore password is wrong + mKeyAliases.removeAll(); + + // get the alias list (also used as a keystore password test) + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + FileInputStream fis = new FileInputStream(mWizard.getKeystore()); + keyStore.load(fis, mWizard.getKeystorePassword().toCharArray()); + fis.close(); + + Enumeration aliases = keyStore.aliases(); + + // get the alias from the project previous export, and look for a match as + // we add the aliases to the combo. + IProject project = mWizard.getProject(); + + String keyAlias = ProjectHelper.loadStringProperty(project, + ExportWizard.PROPERTY_ALIAS); + + ArrayList aliasList = new ArrayList(); + + int selection = -1; + int count = 0; + while (aliases.hasMoreElements()) { + String alias = aliases.nextElement(); + mKeyAliases.add(alias); + aliasList.add(alias); + if (selection == -1 && alias.equalsIgnoreCase(keyAlias)) { + selection = count; + } + count++; + } + + mWizard.setExistingAliases(aliasList); + + if (selection != -1) { + mKeyAliases.select(selection); + + // since a match was found and is selected, we need to give it to + // the wizard as well + mWizard.setKeyAlias(keyAlias); + } else { + mKeyAliases.clearSelection(); + } + + // reset the password + mKeyPassword.setText(""); //$NON-NLS-1$ + + // enable onChange, and call it to display errors and enable/disable pageCompleted. + mDisableOnChange = false; + onChange(); + } catch (KeyStoreException e) { + onException(e); + } catch (FileNotFoundException e) { + onException(e); + } catch (NoSuchAlgorithmException e) { + onException(e); + } catch (CertificateException e) { + onException(e); + } catch (IOException e) { + onException(e); + } finally { + // in case we exit with an exception, we need to reset this + mDisableOnChange = false; + } + } + } + + @Override + public IWizardPage getPreviousPage() { + return mWizard.getKeystoreSelectionPage(); + } + + @Override + public IWizardPage getNextPage() { + if (mWizard.getKeyCreationMode()) { + return mWizard.getKeyCreationPage(); + } + + return mWizard.getKeyCheckPage(); + } + + /** + * Handles changes and update the error message and calls {@link #setPageComplete(boolean)}. + */ + private void onChange() { + if (mDisableOnChange) { + return; + } + + setErrorMessage(null); + setMessage(null); + + if (mWizard.getKeyCreationMode() == false) { + if (mKeyAliases.getSelectionIndex() == -1) { + setErrorMessage("Select a key alias."); + setPageComplete(false); + return; + } + + if (mKeyPassword.getText().trim().length() == 0) { + setErrorMessage("Enter key password."); + setPageComplete(false); + return; + } + } + + setPageComplete(true); + } + + private void enableWidgets() { + boolean useKey = !mWizard.getKeyCreationMode(); + mKeyAliasesLabel.setEnabled(useKey); + mKeyAliases.setEnabled(useKey); + mKeyPassword.setEnabled(useKey); + mKeyPasswordLabel.setEnabled(useKey); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeystoreSelectionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeystoreSelectionPage.java new file mode 100644 index 0000000..c5a4d47 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeystoreSelectionPage.java @@ -0,0 +1,260 @@ +/* + * 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.project.export; + +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.io.File; + +/** + * Keystore selection page. This page allows to choose to create a new keystore or use an + * existing one. + */ +final class KeystoreSelectionPage extends ExportWizardPage { + + private final ExportWizard mWizard; + private Button mUseExistingKeystore; + private Button mCreateKeystore; + private Text mKeystore; + private Text mKeystorePassword; + private Label mConfirmLabel; + private Text mKeystorePassword2; + private boolean mDisableOnChange = false; + + protected KeystoreSelectionPage(ExportWizard wizard, String pageName) { + super(pageName); + mWizard = wizard; + + setTitle("Keystore selection"); + setDescription(""); //TODO + } + + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + GridLayout gl = new GridLayout(3, false); + composite.setLayout(gl); + + GridData gd; + + mUseExistingKeystore = new Button(composite, SWT.RADIO); + mUseExistingKeystore.setText("Use existing keystore"); + mUseExistingKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + mUseExistingKeystore.setSelection(true); + + mCreateKeystore = new Button(composite, SWT.RADIO); + mCreateKeystore.setText("Create new keystore"); + mCreateKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + + new Label(composite, SWT.NONE).setText("Location:"); + mKeystore = new Text(composite, SWT.BORDER); + mKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + final Button browseButton = new Button(composite, SWT.PUSH); + browseButton.setText("Browse..."); + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog fileDialog; + if (mUseExistingKeystore.getSelection()) { + fileDialog = new FileDialog(browseButton.getShell(),SWT.OPEN); + fileDialog.setText("Load Keystore"); + } else { + fileDialog = new FileDialog(browseButton.getShell(),SWT.SAVE); + fileDialog.setText("Select Keystore Name"); + } + + String fileName = fileDialog.open(); + if (fileName != null) { + mKeystore.setText(fileName); + } + } + }); + + new Label(composite, SWT.NONE).setText("Password:"); + mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD); + mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mKeystorePassword.addVerifyListener(sPasswordVerifier); + new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); + gd.heightHint = gd.widthHint = 0; + + mConfirmLabel = new Label(composite, SWT.NONE); + mConfirmLabel.setText("Confirm:"); + mKeystorePassword2 = new Text(composite, SWT.BORDER | SWT.PASSWORD); + mKeystorePassword2.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mKeystorePassword2.addVerifyListener(sPasswordVerifier); + new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); + gd.heightHint = gd.widthHint = 0; + mKeystorePassword2.setEnabled(false); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + mUseExistingKeystore.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + boolean createStore = !mUseExistingKeystore.getSelection(); + mKeystorePassword2.setEnabled(createStore); + mConfirmLabel.setEnabled(createStore); + mWizard.setKeystoreCreationMode(createStore); + onChange(); + } + }); + + mKeystore.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mWizard.setKeystore(mKeystore.getText().trim()); + onChange(); + } + }); + + mKeystorePassword.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + mWizard.setKeystorePassword(mKeystorePassword.getText()); + onChange(); + } + }); + + mKeystorePassword2.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onChange(); + } + }); + } + + @Override + public IWizardPage getNextPage() { + if (mUseExistingKeystore.getSelection()) { + return mWizard.getKeySelectionPage(); + } + + return mWizard.getKeyCreationPage(); + } + + @Override + void onShow() { + // fill the texts with information loaded from the project. + if ((mProjectDataChanged & DATA_PROJECT) != 0) { + // reset the keystore/alias from the content of the project + IProject project = mWizard.getProject(); + + // disable onChange for now. we'll call it once at the end. + mDisableOnChange = true; + + String keystore = ProjectHelper.loadStringProperty(project, + ExportWizard.PROPERTY_KEYSTORE); + if (keystore != null) { + mKeystore.setText(keystore); + } + + // reset the passwords + mKeystorePassword.setText(""); //$NON-NLS-1$ + mKeystorePassword2.setText(""); //$NON-NLS-1$ + + // enable onChange, and call it to display errors and enable/disable pageCompleted. + mDisableOnChange = false; + onChange(); + } + } + + /** + * Handles changes and update the error message and calls {@link #setPageComplete(boolean)}. + */ + private void onChange() { + if (mDisableOnChange) { + return; + } + + setErrorMessage(null); + setMessage(null); + + boolean createStore = !mUseExistingKeystore.getSelection(); + + // checks the keystore path is non null. + String keystore = mKeystore.getText().trim(); + if (keystore.length() == 0) { + setErrorMessage("Enter path to keystore."); + setPageComplete(false); + return; + } else { + File f = new File(keystore); + if (f.exists() == false) { + if (createStore == false) { + setErrorMessage("Keystore does not exist."); + setPageComplete(false); + return; + } + } else if (f.isDirectory()) { + setErrorMessage("Keystore path is a directory."); + setPageComplete(false); + return; + } else if (f.isFile()) { + if (createStore) { + setErrorMessage("File already exists."); + setPageComplete(false); + return; + } + } + } + + String value = mKeystorePassword.getText(); + if (value.length() == 0) { + setErrorMessage("Enter keystore password."); + setPageComplete(false); + return; + } else if (createStore && value.length() < 6) { + setErrorMessage("Keystore password is too short - must be at least 6 characters."); + setPageComplete(false); + return; + } + + if (createStore) { + if (mKeystorePassword2.getText().length() == 0) { + setErrorMessage("Confirm keystore password."); + setPageComplete(false); + return; + } + + if (mKeystorePassword.getText().equals(mKeystorePassword2.getText()) == false) { + setErrorMessage("Keystore passwords do not match."); + setPageComplete(false); + return; + } + } + + setPageComplete(true); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/PreExportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/PreExportPage.java deleted file mode 100644 index 5fc204d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/PreExportPage.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * 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.project.export; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestParser; -import com.android.ide.eclipse.common.project.BaseProjectHelper; -import com.android.ide.eclipse.common.project.ProjectChooserHelper; - -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -import java.io.File; - -/** - * First Export Wizard Page. Display warning/errors. - */ -public class PreExportPage extends ExportWizardPage { - private final static String IMG_ERROR = "error.png"; //$NON-NLS-1$ - private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$ - - private final ExportWizard mWizard; - private Display mDisplay; - private Image mError; - private Image mWarning; - private boolean mHasMessage = false; - private Composite mTopComposite; - private Composite mErrorComposite; - private Text mProjectText; - private ProjectChooserHelper mProjectChooserHelper; - - protected PreExportPage(ExportWizard wizard, String pageName) { - super(pageName); - mWizard = wizard; - - setTitle("Pre Export Checks"); - setDescription("Performs a set of checks to make sure the application can be exported."); - } - - public void createControl(Composite parent) { - mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); - mDisplay = parent.getDisplay(); - - GridLayout gl = null; - GridData gd = null; - - mTopComposite = new Composite(parent, SWT.NONE); - mTopComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); - mTopComposite.setLayout(new GridLayout(1, false)); - - // composite for the project selection. - Composite projectComposite = new Composite(mTopComposite, SWT.NONE); - projectComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - projectComposite.setLayout(gl = new GridLayout(3, false)); - gl.marginHeight = gl.marginWidth = 0; - - Label label = new Label(projectComposite, SWT.NONE); - label.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - gd.horizontalSpan = 3; - label.setText("Select the project to export:"); - - new Label(projectComposite, SWT.NONE).setText("Project:"); - mProjectText = new Text(projectComposite, SWT.BORDER); - mProjectText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - mProjectText.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - handleProjectNameChange(); - } - }); - - Button browseButton = new Button(projectComposite, SWT.PUSH); - browseButton.setText("Browse..."); - browseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject( - mProjectText.getText().trim()); - - if (javaProject != null) { - IProject project = javaProject.getProject(); - - // set the new name in the text field. The modify listener will take - // care of updating the status and the ExportWizard object. - mProjectText.setText(project.getName()); - } - } - }); - - setControl(mTopComposite); - } - - @Override - void onShow() { - // get the project and init the ui - IProject project = mWizard.getProject(); - if (project != null) { - mProjectText.setText(project.getName()); - } - } - - private void buildErrorUi(IProject project) { - // Show description the first time - setErrorMessage(null); - setMessage(null); - setPageComplete(true); - mHasMessage = false; - - // composite parent for the warning/error - GridLayout gl = null; - mErrorComposite = new Composite(mTopComposite, SWT.NONE); - mErrorComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - gl = new GridLayout(2, false); - gl.marginHeight = gl.marginWidth = 0; - gl.verticalSpacing *= 3; // more spacing than normal. - mErrorComposite.setLayout(gl); - - if (project == null) { - setErrorMessage("Select project to export."); - mHasMessage = true; - } else { - try { - if (project.hasNature(AndroidConstants.NATURE) == false) { - addError(mErrorComposite, "Project is not an Android project."); - } else { - // check for errors - if (ProjectHelper.hasError(project, true)) { - addError(mErrorComposite, "Project has compilation error(s)"); - } - - // check the project output - IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project); - if (outputIFolder != null) { - String outputOsPath = outputIFolder.getLocation().toOSString(); - String apkFilePath = outputOsPath + File.separator + project.getName() + - AndroidConstants.DOT_ANDROID_PACKAGE; - - File f = new File(apkFilePath); - if (f.isFile() == false) { - addError(mErrorComposite, - String.format("%1$s/%2$s/%1$s%3$s does not exists!", - project.getName(), - outputIFolder.getName(), - AndroidConstants.DOT_ANDROID_PACKAGE)); - } - } else { - addError(mErrorComposite, - "Unable to get the output folder of the project!"); - } - - - // project is an android project, we check the debuggable attribute. - AndroidManifestParser manifestParser = AndroidManifestParser.parse( - BaseProjectHelper.getJavaProject(project), null /* errorListener */, - true /* gatherData */, false /* markErrors */); - - Boolean debuggable = manifestParser.getDebuggable(); - - if (debuggable != null && debuggable == Boolean.TRUE) { - addWarning(mErrorComposite, - "The manifest 'debuggable' attribute is set to true.\nYou should set it to false for applications that you release to the public."); - } - - // check for mapview stuff - } - } catch (CoreException e) { - // unable to access nature - addError(mErrorComposite, "Unable to get project nature"); - } - } - - if (mHasMessage == false) { - Label label = new Label(mErrorComposite, SWT.NONE); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalSpan = 2; - label.setLayoutData(gd); - label.setText("No errors found. Click Next."); - } - - mTopComposite.layout(); - } - - /** - * Adds an error label to a {@link Composite} object. - * @param parent the Composite parent. - * @param message the error message. - */ - private void addError(Composite parent, String message) { - if (mError == null) { - mError = AdtPlugin.getImageLoader().loadImage(IMG_ERROR, mDisplay); - } - - new Label(parent, SWT.NONE).setImage(mError); - Label label = new Label(parent, SWT.NONE); - label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - label.setText(message); - - setErrorMessage("Application cannot be exported due to the error(s) below."); - setPageComplete(false); - mHasMessage = true; - } - - /** - * Adds a warning label to a {@link Composite} object. - * @param parent the Composite parent. - * @param message the warning message. - */ - private void addWarning(Composite parent, String message) { - if (mWarning == null) { - mWarning = AdtPlugin.getImageLoader().loadImage(IMG_WARNING, mDisplay); - } - - new Label(parent, SWT.NONE).setImage(mWarning); - Label label = new Label(parent, SWT.NONE); - label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - label.setText(message); - - mHasMessage = true; - } - - /** - * Checks the parameters for correctness, and update the error message and buttons. - * @return the current IProject of this launch config. - */ - private void handleProjectNameChange() { - setPageComplete(false); - - if (mErrorComposite != null) { - mErrorComposite.dispose(); - mErrorComposite = null; - } - - // update the wizard with the new project - mWizard.setProject(null, null, null); - - //test the project name first! - String text = mProjectText.getText().trim(); - if (text.length() == 0) { - setErrorMessage("Select project to export."); - } else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) { - setErrorMessage("Project name contains unsupported characters!"); - } else { - IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); - IProject found = null; - for (IJavaProject javaProject : projects) { - if (javaProject.getProject().getName().equals(text)) { - found = javaProject.getProject(); - break; - } - - } - - if (found != null) { - setErrorMessage(null); - - // update the wizard with the new project - setApkFilePathInWizard(found); - - // now rebuild the error ui. - buildErrorUi(found); - } else { - setErrorMessage(String.format("There is no android project named '%1$s'", - text)); - } - } - } - - private void setApkFilePathInWizard(IProject project) { - if (project != null) { - IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project); - if (outputIFolder != null) { - String outputOsPath = outputIFolder.getLocation().toOSString(); - String apkFilePath = outputOsPath + File.separator + project.getName() + - AndroidConstants.DOT_ANDROID_PACKAGE; - - File f = new File(apkFilePath); - if (f.isFile()) { - mWizard.setProject(project, apkFilePath, f.getName()); - return; - } - } - } - - mWizard.setProject(null, null, null); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java new file mode 100644 index 0000000..3614be3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java @@ -0,0 +1,323 @@ +/* + * 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.project.export; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestParser; +import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.ide.eclipse.common.project.ProjectChooserHelper; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.io.File; + +/** + * First Export Wizard Page. Display warning/errors. + */ +final class ProjectCheckPage extends ExportWizardPage { + private final static String IMG_ERROR = "error.png"; //$NON-NLS-1$ + private final static String IMG_WARNING = "warning.png"; //$NON-NLS-1$ + + private final ExportWizard mWizard; + private Display mDisplay; + private Image mError; + private Image mWarning; + private boolean mHasMessage = false; + private Composite mTopComposite; + private Composite mErrorComposite; + private Text mProjectText; + private ProjectChooserHelper mProjectChooserHelper; + private boolean mFirstOnShow = true; + + protected ProjectCheckPage(ExportWizard wizard, String pageName) { + super(pageName); + mWizard = wizard; + + setTitle("Project Checks"); + setDescription("Performs a set of checks to make sure the application can be exported."); + } + + public void createControl(Composite parent) { + mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); + mDisplay = parent.getDisplay(); + + GridLayout gl = null; + GridData gd = null; + + mTopComposite = new Composite(parent, SWT.NONE); + mTopComposite.setLayoutData(new GridData(GridData.FILL_BOTH)); + mTopComposite.setLayout(new GridLayout(1, false)); + + // composite for the project selection. + Composite projectComposite = new Composite(mTopComposite, SWT.NONE); + projectComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + projectComposite.setLayout(gl = new GridLayout(3, false)); + gl.marginHeight = gl.marginWidth = 0; + + Label label = new Label(projectComposite, SWT.NONE); + label.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + label.setText("Select the project to export:"); + + new Label(projectComposite, SWT.NONE).setText("Project:"); + mProjectText = new Text(projectComposite, SWT.BORDER); + mProjectText.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + mProjectText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + handleProjectNameChange(); + } + }); + + Button browseButton = new Button(projectComposite, SWT.PUSH); + browseButton.setText("Browse..."); + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IJavaProject javaProject = mProjectChooserHelper.chooseJavaProject( + mProjectText.getText().trim()); + + if (javaProject != null) { + IProject project = javaProject.getProject(); + + // set the new name in the text field. The modify listener will take + // care of updating the status and the ExportWizard object. + mProjectText.setText(project.getName()); + } + } + }); + + setControl(mTopComposite); + } + + @Override + void onShow() { + if (mFirstOnShow) { + // get the project and init the ui + IProject project = mWizard.getProject(); + if (project != null) { + mProjectText.setText(project.getName()); + } + + mFirstOnShow = false; + } + } + + private void buildErrorUi(IProject project) { + // Show description the first time + setErrorMessage(null); + setMessage(null); + setPageComplete(true); + mHasMessage = false; + + // composite parent for the warning/error + GridLayout gl = null; + mErrorComposite = new Composite(mTopComposite, SWT.NONE); + mErrorComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + gl = new GridLayout(2, false); + gl.marginHeight = gl.marginWidth = 0; + gl.verticalSpacing *= 3; // more spacing than normal. + mErrorComposite.setLayout(gl); + + if (project == null) { + setErrorMessage("Select project to export."); + mHasMessage = true; + } else { + try { + if (project.hasNature(AndroidConstants.NATURE) == false) { + addError(mErrorComposite, "Project is not an Android project."); + } else { + // check for errors + if (ProjectHelper.hasError(project, true)) { + addError(mErrorComposite, "Project has compilation error(s)"); + } + + // check the project output + IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project); + if (outputIFolder != null) { + String outputOsPath = outputIFolder.getLocation().toOSString(); + String apkFilePath = outputOsPath + File.separator + project.getName() + + AndroidConstants.DOT_ANDROID_PACKAGE; + + File f = new File(apkFilePath); + if (f.isFile() == false) { + addError(mErrorComposite, + String.format("%1$s/%2$s/%1$s%3$s does not exists!", + project.getName(), + outputIFolder.getName(), + AndroidConstants.DOT_ANDROID_PACKAGE)); + } + } else { + addError(mErrorComposite, + "Unable to get the output folder of the project!"); + } + + + // project is an android project, we check the debuggable attribute. + AndroidManifestParser manifestParser = AndroidManifestParser.parse( + BaseProjectHelper.getJavaProject(project), null /* errorListener */, + true /* gatherData */, false /* markErrors */); + + Boolean debuggable = manifestParser.getDebuggable(); + + if (debuggable != null && debuggable == Boolean.TRUE) { + addWarning(mErrorComposite, + "The manifest 'debuggable' attribute is set to true.\nYou should set it to false for applications that you release to the public."); + } + + // check for mapview stuff + } + } catch (CoreException e) { + // unable to access nature + addError(mErrorComposite, "Unable to get project nature"); + } + } + + if (mHasMessage == false) { + Label label = new Label(mErrorComposite, SWT.NONE); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 2; + label.setLayoutData(gd); + label.setText("No errors found. Click Next."); + } + + mTopComposite.layout(); + } + + /** + * Adds an error label to a {@link Composite} object. + * @param parent the Composite parent. + * @param message the error message. + */ + private void addError(Composite parent, String message) { + if (mError == null) { + mError = AdtPlugin.getImageLoader().loadImage(IMG_ERROR, mDisplay); + } + + new Label(parent, SWT.NONE).setImage(mError); + Label label = new Label(parent, SWT.NONE); + label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + label.setText(message); + + setErrorMessage("Application cannot be exported due to the error(s) below."); + setPageComplete(false); + mHasMessage = true; + } + + /** + * Adds a warning label to a {@link Composite} object. + * @param parent the Composite parent. + * @param message the warning message. + */ + private void addWarning(Composite parent, String message) { + if (mWarning == null) { + mWarning = AdtPlugin.getImageLoader().loadImage(IMG_WARNING, mDisplay); + } + + new Label(parent, SWT.NONE).setImage(mWarning); + Label label = new Label(parent, SWT.NONE); + label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + label.setText(message); + + mHasMessage = true; + } + + /** + * Checks the parameters for correctness, and update the error message and buttons. + * @return the current IProject of this launch config. + */ + private void handleProjectNameChange() { + setPageComplete(false); + + if (mErrorComposite != null) { + mErrorComposite.dispose(); + mErrorComposite = null; + } + + // update the wizard with the new project + mWizard.setProject(null, null, null); + + //test the project name first! + String text = mProjectText.getText().trim(); + if (text.length() == 0) { + setErrorMessage("Select project to export."); + } else if (text.matches("[a-zA-Z0-9_ \\.-]+") == false) { + setErrorMessage("Project name contains unsupported characters!"); + } else { + IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null); + IProject found = null; + for (IJavaProject javaProject : projects) { + if (javaProject.getProject().getName().equals(text)) { + found = javaProject.getProject(); + break; + } + + } + + if (found != null) { + setErrorMessage(null); + + // update the wizard with the new project + setApkFilePathInWizard(found); + + // now rebuild the error ui. + buildErrorUi(found); + } else { + setErrorMessage(String.format("There is no android project named '%1$s'", + text)); + } + } + } + + private void setApkFilePathInWizard(IProject project) { + if (project != null) { + IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project); + if (outputIFolder != null) { + String outputOsPath = outputIFolder.getLocation().toOSString(); + String apkFilePath = outputOsPath + File.separator + project.getName() + + AndroidConstants.DOT_ANDROID_PACKAGE; + + File f = new File(apkFilePath); + if (f.isFile()) { + mWizard.setProject(project, apkFilePath, f.getName()); + return; + } + } + } + + mWizard.setProject(null, null, null); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/SigningExportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/SigningExportPage.java deleted file mode 100644 index 5e7ed0f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/SigningExportPage.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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.project.export; - -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; - -import org.eclipse.core.resources.IProject; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -import java.io.File; - -/** - * Second export wizard page. - */ -public class SigningExportPage extends ExportWizardPage { - - private final ExportWizard mWizard; - private Text mKeystore; - private Text mAlias; - private Text mKeystorePassword; - private Text mKeyPassword; - private boolean mDisableOnChange = false; - - protected SigningExportPage(ExportWizard wizard, String pageName) { - super(pageName); - mWizard = wizard; - - setTitle("Application Signing"); - setDescription("Defines which store, key and certificate to use to sign the Android Application."); - } - - public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NULL); - composite.setLayoutData(new GridData(GridData.FILL_BOTH)); - GridLayout gl = new GridLayout(3, false); - composite.setLayout(gl); - - GridData gd; - - new Label(composite, SWT.NONE).setText("Keystore:"); - mKeystore = new Text(composite, SWT.BORDER); - mKeystore.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - final Button browseButton = new Button(composite, SWT.PUSH); - browseButton.setText("Browse..."); - browseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.OPEN); - fileDialog.setText("Load Keystore"); - - String fileName = fileDialog.open(); - if (fileName != null) { - mKeystore.setText(fileName); - } - } - }); - - new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); - gd.horizontalSpan = 2; - gd.heightHint = 0; - new Button(composite, SWT.PUSH).setText("New..."); - - new Label(composite, SWT.NONE).setText("Key Alias:"); - mAlias = new Text(composite, SWT.BORDER); - mAlias.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - - new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - gd.horizontalSpan = 3; - - new Label(composite, SWT.NONE).setText("Store password:"); - mKeystorePassword = new Text(composite, SWT.BORDER | SWT.PASSWORD); - mKeystorePassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - new Composite(composite, SWT.NONE).setLayoutData(gd = new GridData()); - gd.heightHint = gd.widthHint = 0; - - new Label(composite, SWT.NONE).setText("Key password:"); - mKeyPassword = new Text(composite, SWT.BORDER | SWT.PASSWORD); - mKeyPassword.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - - // Show description the first time - setErrorMessage(null); - setMessage(null); - setControl(composite); - - mKeystore.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - mWizard.setKeystore(mKeystore.getText().trim()); - onChange(); - } - }); - mAlias.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - mWizard.setKeyAlias(mAlias.getText().trim()); - onChange(); - } - }); - mKeystorePassword.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - mWizard.setKeystorePassword(mKeystorePassword.getText().trim().toCharArray()); - onChange(); - } - }); - mKeyPassword.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - mWizard.setKeyPassword(mKeyPassword.getText().trim().toCharArray()); - onChange(); - } - }); - } - - @Override - void onShow() { - // fill the texts with information loaded from the project. - if (mNewProjectReference) { - // reset the keystore/alias from the content of the project - IProject project = mWizard.getProject(); - - // disable onChange for now. we'll call it once at the end. - mDisableOnChange = true; - - String keystore = ProjectHelper.loadStringProperty(project, - ExportWizard.PROPERTY_KEYSTORE); - if (keystore != null) { - mKeystore.setText(keystore); - } - - String alias = ProjectHelper.loadStringProperty(project, ExportWizard.PROPERTY_ALIAS); - if (alias != null) { - mAlias.setText(alias); - } - - // reset the passwords - mKeystorePassword.setText(""); //$NON-NLS-1$ - mKeyPassword.setText(""); //$NON-NLS-1$ - - // enable onChange, and call it to display errors and enable/disable pageCompleted. - mDisableOnChange = false; - onChange(); - } - } - - /** - * Handles changes and update the error message and calls {@link #setPageComplete(boolean)}. - */ - private void onChange() { - if (mDisableOnChange) { - return; - } - - setErrorMessage(null); - setMessage(null); - - // checks the keystore path is non null. - String keystore = mKeystore.getText().trim(); - if (keystore.length() == 0) { - setErrorMessage("Enter path to keystore."); - setPageComplete(false); - return; - } else { - File f = new File(keystore); - if (f.exists() == false) { - setErrorMessage("Keystore does not exists!"); - setPageComplete(false); - return; - } else if (f.isDirectory()) { - setErrorMessage("Keystore is a directory!"); - setPageComplete(false); - return; - } - } - - if (mAlias.getText().trim().length() == 0) { - setErrorMessage("Enter key alias."); - setPageComplete(false); - return; - } - - if (mKeystorePassword.getText().trim().length() == 0) { - setErrorMessage("Enter keystore password."); - setPageComplete(false); - return; - } - - if (mKeyPassword.getText().trim().length() == 0) { - setErrorMessage("Enter key password."); - setPageComplete(false); - return; - } - - setPageComplete(true); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainer.java index d7b290b..945fe52 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainer.java @@ -27,16 +27,19 @@ class AndroidClasspathContainer implements IClasspathContainer { private IClasspathEntry[] mClasspathEntry; private IPath mContainerPath; + private String mName; /** * Constructs the container with the {@link IClasspathEntry} representing the android * framework jar file and the container id * @param entry the entry representing the android framework. * @param path the path containing the classpath container id. + * @param name the name of the container to display. */ - AndroidClasspathContainer(IClasspathEntry entry, IPath path) { + AndroidClasspathContainer(IClasspathEntry entry, IPath path, String name) { mClasspathEntry = new IClasspathEntry[] { entry }; mContainerPath = path; + mName = name; } public IClasspathEntry[] getClasspathEntries() { @@ -44,7 +47,7 @@ class AndroidClasspathContainer implements IClasspathContainer { } public String getDescription() { - return "Android Library"; + return mName; } public int getKind() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java index 5dca350..2cafa01 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/AndroidClasspathContainerInitializer.java @@ -16,10 +16,16 @@ package com.android.ide.eclipse.adt.project.internal; +import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.sdklib.IAndroidTarget; +import org.eclipse.core.resources.IMarker; 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.NullProgressMonitor; @@ -38,10 +44,6 @@ import org.eclipse.jdt.core.JavaModelException; * {@link IProject}s. This removes the hard-coded path to the android.jar. */ public class AndroidClasspathContainerInitializer extends ClasspathContainerInitializer { - /** The old container id */ - private final static String OLD_CONTAINER_ID = - "com.android.ide.eclipse.adt.project.AndroidClasspathContainerInitializer"; //$NON-NLS-1$ - /** The container id for the android framework jar file */ private final static String CONTAINER_ID = "com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"; //$NON-NLS-1$ @@ -58,17 +60,10 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit */ @Override public void initialize(IPath containerPath, IJavaProject project) throws CoreException { - String id = null; - if (OLD_CONTAINER_ID.equals(containerPath.toString())) { - id = OLD_CONTAINER_ID; - } else if (CONTAINER_ID.equals(containerPath.toString())) { - id = CONTAINER_ID; - } - - if (id != null) { - JavaCore.setClasspathContainer(new Path(id), + if (CONTAINER_ID.equals(containerPath.toString())) { + JavaCore.setClasspathContainer(new Path(CONTAINER_ID), new IJavaProject[] { project }, - new IClasspathContainer[] { allocateAndroidContainer(id) }, + new IClasspathContainer[] { allocateAndroidContainer(CONTAINER_ID, project) }, new NullProgressMonitor()); } } @@ -82,15 +77,6 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit } /** - * Checks the {@link IPath} objects against the old android framework container id and - * returns true if they are identical. - * @param path the IPath to check. - */ - public static boolean checkOldPath(IPath path) { - return OLD_CONTAINER_ID.equals(path.toString()); - } - - /** * Checks the {@link IPath} objects against the android framework container id and * returns true if they are identical. * @param path the IPath to check. @@ -106,41 +92,18 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit * @return true if success, false otherwise. */ public static boolean updateProjects(IJavaProject[] androidProjects) { - try { - // because those projects could have the old id, we are going to fix - // them dynamically here. - for (IJavaProject javaProject: androidProjects) { - IClasspathEntry[] entries = javaProject.getRawClasspath(); - - int containerIndex = ProjectHelper.findClasspathEntryByPath(entries, - OLD_CONTAINER_ID, - IClasspathEntry.CPE_CONTAINER); - if (containerIndex != -1) { - // the project has the old container, we remove it - entries = ProjectHelper.removeEntryFromClasspath(entries, containerIndex); - - // we add the new one instead - entries = ProjectHelper.addEntryToClasspath(entries, getContainerEntry()); - - // and give the new entries to the project - javaProject.setRawClasspath(entries, new NullProgressMonitor()); - } - } - // Allocate a new AndroidClasspathContainer, and associate it to the android framework // container id for each projects. // By providing a new association between a container id and a IClasspathContainer, // this forces the JDT to query the IClasspathContainer for new IClasspathEntry (with // IClasspathContainer#getClasspathEntries()), and therefore force recompilation of // the projects. - // TODO: We could only do that for the projects haven't fixed above - // (this isn't something that will happen a lot though) int projectCount = androidProjects.length; IClasspathContainer[] containers = new IClasspathContainer[projectCount]; for (int i = 0 ; i < projectCount; i++) { - containers[i] = allocateAndroidContainer(CONTAINER_ID); + containers[i] = allocateAndroidContainer(CONTAINER_ID, androidProjects[i]); } // give each project their new container in one call. @@ -158,23 +121,123 @@ public class AndroidClasspathContainerInitializer extends ClasspathContainerInit * Allocates and returns an {@link AndroidClasspathContainer} object with the proper * path to the framework jar file. * @param containerId the container id to be used. + * @param javaProject The java project that will receive the container. */ - private static IClasspathContainer allocateAndroidContainer(String containerId) { - return new AndroidClasspathContainer(createFrameworkClasspath(), new Path(containerId)); + private static IClasspathContainer allocateAndroidContainer(String containerId, + IJavaProject javaProject) { + IProject iProject = javaProject.getProject(); + + // remove potential MARKER_TARGETs. + try { + if (iProject.exists()) { + iProject.deleteMarkers(AdtConstants.MARKER_TARGET, true, + IResource.DEPTH_INFINITE); + } + } catch (CoreException ce) { + // just log the error + AdtPlugin.log(ce, "Error removing target marker."); + } + + + // first we check if the SDK has been loaded + boolean sdkIsLoaded = AdtPlugin.getDefault().getSdkLoadStatus(javaProject) == + LoadStatus.LOADED; + + // then we check if the project has a valid target. + IAndroidTarget target = null; + if (sdkIsLoaded) { + target = Sdk.getCurrent().getTarget(iProject); + } + + // if we are loaded and the target is non null, we create a valid ClassPathContainer + if (sdkIsLoaded && target != null) { + String targetName = null; + if (target.isPlatform()) { + targetName = target.getName(); + } else { + targetName = String.format("%1$s (%2$s)", target.getName(), + target.getApiVersionName()); + } + + return new AndroidClasspathContainer(createFrameworkClasspath(target), + new Path(containerId), targetName); + } + + // else we put a marker on the project, and return a dummy container (to replace the + // previous one if there was one.) + + // Get the project's target's hash string (if it exists) + String hashString = Sdk.getProjectTargetHashString(iProject); + + String message = null; + boolean outputToConsole = true; + if (hashString == null || hashString.length() == 0) { + message = String.format( + "Project has no target set. Edit the project properties to set one."); + } else if (sdkIsLoaded) { + message = String.format( + "Unable to resolve target '%s'", hashString); + } else { + // this is the case where there is a hashString but the SDK is not yet + // loaded and therefore we can't get the target yet. + message = String.format( + "Unable to resolve target '%s' until the SDK is loaded.", hashString); + + // let's not log this one to the console as it will happen at every boot, + // and it's expected. (we do keep the error marker though). + outputToConsole = false; + } + + // log the error and put the marker on the project + if (outputToConsole) { + AdtPlugin.printBuildToConsole(AdtConstants.BUILD_ALWAYS, iProject, message); + } + IMarker marker = BaseProjectHelper.addMarker(iProject, AdtConstants.MARKER_TARGET, message, + IMarker.SEVERITY_ERROR); + + // add a marker priority as this is an more important error than the error that will + // spring from the lack of library + try { + marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH); + } catch (CoreException e) { + // just log the error + AdtPlugin.log(e, "Error changing target marker priority."); + } + + // return a dummy container to replace the one we may have had before. + return new IClasspathContainer() { + public IClasspathEntry[] getClasspathEntries() { + return new IClasspathEntry[0]; + } + + public String getDescription() { + return "Unable to get system library for the project"; + } + + public int getKind() { + return IClasspathContainer.K_DEFAULT_SYSTEM; + } + + public IPath getPath() { + return null; + } + }; } /** - * Creates and returns a new {@link IClasspathEntry} object for the android framework. - *

          This references the OS path to the android.jar and the java doc directory. This is - * dynamically created when a project is opened, and never saved in the project itself, so - * there's no risk of storing an obsolete path. + * Creates and returns a new {@link IClasspathEntry} object for the android + * framework.

          This references the OS path to the android.jar and the + * java doc directory. This is dynamically created when a project is opened, + * and never saved in the project itself, so there's no risk of storing an + * obsolete path. + * + * @param target The target that contains the libraries. */ - private static IClasspathEntry createFrameworkClasspath() { + private static IClasspathEntry createFrameworkClasspath(IAndroidTarget target) { // now add the android framework to the class path. // create the path object. - IPath android_lib = new Path(AdtPlugin.getOsAbsoluteFramework()); - - IPath android_src = new Path(AdtPlugin.getOsAbsoluteAndroidSources()); + IPath android_lib = new Path(target.getPath(IAndroidTarget.ANDROID_JAR)); + IPath android_src = new Path(target.getPath(IAndroidTarget.SOURCES)); // create the java doc link. IClasspathAttribute cpAttribute = JavaCore.newClasspathAttribute( diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectCreationPage.java deleted file mode 100644 index d395905..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectCreationPage.java +++ /dev/null @@ -1,1060 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -/* - * References: - * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard - * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage - */ - -package com.android.ide.eclipse.adt.project.internal; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; - -import org.eclipse.core.filesystem.URIUtil; -import org.eclipse.core.resources.IProject; -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.eclipse.core.runtime.Path; -import org.eclipse.core.runtime.Platform; -import org.eclipse.jdt.core.JavaConventions; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.osgi.util.TextProcessor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -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.DirectoryDialog; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Text; - -import java.io.File; -import java.io.FileFilter; -import java.net.URI; -import java.util.regex.Pattern; - -/** - * NewAndroidProjectCreationPage is a project creation page that provides the - * following fields: - *

            - *
          • Package name - *
          • Activity name - *
          • Location of the SDK - *
          - * Note: this class is public so that it can be accessed from unit tests. - * It is however an internal class. Its API may change without notice. - * It should semantically be considered as a private final class. - * Do not derive from this class. - */ -public class NewProjectCreationPage extends WizardPage { - - // constants - /** Initial value for all name fields (project, activity, application, package). Used - * whenever a value is requested before controls are created. */ - private static final String INITIAL_NAME = ""; //$NON-NLS-1$ - /** Initial value for the Create New Project radio; False means Create From Existing would be - * the default.*/ - private static final boolean INITIAL_CREATE_NEW_PROJECT = true; - /** Initial value for the Use Default Location check box. */ - private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; - /** Initial value for the Create Activity check box. */ - private static final boolean INITIAL_CREATE_ACTIVITY = true; - - - /** Pattern for characters accepted in a project name. Since this will be used as a - * directory name, we're being a bit conservative on purpose. It cannot start with a space. */ - private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$ - /** Last user-browsed location, static so that it be remembered for the whole session */ - private static String sCustomLocationOsPath = ""; //$NON-NLS-1$ - - private final int MSG_NONE = 0; - private final int MSG_WARNING = 1; - private final int MSG_ERROR = 2; - - private String mUserPackageName = ""; //$NON-NLS-1$ - private String mUserActivityName = ""; //$NON-NLS-1$ - private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY; - private String mSourceFolder = ""; //$NON-NLS-1$ - - // widgets - private Text mProjectNameField; - private Text mPackageNameField; - private Text mActivityNameField; - private Text mApplicationNameField; - private Button mCreateNewProjectRadio; - private Button mUseDefaultLocation; - private Label mLocationLabel; - private Text mLocationPathField; - private Button mBrowseButton; - private Button mCreateActivityCheck; - - private boolean mInternalLocationPathUpdate; - protected boolean mInternalProjectNameUpdate; - protected boolean mInternalApplicationNameUpdate; - private boolean mInternalCreateActivityUpdate; - private boolean mInternalActivityNameUpdate; - protected boolean mProjectNameModifiedByUser; - protected boolean mApplicationNameModifiedByUser; - - - /** - * Creates a new project creation wizard page. - * - * @param pageName the name of this page - */ - public NewProjectCreationPage(String pageName) { - super(pageName); - setPageComplete(false); - if (sCustomLocationOsPath == null || - sCustomLocationOsPath.length() == 0 || - !new File(sCustomLocationOsPath).isDirectory()) { - sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder(); - } - } - - // --- Getters used by NewProjectWizard --- - - /** - * Returns the current project location path as entered by the user, or its - * anticipated initial value. Note that if the default has been returned the - * path in a project description used to create a project should not be set. - * - * @return the project location path or its anticipated initial value. - */ - public IPath getLocationPath() { - return new Path(getProjectLocation()); - } - - /** Returns the value of the project name field with leading and trailing spaces removed. */ - public String getProjectName() { - return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim(); - } - - /** Returns the value of the package name field with spaces trimmed. */ - public String getPackageName() { - return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim(); - } - - /** Returns the value of the activity name field with spaces trimmed. */ - public String getActivityName() { - return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim(); - } - - /** Returns the value of the application name field with spaces trimmed. */ - public String getApplicationName() { - // Return the name of the activity as default application name. - return mApplicationNameField == null ? getActivityName() - : mApplicationNameField.getText().trim(); - - } - - /** Returns the value of the "Create New Project" radio. */ - public boolean isNewProject() { - return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT - : mCreateNewProjectRadio.getSelection(); - } - - /** Returns the value of the "Create Activity" checkbox. */ - public boolean isCreateActivity() { - return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY - : mCreateActivityCheck.getSelection(); - } - - /** Returns the value of the Use Default Location field. */ - public boolean useDefaultLocation() { - return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION - : mUseDefaultLocation.getSelection(); - } - - /** Returns the internal source folder (for the "existing project" mode) or the default - * "src" constant. */ - public String getSourceFolder() { - if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) { - return AndroidConstants.FD_SOURCES; - } else { - return mSourceFolder; - } - } - - /** - * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when - * the dialog is made visible. - */ - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) { - mProjectNameField.setFocus(); - } - } - - // --- UI creation --- - - /** - * Creates the top level control for this dialog page under the given parent - * composite. - * - * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) - */ - public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NULL); - composite.setFont(parent.getFont()); - - initializeDialogUnits(parent); - - composite.setLayout(new GridLayout()); - composite.setLayoutData(new GridData(GridData.FILL_BOTH)); - - createProjectNameGroup(composite); - createLocationGroup(composite); - createPropertiesGroup(composite); - - // Update state the first time - enableLocationWidgets(); - setPageComplete(validatePage()); - // Show description the first time - setErrorMessage(null); - setMessage(null); - setControl(composite); - } - - /** - * Creates the group for the project name: - * [label: "Project Name"] [text field] - * - * @param parent the parent composite - */ - private final void createProjectNameGroup(Composite parent) { - Composite group = new Composite(parent, SWT.NONE); - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - // new project label - Label label = new Label(group, SWT.NONE); - label.setText("Project name:"); - label.setFont(parent.getFont()); - label.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); - - // new project name entry field - mProjectNameField = new Text(group, SWT.BORDER); - GridData data = new GridData(GridData.FILL_HORIZONTAL); - mProjectNameField.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); - mProjectNameField.setLayoutData(data); - mProjectNameField.setFont(parent.getFont()); - mProjectNameField.addListener(SWT.Modify, new Listener() { - public void handleEvent(Event event) { - if (!mInternalProjectNameUpdate) { - mProjectNameModifiedByUser = true; - } - updateLocationPathField(null); - } - }); - } - - - /** - * Creates the group for the Project options: - * [radio] Create new project - * [radio] Create project from existing sources - * [check] Use default location - * Location [text field] [browse button] - * - * @param parent the parent composite - */ - private final void createLocationGroup(Composite parent) { - Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); - // Layout has 4 columns of non-equal size - group.setLayout(new GridLayout()); - group.setLayoutData(new GridData(GridData.FILL_BOTH)); - group.setFont(parent.getFont()); - group.setText("Contents"); - - mCreateNewProjectRadio = new Button(group, SWT.RADIO); - mCreateNewProjectRadio.setText("Create new project in workspace"); - mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT); - Button existing_project_radio = new Button(group, SWT.RADIO); - existing_project_radio.setText("Create project from existing source"); - existing_project_radio.setSelection(!INITIAL_CREATE_NEW_PROJECT); - - mUseDefaultLocation = new Button(group, SWT.CHECK); - mUseDefaultLocation.setText("Use default location"); - mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION); - - SelectionListener location_listener = new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - enableLocationWidgets(); - extractNamesFromAndroidManifest(); - setPageComplete(validatePage()); - } - }; - - mCreateNewProjectRadio.addSelectionListener(location_listener); - existing_project_radio.addSelectionListener(location_listener); - mUseDefaultLocation.addSelectionListener(location_listener); - - Composite location_group = new Composite(group, SWT.NONE); - location_group.setLayout(new GridLayout(4, /* num columns */ - false /* columns of not equal size */)); - location_group.setLayoutData(new GridData(GridData.FILL_BOTH)); - location_group.setFont(parent.getFont()); - - mLocationLabel = new Label(location_group, SWT.NONE); - mLocationLabel.setText("Location:"); - - mLocationPathField = new Text(location_group, SWT.BORDER); - GridData data = new GridData(GridData.FILL, /* horizontal alignment */ - GridData.BEGINNING, /* vertical alignment */ - true, /* grabExcessHorizontalSpace */ - false, /* grabExcessVerticalSpace */ - 2, /* horizontalSpan */ - 1); /* verticalSpan */ - mLocationPathField.setLayoutData(data); - mLocationPathField.setFont(parent.getFont()); - mLocationPathField.addListener(SWT.Modify, new Listener() { - public void handleEvent(Event event) { - onLocationPathFieldModified(); - } - }); - - mBrowseButton = new Button(location_group, SWT.PUSH); - mBrowseButton.setText("Browse..."); - setButtonLayoutData(mBrowseButton); - mBrowseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - openDirectoryBrowser(); - } - }); - } - - /** - * Display a directory browser and update the location path field with the selected path - */ - private void openDirectoryBrowser() { - - String existing_dir = getLocationPathFieldValue(); - - // Disable the path if it doesn't exist - if (existing_dir.length() == 0) { - existing_dir = null; - } else { - File f = new File(existing_dir); - if (!f.exists()) { - existing_dir = null; - } - } - - DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell()); - dd.setMessage("Browse for folder"); - dd.setFilterPath(existing_dir); - String abs_dir = dd.open(); - - if (abs_dir != null) { - updateLocationPathField(abs_dir); - extractNamesFromAndroidManifest(); - setPageComplete(validatePage()); - } - } - - /** - * Creates the group for the project properties: - * - Package name [text field] - * - Activity name [text field] - * - Application name [text field] - * - * @param parent the parent composite - */ - private final void createPropertiesGroup(Composite parent) { - // package specification group - Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); - GridLayout layout = new GridLayout(); - layout.numColumns = 2; - group.setLayout(layout); - group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - group.setFont(parent.getFont()); - group.setText("Properties"); - - // new application label - Label label = new Label(group, SWT.NONE); - label.setText("Application name:"); - label.setFont(parent.getFont()); - label.setToolTipText("Name of the Application. This is a free string. It can be empty."); - - // new application name entry field - mApplicationNameField = new Text(group, SWT.BORDER); - GridData data = new GridData(GridData.FILL_HORIZONTAL); - mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty."); - mApplicationNameField.setLayoutData(data); - mApplicationNameField.setFont(parent.getFont()); - mApplicationNameField.addListener(SWT.Modify, new Listener() { - public void handleEvent(Event event) { - if (!mInternalApplicationNameUpdate) { - mApplicationNameModifiedByUser = true; - } - } - }); - - // new package label - label = new Label(group, SWT.NONE); - label.setText("Package name:"); - label.setFont(parent.getFont()); - label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); - - // new package name entry field - mPackageNameField = new Text(group, SWT.BORDER); - data = new GridData(GridData.FILL_HORIZONTAL); - mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); - mPackageNameField.setLayoutData(data); - mPackageNameField.setFont(parent.getFont()); - mPackageNameField.addListener(SWT.Modify, new Listener() { - public void handleEvent(Event event) { - onPackageNameFieldModified(); - } - }); - - // new activity label - mCreateActivityCheck = new Button(group, SWT.CHECK); - mCreateActivityCheck.setText("Create Activity:"); - mCreateActivityCheck.setToolTipText("Specifies if you want to create a default Activity."); - mCreateActivityCheck.setFont(parent.getFont()); - mCreateActivityCheck.setSelection(INITIAL_CREATE_ACTIVITY); - mCreateActivityCheck.addListener(SWT.Selection, new Listener() { - public void handleEvent(Event event) { - onCreateActivityCheckModified(); - enableLocationWidgets(); - } - }); - - // new activity name entry field - mActivityNameField = new Text(group, SWT.BORDER); - data = new GridData(GridData.FILL_HORIZONTAL); - mActivityNameField.setToolTipText("Name of the Activity class to create. Must be a valid Java identifier."); - mActivityNameField.setLayoutData(data); - mActivityNameField.setFont(parent.getFont()); - mActivityNameField.addListener(SWT.Modify, new Listener() { - public void handleEvent(Event event) { - onActivityNameFieldModified(); - } - }); - } - - - //--- Internal getters & setters ------------------ - - /** Returns the location path field value with spaces trimmed. */ - private String getLocationPathFieldValue() { - return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); - } - - /** Returns the current project location, depending on the Use Default Location check box. */ - public String getProjectLocation() { - if (isNewProject() && useDefaultLocation()) { - return Platform.getLocation().toString(); - } else { - return getLocationPathFieldValue(); - } - } - - /** - * Creates a project resource handle for the current project name field - * value. - *

          - * This method does not create the project resource; this is the - * responsibility of IProject::create invoked by the new - * project resource wizard. - *

          - * - * @return the new project resource handle - */ - private IProject getProjectHandle() { - return ResourcesPlugin.getWorkspace().getRoot().getProject(getProjectName()); - } - - // --- UI Callbacks ---- - - /** - * Enables or disable the location widgets depending on the user selection: - * the location path is enabled when using the "existing source" mode (i.e. not new project) - * or in new project mode with the "use default location" turned off. - */ - private void enableLocationWidgets() { - boolean is_new_project = isNewProject(); - boolean use_default = useDefaultLocation(); - boolean location_enabled = !is_new_project || !use_default; - boolean create_activity = isCreateActivity(); - - mUseDefaultLocation.setEnabled(is_new_project); - - mLocationLabel.setEnabled(location_enabled); - mLocationPathField.setEnabled(location_enabled); - mBrowseButton.setEnabled(location_enabled); - - mPackageNameField.setEnabled(is_new_project); - mCreateActivityCheck.setEnabled(is_new_project); - mActivityNameField.setEnabled(is_new_project & create_activity); - - updateLocationPathField(null); - updatePackageAndActivityFields(); - } - - /** - * Updates the location directory path field. - *
          - * When custom user selection is enabled, use the abs_dir argument if not null and also - * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the - * user selection to be remembered when the user switches from default to custom. - *
          - * When custom user selection is disabled, use the workspace default location with the - * current project name. This does not change the internally cached abs_dir. - * - * @param abs_dir A new absolute directory path or null to use the default. - */ - private void updateLocationPathField(String abs_dir) { - boolean is_new_project = isNewProject(); - boolean use_default = useDefaultLocation(); - boolean custom_location = !is_new_project || !use_default; - - if (!mInternalLocationPathUpdate) { - mInternalLocationPathUpdate = true; - if (custom_location) { - if (abs_dir != null) { - sCustomLocationOsPath = TextProcessor.process(abs_dir); - } - if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) { - mLocationPathField.setText(sCustomLocationOsPath); - } - } else { - String value = Platform.getLocation().append(getProjectName()).toString(); - value = TextProcessor.process(value); - if (!mLocationPathField.getText().equals(value)) { - mLocationPathField.setText(value); - } - } - setPageComplete(validatePage()); - mInternalLocationPathUpdate = false; - } - } - - /** - * The location path field is either modified internally (from updateLocationPathField) - * or manually by the user when the custom_location mode is not set. - * - * Ignore the internal modification. When modified by the user, memorize the choice and - * validate the page. - */ - private void onLocationPathFieldModified() { - if (!mInternalLocationPathUpdate) { - // When the updates doesn't come from updateLocationPathField, it must be the user - // editing the field manually, in which case we want to save the value internally. - sCustomLocationOsPath = getLocationPathFieldValue(); - extractNamesFromAndroidManifest(); - setPageComplete(validatePage()); - } - } - - /** - * The package name field is either modified internally (from extractNamesFromAndroidManifest) - * or manually by the user when the custom_location mode is not set. - * - * Ignore the internal modification. When modified by the user, memorize the choice and - * validate the page. - */ - private void onPackageNameFieldModified() { - if (isNewProject()) { - mUserPackageName = getPackageName(); - setPageComplete(validatePage()); - } - } - - /** - * The create activity checkbox is either modified internally (from - * extractNamesFromAndroidManifest) or manually by the user. - * - * Ignore the internal modification. When modified by the user, memorize the choice and - * validate the page. - */ - private void onCreateActivityCheckModified() { - if (isNewProject() && !mInternalCreateActivityUpdate) { - mUserCreateActivityCheck = isCreateActivity(); - } - setPageComplete(validatePage()); - } - - /** - * The activity name field is either modified internally (from extractNamesFromAndroidManifest) - * or manually by the user when the custom_location mode is not set. - * - * Ignore the internal modification. When modified by the user, memorize the choice and - * validate the page. - */ - private void onActivityNameFieldModified() { - if (isNewProject() && !mInternalActivityNameUpdate) { - mUserActivityName = getActivityName(); - setPageComplete(validatePage()); - } - } - - /** - * Called when the radio buttons are changed between the "create new project" and the - * "use existing source" mode. This reverts the fields to whatever the user manually - * entered before. - */ - private void updatePackageAndActivityFields() { - if (isNewProject()) { - if (mUserPackageName.length() > 0 && - !mPackageNameField.getText().equals(mUserPackageName)) { - mPackageNameField.setText(mUserPackageName); - } - - if (mUserActivityName.length() > 0 && - !mActivityNameField.getText().equals(mUserActivityName)) { - mInternalActivityNameUpdate = true; - mActivityNameField.setText(mUserActivityName); - mInternalActivityNameUpdate = false; - } - - if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) { - mInternalCreateActivityUpdate = true; - mCreateActivityCheck.setSelection(mUserCreateActivityCheck); - mInternalCreateActivityUpdate = false; - } - } - } - - /** - * Extract names from an android manifest. - * This is done only if the user selected the "use existing source" and a manifest xml file - * can actually be found in the custom user directory. - */ - private void extractNamesFromAndroidManifest() { - if (!isNewProject()) { - File f = new File(getProjectLocation()); - if (f.isDirectory()) { - Path path = new Path(f.getPath()); - String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); - if (manifest.exists()) { - String packageName = null; - String activityName = null; - try { - packageName = manifest.getPackageName(); - activityName = manifest.getActivityName(1); - } catch (Exception e) { - // pass - } - - - if (packageName != null && packageName.length() > 0) { - mPackageNameField.setText(packageName); - } - - if (activityName != null && activityName.length() > 0) { - mInternalActivityNameUpdate = true; - mInternalCreateActivityUpdate = true; - mActivityNameField.setText(activityName); - mCreateActivityCheck.setSelection(true); - mInternalCreateActivityUpdate = false; - mInternalActivityNameUpdate = false; - - // If project name and application names are empty, use the activity - // name as a default. If the activity name has dots, it's a part of a - // package specification and only the last identifier must be used. - if (activityName.indexOf('.') != -1) { - String[] ids = activityName.split(AndroidConstants.RE_DOT); - activityName = ids[ids.length - 1]; - } - if (mProjectNameField.getText().length() == 0 || - !mProjectNameModifiedByUser) { - mInternalProjectNameUpdate = true; - mProjectNameField.setText(activityName); - mInternalProjectNameUpdate = false; - } - if (mApplicationNameField.getText().length() == 0 || - !mApplicationNameModifiedByUser) { - mInternalApplicationNameUpdate = true; - mApplicationNameField.setText(activityName); - mInternalApplicationNameUpdate = false; - } - } else { - mInternalActivityNameUpdate = true; - mInternalCreateActivityUpdate = true; - mActivityNameField.setText(""); - mCreateActivityCheck.setSelection(false); - mInternalCreateActivityUpdate = false; - mInternalActivityNameUpdate = false; - - // There is no activity name to use to fill in the project and application - // name. However if there's a package name, we can use this as a base. - if (packageName != null && packageName.length() > 0) { - // Package name is a java identifier, so it's most suitable for - // an application name. - - if (mApplicationNameField.getText().length() == 0 || - !mApplicationNameModifiedByUser) { - mInternalApplicationNameUpdate = true; - mApplicationNameField.setText(packageName); - mInternalApplicationNameUpdate = false; - } - - // For the project name, remove any dots - packageName = packageName.replace('.', '_'); - if (mProjectNameField.getText().length() == 0 || - !mProjectNameModifiedByUser) { - mInternalProjectNameUpdate = true; - mProjectNameField.setText(packageName); - mInternalProjectNameUpdate = false; - } - - } - } - } - } - } - } - - /** - * Returns whether this page's controls currently all contain valid values. - * - * @return true if all controls are valid, and - * false if at least one is invalid - */ - protected boolean validatePage() { - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - - int status = validateProjectField(workspace); - if ((status & MSG_ERROR) == 0) { - status |= validateLocationPath(workspace); - } - if ((status & MSG_ERROR) == 0) { - status |= validatePackageField(); - } - if ((status & MSG_ERROR) == 0) { - status |= validateActivityField(); - } - if ((status & MSG_ERROR) == 0) { - status |= validateSourceFolder(); - } - if (status == MSG_NONE) { - setStatus(null, MSG_NONE); - } - - // Return false if there's an error so that the finish button be disabled. - return (status & MSG_ERROR) == 0; - } - - /** - * Validates the project name field. - * - * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. - */ - private int validateProjectField(IWorkspace workspace) { - // Validate project field - String projectFieldContents = getProjectName(); - if (projectFieldContents.length() == 0) { - return setStatus("Project name must be specified", MSG_ERROR); - } - - // Limit the project name to shell-agnostic characters since it will be used to - // generate the final package - if (!sProjectNamePattern.matcher(projectFieldContents).matches()) { - return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.", - MSG_ERROR); - } - - IStatus nameStatus = workspace.validateName(projectFieldContents, IResource.PROJECT); - if (!nameStatus.isOK()) { - return setStatus(nameStatus.getMessage(), MSG_ERROR); - } - - if (getProjectHandle().exists()) { - return setStatus("A project with that name already exists in the workspace", - MSG_ERROR); - } - - return MSG_NONE; - } - - /** - * Validates the location path field. - * - * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. - */ - private int validateLocationPath(IWorkspace workspace) { - Path path = new Path(getProjectLocation()); - if (isNewProject()) { - if (!useDefaultLocation()) { - // If not using the default value validate the location. - URI uri = URIUtil.toURI(path.toOSString()); - IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(), - uri); - if (!locationStatus.isOK()) { - return setStatus(locationStatus.getMessage(), MSG_ERROR); - } else { - // The location is valid as far as Eclipse is concerned (i.e. mostly not - // an existing workspace project.) Check it either doesn't exist or is - // a directory that is empty. - File f = path.toFile(); - if (f.exists() && !f.isDirectory()) { - return setStatus("A directory name must be specified.", MSG_ERROR); - } else if (f.isDirectory()) { - // However if the directory exists, we should put a warning if it is not - // empty. We don't put an error (we'll ask the user again for confirmation - // before using the directory.) - String[] l = f.list(); - if (l.length != 0) { - return setStatus("The selected output directory is not empty.", - MSG_WARNING); - } - } - } - } else { - // Otherwise validate the path string is not empty - if (getProjectLocation().length() == 0) { - return setStatus("A directory name must be specified.", MSG_ERROR); - } - - File dest = path.append(getProjectName()).toFile(); - if (dest.exists()) { - return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.", - getProjectName()), MSG_ERROR); - } - } - } else { - // Must be an existing directory - File f = path.toFile(); - if (!f.isDirectory()) { - return setStatus("An existing directory name must be specified.", MSG_ERROR); - } - - // Check there's an android manifest in the directory - String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); - AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); - if (!manifest.exists()) { - return setStatus( - String.format("File %1$s not found in %2$s.", - AndroidConstants.FN_ANDROID_MANIFEST, f.getName()), - MSG_ERROR); - } - - // Parse it and check the important fields. - String packageName = manifest.getPackageName(); - if (packageName == null || packageName.length() == 0) { - return setStatus( - String.format("No package name defined in %1$s.", osPath), - MSG_ERROR); - } - - String activityName = manifest.getActivityName(1); - if (activityName == null || activityName.length() == 0) { - // This is acceptable now as long as no activity needs to be created - if (isCreateActivity()) { - return setStatus( - String.format("No activity name defined in %1$s.", osPath), - MSG_ERROR); - } - } - - // If there's already a .project, tell the user to use import instead. - if (path.append(".project").toFile().exists()) { //$NON-NLS-1$ - return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.", - MSG_WARNING); - } - } - - return MSG_NONE; - } - - /** - * Validates the activity name field. - * - * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. - */ - private int validateActivityField() { - // Disregard if not creating an activity - if (!isCreateActivity()) { - return MSG_NONE; - } - - // Validate activity field - String activityFieldContents = getActivityName(); - if (activityFieldContents.length() == 0) { - return setStatus("Activity name must be specified.", MSG_ERROR); - } - - // The activity field can actually contain part of a sub-package name - // or it can start with a dot "." to indicates it comes from the parent package name. - String packageName = ""; - int pos = activityFieldContents.lastIndexOf('.'); - if (pos >= 0) { - packageName = activityFieldContents.substring(0, pos); - if (packageName.startsWith(".")) { //$NON-NLS-1$ - packageName = packageName.substring(1); - } - - activityFieldContents = activityFieldContents.substring(pos + 1); - } - - // the activity field can contain a simple java identifier, or a - // package name or one that starts with a dot. So if it starts with a dot, - // ignore this dot -- the rest must look like a package name. - if (activityFieldContents.charAt(0) == '.') { - activityFieldContents = activityFieldContents.substring(1); - } - - // Check it's a valid activity string - int result = MSG_NONE; - IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents, - "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ - if (!status.isOK()) { - result = setStatus(status.getMessage(), - status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); - } - - // Check it's a valid package string - if (result != MSG_ERROR && packageName.length() > 0) { - status = JavaConventions.validatePackageName(packageName, - "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ - if (!status.isOK()) { - result = setStatus(status.getMessage() + " (in the activity name)", - status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); - } - } - - - return result; - } - - /** - * Validates the package name field. - * - * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. - */ - private int validatePackageField() { - // Validate package field - String packageFieldContents = getPackageName(); - if (packageFieldContents.length() == 0) { - return setStatus("Package name must be specified.", MSG_ERROR); - } - - // Check it's a valid package string - int result = MSG_NONE; - IStatus status = JavaConventions.validatePackageName(packageFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ - if (!status.isOK()) { - result = setStatus(status.getMessage(), - status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); - } - - // The Android Activity Manager does not accept packages names with only one - // identifier. Check the package name has at least one dot in them (the previous rule - // validated that if such a dot exist, it's not the first nor last characters of the - // string.) - if (result != MSG_ERROR && packageFieldContents.indexOf('.') == -1) { - return setStatus("Package name must have at least two identifiers.", MSG_ERROR); - } - - return result; - } - - /** - * Validates that an existing project actually has a source folder. - * - * For project in "use existing source" mode, this tries to find the source folder. - * A source folder should be just under the project directory and it should have all - * the directories composing the package+activity name. - * - * As a side effect, it memorizes the source folder in mSourceFolder. - * - * TODO: support multiple source folders for multiple activities. - * - * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. - */ - private int validateSourceFolder() { - // This check does nothing when creating a new project. - // This check is also useless when no activity is present or created. - if (isNewProject() || !isCreateActivity()) { - return MSG_NONE; - } - - String osTarget = getActivityName(); - - if (osTarget.indexOf('.') == -1) { - osTarget = getPackageName() + File.separator + osTarget; - } else if (osTarget.indexOf('.') == 0) { - osTarget = getPackageName() + osTarget; - } - osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA; - - String projectPath = getProjectLocation(); - File projectDir = new File(projectPath); - File[] all_dirs = projectDir.listFiles(new FileFilter() { - public boolean accept(File pathname) { - return pathname.isDirectory(); - } - }); - for (File f : all_dirs) { - Path path = new Path(f.getAbsolutePath()); - File java_activity = path.append(osTarget).toFile(); - if (java_activity.isFile()) { - mSourceFolder = f.getName(); - return MSG_NONE; - } - } - - if (all_dirs.length > 0) { - return setStatus( - String.format("%1$s can not be found under %2$s.", osTarget, projectPath), - MSG_ERROR); - } else { - return setStatus( - String.format("No source folders can be found in %1$s.", projectPath), - MSG_ERROR); - } - } - - /** - * Sets the error message for the wizard with the given message icon. - * - * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING. - * @return As a convenience, always returns messageType so that the caller can return - * immediately. - */ - private int setStatus(String message, int messageType) { - if (message == null) { - setErrorMessage(null); - setMessage(null); - } else if (!message.equals(getMessage())) { - setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR); - } - return messageType; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectWizard.java deleted file mode 100644 index a67f5ed..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/internal/NewProjectWizard.java +++ /dev/null @@ -1,706 +0,0 @@ -/* - * Copyright (C) 2007 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.project.internal; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.adt.project.AndroidNature; -import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceStatus; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; -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.core.runtime.Platform; -import org.eclipse.core.runtime.SubProgressMonitor; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction; -import org.eclipse.jface.dialogs.ErrorDialog; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.ui.INewWizard; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.actions.WorkspaceModifyOperation; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -/** - * A "New Android Project" Wizard. - *

          - * Note: this class is public so that it can be accessed from unit tests. - * It is however an internal class. Its API may change without notice. - * It should semantically be considered as a private final class. - * Do not derive from this class. - - */ -public class NewProjectWizard extends Wizard implements INewWizard { - - private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$ - private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$ - private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$ - private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$ - private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$ - private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$ - private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ - private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$ - private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$ - - private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$ - private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$ - private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$ - - private static final String BIN_DIRECTORY = - AndroidConstants.FD_BINARIES + AndroidConstants.WS_SEP; - private static final String RES_DIRECTORY = - AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; - private static final String ASSETS_DIRECTORY = - AndroidConstants.FD_ASSETS + AndroidConstants.WS_SEP; - private static final String DRAWABLE_DIRECTORY = - AndroidConstants.FD_DRAWABLE + AndroidConstants.WS_SEP; - private static final String LAYOUT_DIRECTORY = - AndroidConstants.FD_LAYOUT + AndroidConstants.WS_SEP; - private static final String VALUES_DIRECTORY = - AndroidConstants.FD_VALUES + AndroidConstants.WS_SEP; - - private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$ - private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY - + "AndroidManifest.template"; //$NON-NLS-1$ - private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY - + "activity.template"; //$NON-NLS-1$ - private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY - + "launcher_intent_filter.template"; //$NON-NLS-1$ - - private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY - + "strings.template"; //$NON-NLS-1$ - private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY - + "string.template"; //$NON-NLS-1$ - private static final String ICON = "icon.png"; //$NON-NLS-1$ - - private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$ - - private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$ - private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$ - private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$ - - private static final String[] DEFAULT_DIRECTORIES = new String[] { - BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY }; - private static final String[] RES_DIRECTORIES = new String[] { - DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY}; - - private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ - private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$ - private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$ - private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$ - - protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$ - - private NewProjectCreationPage mMainPage; - - /** - * Initializes this creation wizard using the passed workbench and object - * selection. Inherited from org.eclipse.ui.IWorkbenchWizard - */ - public void init(IWorkbench workbench, IStructuredSelection selection) { - setHelpAvailable(false); // TODO have help - setWindowTitle("New Android Project"); - setImageDescriptor(); - - mMainPage = createMainPage(); - mMainPage.setTitle("New Android Project"); - mMainPage.setDescription("Creates a new Android Project resource."); - } - - /** - * Creates the wizard page. - *

          - * Please do NOT override this method. - *

          - * This is protected so that it can be overridden by unit tests. - * However the contract of this class is private and NO ATTEMPT will be made - * to maintain compatibility between different versions of the plugin. - */ - protected NewProjectCreationPage createMainPage() { - return new NewProjectCreationPage(MAIN_PAGE_NAME); - } - - // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- - // The Wizard class implements most defaults and boilerplate code needed by - // IWizard - - /** - * Adds pages to this wizard. - */ - @Override - public void addPages() { - addPage(mMainPage); - } - - /** - * Performs any actions appropriate in response to the user having pressed - * the Finish button, or refuse if finishing now is not permitted: here, it - * actually creates the workspace project and then switch to the Java - * perspective. - * - * @return True - */ - @Override - public boolean performFinish() { - if (!createAndroidProject()) { - return false; - } - - // Open the default Java Perspective - OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction(); - action.run(); - return true; - } - - // -- Custom Methods -- - - /** - * Before actually creating the project for a new project (as opposed to using an - * existing project), we check if the target location is a directory that either does - * not exist or is empty. - * - * If it's not empty, ask the user for confirmation. - * - * @param destination The destination folder where the new project is to be created. - * @return True if the destination doesn't exist yet or is an empty directory or is - * accepted by the user. - */ - private boolean validateNewProjectLocationIsEmpty(IPath destination) { - File f = new File(destination.toOSString()); - if (f.isDirectory() && f.list().length > 0) { - return AdtPlugin.displayPrompt("New Android Project", - "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?"); - } - return true; - } - - /** - * Creates the android project. - * @return True if the project could be created. - */ - private boolean createAndroidProject() { - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName()); - final IProjectDescription description = workspace.newProjectDescription(project.getName()); - - final Map parameters = new HashMap(); - parameters.put(PARAM_PROJECT, mMainPage.getProjectName()); - parameters.put(PARAM_PACKAGE, mMainPage.getPackageName()); - parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); - parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); - parameters.put(PARAM_IS_NEW_PROJECT, Boolean.toString(mMainPage.isNewProject())); - parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder()); - - if (mMainPage.isCreateActivity()) { - // An activity name can be of the form ".package.Class" or ".Class". - // The initial dot is ignored, as it is always added later in the templates. - String activityName = mMainPage.getActivityName(); - if (activityName.startsWith(".")) { //$NON-NLS-1$ - activityName = activityName.substring(1); - } - parameters.put(PARAM_ACTIVITY, activityName); - } - - // create a dictionary of string that will contain name+content. - // we'll put all the strings into values/strings.xml - final HashMap stringDictionary = new HashMap(); - stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName()); - - IPath path = mMainPage.getLocationPath(); - IPath defaultLocation = Platform.getLocation(); - if (!path.equals(defaultLocation)) { - description.setLocation(path); - } - - if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() && - !validateNewProjectLocationIsEmpty(path)) { - return false; - } - - // Create a monitored operation to create the actual project - WorkspaceModifyOperation op = new WorkspaceModifyOperation() { - @Override - protected void execute(IProgressMonitor monitor) throws InvocationTargetException { - createProjectAsync(project, description, monitor, parameters, stringDictionary); - } - }; - - // Run the operation in a different thread - runAsyncOperation(op); - return true; - } - - /** - * Runs the operation in a different thread and display generated - * exceptions. - * - * @param op The asynchronous operation to run. - */ - private void runAsyncOperation(WorkspaceModifyOperation op) { - try { - getContainer().run(true /* fork */, true /* cancelable */, op); - } catch (InvocationTargetException e) { - // The runnable threw an exception - Throwable t = e.getTargetException(); - if (t instanceof CoreException) { - CoreException core = (CoreException) t; - 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(getShell(), "Error", "Error: Case Variant Exists"); - } else { - ErrorDialog.openError(getShell(), "Error", null, core.getStatus()); - } - } else { - // Some other kind of exception - MessageDialog.openError(getShell(), "Error", t.getMessage()); - } - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - /** - * Creates the actual project, sets its nature and adds the required folders - * and files to it. This is run asynchronously in a different thread. - * - * @param project The project to create. - * @param description A description of the project. - * @param monitor An existing monitor. - * @param parameters Template parameters. - * @param stringDictionary String definition. - * @throws InvocationTargetException to wrap any unmanaged exception and - * return it to the calling thread. The method can fail if it fails - * to create or modify the project or if it is canceled by the user. - */ - private void createProjectAsync(IProject project, IProjectDescription description, - IProgressMonitor monitor, Map parameters, - Map stringDictionary) - throws InvocationTargetException { - monitor.beginTask("Create Android Project", 100); - try { - // Create project and open it - project.create(description, new SubProgressMonitor(monitor, 10)); - if (monitor.isCanceled()) throw new OperationCanceledException(); - project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10)); - - // Add the Java and android nature to the project - AndroidNature.setupProjectNatures(project, monitor); - - // Create folders in the project if they don't already exist - addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); - String[] sourceFolder = new String[] { parameters.get(PARAM_SRC_FOLDER) }; - addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor); - - // Create the resource folders in the project if they don't already exist. - addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); - - // Setup class path - IJavaProject javaProject = JavaCore.create(project); - setupSourceFolder(javaProject, sourceFolder[0], monitor); - - if (Boolean.parseBoolean(parameters.get(PARAM_IS_NEW_PROJECT))) { - // Create files in the project if they don't already exist - addManifest(project, parameters, stringDictionary, monitor); - - // add the default app icon - addIcon(project, monitor); - - // Create the default package components - addSampleCode(project, sourceFolder[0], parameters, stringDictionary, monitor); - - // add the string definition file if needed - if (stringDictionary.size() > 0) { - addStringDictionaryFile(project, stringDictionary, monitor); - } - - // Set output location - javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(), - monitor); - } - - // Fix the project to make sure all properties are as expected. - // Necessary for existing projects and good for new ones to. - ProjectHelper.fixProject(project); - - } catch (CoreException e) { - throw new InvocationTargetException(e); - } catch (IOException e) { - throw new InvocationTargetException(e); - } finally { - monitor.done(); - } - } - - /** - * Adds default directories to the project. - * - * @param project The Java Project to update. - * @param parentFolder The path of the parent folder. Must end with a - * separator. - * @param folders Folders to be added. - * @param monitor An existing monitor. - * @throws CoreException if the method fails to create the directories in - * the project. - */ - private void addDefaultDirectories(IProject project, String parentFolder, - String[] folders, IProgressMonitor monitor) throws CoreException { - for (String name : folders) { - if (name.length() > 0) { - IFolder folder = project.getFolder(parentFolder + name); - if (!folder.exists()) { - folder.create(true /* force */, true /* local */, - new SubProgressMonitor(monitor, 10)); - } - } - } - } - - /** - * Adds the manifest to the project. - * - * @param project The Java Project to update. - * @param parameters Template Parameters. - * @param stringDictionary String List to be added to a string definition - * file. This map will be filled by this method. - * @param monitor An existing monitor. - * @throws CoreException if the method fails to update the project. - * @throws IOException if the method fails to create the files in the - * project. - */ - private void addManifest(IProject project, Map parameters, - Map stringDictionary, IProgressMonitor monitor) - throws CoreException, IOException { - - // get IFile to the manifest and check if it's not already there. - IFile file = project.getFile(AndroidConstants.FN_ANDROID_MANIFEST); - if (!file.exists()) { - - // Read manifest template - String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST); - - // Replace all keyword parameters - manifestTemplate = replaceParameters(manifestTemplate, parameters); - - if (parameters.containsKey(PARAM_ACTIVITY)) { - // now get the activity template - String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES); - - // Replace all keyword parameters to make main activity. - String activities = replaceParameters(activityTemplate, parameters); - - // set the intent. - String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER); - - // set the intent to the main activity - activities = activities.replaceAll(PH_INTENT_FILTERS, intent); - - // set the activity(ies) in the manifest - manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities); - } else { - // remove the activity(ies) from the manifest - manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); - } - - // Save in the project as UTF-8 - InputStream stream = new ByteArrayInputStream( - manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$ - file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); - } - } - - /** - * Adds the string resource file. - * - * @param project The Java Project to update. - * @param strings The list of strings to be added to the string file. - * @param monitor An existing monitor. - * @throws CoreException if the method fails to update the project. - * @throws IOException if the method fails to create the files in the - * project. - */ - private void addStringDictionaryFile(IProject project, - Map strings, IProgressMonitor monitor) - throws CoreException, IOException { - - // create the IFile object and check if the file doesn't already exist. - IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + VALUES_DIRECTORY + AndroidConstants.WS_SEP + STRINGS_FILE); - if (!file.exists()) { - // get the Strings.xml template - String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS); - - // get the template for one string - String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING); - - // get all the string names - Set stringNames = strings.keySet(); - - // loop on it and create the string definitions - StringBuilder stringNodes = new StringBuilder(); - for (String key : stringNames) { - // get the value from the key - String value = strings.get(key); - - // place them in the template - String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key); - stringDef = stringDef.replace(PARAM_STRING_CONTENT, value); - - // append to the other string - if (stringNodes.length() > 0) { - stringNodes.append("\n"); - } - stringNodes.append(stringDef); - } - - // put the string nodes in the Strings.xml template - stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS, - stringNodes.toString()); - - // write the file as UTF-8 - InputStream stream = new ByteArrayInputStream( - stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$ - file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); - } - } - - - /** - * Adds default application icon to the project. - * - * @param project The Java Project to update. - * @param monitor An existing monitor. - * @throws CoreException if the method fails to update the project. - */ - private void addIcon(IProject project, IProgressMonitor monitor) - throws CoreException { - IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP - + DRAWABLE_DIRECTORY + AndroidConstants.WS_SEP + ICON); - if (!file.exists()) { - // read the content from the template - byte[] buffer = AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON); - - // if valid - if (buffer != null) { - // Save in the project - InputStream stream = new ByteArrayInputStream(buffer); - file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); - } - } - } - - /** - * Creates the package folder and copies the sample code in the project. - * - * @param project The Java Project to update. - * @param parameters Template Parameters. - * @param stringDictionary String List to be added to a string definition - * file. This map will be filled by this method. - * @param monitor An existing monitor. - * @throws CoreException if the method fails to update the project. - * @throws IOException if the method fails to create the files in the - * project. - */ - private void addSampleCode(IProject project, String sourceFolder, - Map parameters, Map stringDictionary, - IProgressMonitor monitor) throws CoreException, IOException { - // create the java package directories. - IFolder pkgFolder = project.getFolder(sourceFolder); - String packageName = parameters.get(PARAM_PACKAGE); - - // The PARAM_ACTIVITY key will be absent if no activity should be created, - // in which case activityName will be null. - String activityName = parameters.get(PARAM_ACTIVITY); - Map java_activity_parameters = parameters; - if (activityName != null) { - if (activityName.indexOf('.') >= 0) { - // There are package names in the activity name. Transform packageName to add - // those sub packages and remove them from activityName. - packageName += "." + activityName; //$NON-NLS-1$ - int pos = packageName.lastIndexOf('.'); - activityName = packageName.substring(pos + 1); - packageName = packageName.substring(0, pos); - - // Also update the values used in the JAVA_FILE_TEMPLATE below - // (but not the ones from the manifest so don't change the caller's dictionary) - java_activity_parameters = new HashMap(parameters); - java_activity_parameters.put(PARAM_PACKAGE, packageName); - java_activity_parameters.put(PARAM_ACTIVITY, activityName); - } - } - - String[] components = packageName.split(AndroidConstants.RE_DOT); - for (String component : components) { - pkgFolder = pkgFolder.getFolder(component); - if (!pkgFolder.exists()) { - pkgFolder.create(true /* force */, true /* local */, - new SubProgressMonitor(monitor, 10)); - } - } - - if (activityName != null) { - // create the main activity Java file - String activityJava = activityName + AndroidConstants.DOT_JAVA; - IFile file = pkgFolder.getFile(activityJava); - if (!file.exists()) { - copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor); - } - } - - // create the layout file - IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY); - IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML); - if (!file.exists()) { - copyFile(LAYOUT_TEMPLATE, file, parameters, monitor); - if (activityName != null) { - stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!"); - } else { - stringDictionary.put(STRING_HELLO_WORLD, "Hello World!"); - } - } - } - - /** - * Adds the given folder to the project's class path. - * - * @param javaProject The Java Project to update. - * @param sourceFolder Template Parameters. - * @param monitor An existing monitor. - * @throws JavaModelException if the classpath could not be set. - */ - private void setupSourceFolder(IJavaProject javaProject, String sourceFolder, - IProgressMonitor monitor) throws JavaModelException { - IProject project = javaProject.getProject(); - - // Add "src" to class path - IFolder srcFolder = project.getFolder(sourceFolder); - - IClasspathEntry[] entries = javaProject.getRawClasspath(); - entries = removeSourceClasspath(entries, srcFolder); - entries = removeSourceClasspath(entries, srcFolder.getParent()); - - entries = ProjectHelper.addEntryToClasspath(entries, - JavaCore.newSourceEntry(srcFolder.getFullPath())); - - javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10)); - } - - - /** - * Removes the corresponding source folder from the class path entries if - * found. - * - * @param entries The class path entries to read. A copy will be returned. - * @param folder The parent source folder to remove. - * @return A new class path entries array. - */ - private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) { - if (folder == null) { - return entries; - } - IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath()); - int n = entries.length; - for (int i = n - 1; i >= 0; i--) { - if (entries[i].equals(source)) { - IClasspathEntry[] newEntries = new IClasspathEntry[n - 1]; - if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i); - if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1); - n--; - entries = newEntries; - } - } - return entries; - } - - - /** - * Copies the given file from our resource folder to the new project. - * Expects the file to the US-ASCII or UTF-8 encoded. - * - * @throws CoreException from IFile if failing to create the new file. - * @throws MalformedURLException from URL if failing to interpret the URL. - * @throws FileNotFoundException from RandomAccessFile. - * @throws IOException from RandomAccessFile.length() if can't determine the - * length. - */ - private void copyFile(String resourceFilename, IFile destFile, - Map parameters, IProgressMonitor monitor) - throws CoreException, IOException { - - // Read existing file. - String template = AdtPlugin.readEmbeddedTextFile( - TEMPLATES_DIRECTORY + resourceFilename); - - // Replace all keyword parameters - template = replaceParameters(template, parameters); - - // Save in the project as UTF-8 - InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$ - destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); - } - - /** - * Returns an image descriptor for the wizard logo. - */ - private void setImageDescriptor() { - ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE); - setDefaultPageImageDescriptor(desc); - } - - /** - * Replaces placeholders found in a string with values. - * - * @param str the string to search for placeholders. - * @param parameters a map of to search for in the - * string - * @return A new String object with the placeholder replaced by the values. - */ - private String replaceParameters(String str, Map parameters) { - for (String key : parameters.keySet()) { - str = str.replaceAll(key, parameters.get(key)); - } - - return str; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java new file mode 100644 index 0000000..584dd0d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/properties/AndroidPropertyPage.java @@ -0,0 +1,98 @@ +/* + * 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.project.properties; + +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.sdklib.IAndroidTarget; +import com.android.sdkuilib.SdkTargetSelector; + +import org.eclipse.core.resources.IProject; +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.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.IWorkbenchPropertyPage; +import org.eclipse.ui.dialogs.PropertyPage; + +/** + * Property page for "Android" project. + * This is accessible from the Package Explorer when right clicking a project and choosing + * "Properties". + * + */ +public class AndroidPropertyPage extends PropertyPage implements IWorkbenchPropertyPage { + + private IProject mProject; + private SdkTargetSelector mSelector; + + public AndroidPropertyPage() { + // pass + } + + @Override + protected Control createContents(Composite parent) { + // get the element (this is not yet valid in the constructor). + mProject = (IProject)getElement(); + + Composite top = new Composite(parent, SWT.NONE); + top.setLayoutData(new GridData(GridData.FILL_BOTH)); + top.setLayout(new GridLayout(1, false)); + + Label l = new Label(top, SWT.NONE); + l.setText("Project Target"); + + // get the targets from the sdk + IAndroidTarget[] targets = null; + if (Sdk.getCurrent() != null) { + targets = Sdk.getCurrent().getTargets(); + } + + // build the UI. + mSelector = new SdkTargetSelector(top, targets, false /*allowMultipleSelection*/); + + if (Sdk.getCurrent() != null) { + IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); + if (target != null) { + mSelector.setSelection(target); + } + } + + mSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // look for the selection and validate the page if there is a selection + IAndroidTarget target = mSelector.getFirstSelected(); + setValid(target != null); + } + }); + + return top; + } + + @Override + public boolean performOk() { + if (Sdk.getCurrent() != null) { + Sdk.getCurrent().setProject(mProject, mSelector.getFirstSelected()); + } + + return true; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/AndroidJarLoader.java deleted file mode 100644 index 8c37f05..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/AndroidJarLoader.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (C) 2007 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.resources; - -import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass; -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.SubMonitor; - -import java.io.FileInputStream; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import javax.management.InvalidAttributeValueException; - -/** - * Custom class loader able to load a class from the SDK jar file. - */ -public class AndroidJarLoader extends ClassLoader implements IAndroidLoader { - - public final static class ClassWrapper implements IClass { - private Class mClass; - - public ClassWrapper(Class clazz) { - mClass = clazz; - } - - public String getCanonicalName() { - return mClass.getCanonicalName(); - } - - public IClass[] getDeclaredClasses() { - Class[] classes = mClass.getDeclaredClasses(); - IClass[] iclasses = new IClass[classes.length]; - for (int i = 0 ; i < classes.length ; i++) { - iclasses[i] = new ClassWrapper(classes[i]); - } - - return iclasses; - } - - public IClass getEnclosingClass() { - return new ClassWrapper(mClass.getEnclosingClass()); - } - - public String getSimpleName() { - return mClass.getSimpleName(); - } - - public IClass getSuperclass() { - return new ClassWrapper(mClass.getSuperclass()); - } - - @Override - public boolean equals(Object clazz) { - if (clazz instanceof ClassWrapper) { - return mClass.equals(((ClassWrapper)clazz).mClass); - } - return super.equals(clazz); - } - - @Override - public int hashCode() { - return mClass.hashCode(); - } - - - public boolean isInstantiable() { - int modifiers = mClass.getModifiers(); - return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true; - } - - public Class wrappedClass() { - return mClass; - } - - } - - private String mOsFrameworkLocation; - - /** A cache for binary data extracted from the zip */ - private final HashMap mEntryCache = new HashMap(); - /** A cache for already defined Classes */ - private final HashMap > mClassCache = new HashMap >(); - - /** - * Creates the class loader by providing the os path to the framework jar archive - * - * @param osFrameworkLocation OS Path of the framework JAR file - */ - public AndroidJarLoader(String osFrameworkLocation) { - super(); - mOsFrameworkLocation = osFrameworkLocation; - } - - public String getSource() { - return mOsFrameworkLocation; - } - - /** - * Pre-loads all class binary data that belong to the given package by reading the archive - * once and caching them internally. - *

          - * This does not actually preload "classes", it just reads the unzipped bytes for a given - * class. To obtain a class, one must call {@link #findClass(String)} later. - *

          - * All classes which package name starts with "packageFilter" will be included and can be - * found later. - *

          - * May throw some exceptions if the framework JAR cannot be read. - * - * @param packageFilter The package that contains all the class data to preload, using a fully - * qualified binary name (.e.g "com.my.package."). The matching algorithm - * is simple "startsWith". Use an empty string to include everything. - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - * @throws IOException - * @throws InvalidAttributeValueException - * @throws ClassFormatError - */ - public void preLoadClasses(String packageFilter, IProgressMonitor monitor) - throws IOException, InvalidAttributeValueException, ClassFormatError { - // Transform the package name into a zip entry path - String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ - - SubMonitor progress = SubMonitor.convert(monitor, 100); - - // 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. - String entryPath = entry.getName(); - - if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) { - // only accept class files - continue; - } - - // check if it is part of the package to preload - if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) { - continue; - } - String className = entryPathToClassName(entryPath); - - if (!mEntryCache.containsKey(className)) { - long entrySize = entry.getSize(); - if (entrySize > Integer.MAX_VALUE) { - throw new InvalidAttributeValueException(); - } - byte[] data = readZipData(zis, (int)entrySize); - mEntryCache.put(className, data); - } - - // advance 5% of whatever is allocated on the progress bar - progress.setWorkRemaining(100); - progress.worked(5); - } - } - - /** - * Finds and loads all classes that derive from a given set of super classes. - *

          - * As a side-effect this will load and cache most, if not all, classes in the input JAR file. - * - * @param packageFilter Base name of package of classes to find. - * Use an empty string to find everyting. - * @param superClasses The super classes of all the classes to find. - * @return An hash map which keys are the super classes looked for and which values are - * ArrayList of the classes found. The array lists are always created for all the - * valid keys, they are simply empty if no deriving class is found for a given - * super class. - * @throws IOException - * @throws InvalidAttributeValueException - * @throws ClassFormatError - */ - public HashMap> findClassesDerivingFrom( - String packageFilter, - String[] superClasses) - throws IOException, InvalidAttributeValueException, ClassFormatError { - - packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ - - HashMap> mClassesFound = new HashMap>(); - - for (String className : superClasses) { - mClassesFound.put(className, new ArrayList()); - } - - // 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(AndroidConstants.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); - } - 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; - } - } - } - - return mClassesFound; - } - - /** Helper method that converts a Zip entry path into a corresponding - * Java full qualified binary class name. - *

          - * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo". - */ - private String entryPathToClassName(String entryPath) { - return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - - /** - * Finds the class with the specified binary name. - * - * {@inheritDoc} - */ - @Override - protected Class findClass(String name) throws ClassNotFoundException { - try { - // try to find the class in the cache - Class cached_class = mClassCache.get(name); - if (cached_class == ClassNotFoundException.class) { - // we already know we can't find this class, don't try again - throw new ClassNotFoundException(name); - } else if (cached_class != null) { - return cached_class; - } - - // if not found, look it up and cache it - byte[] data = loadClassData(name); - if (data != null) { - return defineAndCacheClass(name, data); - } else { - // if the class can't be found, record a CNFE class in the map so - // that we don't try to reload it next time - mClassCache.put(name, ClassNotFoundException.class); - throw new ClassNotFoundException(name); - } - } catch (ClassNotFoundException e) { - throw e; - } catch (Exception e) { - throw new ClassNotFoundException(e.getMessage()); - } - } - - /** - * Defines a class based on its binary data and caches the resulting class object. - * - * @param name The binary name of the class (i.e. package.class1$class2) - * @param data The binary data from the loader. - * @return The class defined - * @throws ClassFormatError if defineClass failed. - */ - private Class defineAndCacheClass(String name, byte[] data) throws ClassFormatError { - Class cached_class; - cached_class = defineClass(null, data, 0, data.length); - - if (cached_class != null) { - // Add new class to the cache class and remove it from the zip entry data cache - mClassCache.put(name, cached_class); - mEntryCache.remove(name); - } - return cached_class; - } - - /** - * Loads a class data from its binary name. - *

          - * This uses the class binary data that has been preloaded earlier by the preLoadClasses() - * method if possible. - * - * @param className the binary name - * @return an array of bytes representing the class data or null if not found - * @throws InvalidAttributeValueException - * @throws IOException - */ - private synchronized byte[] loadClassData(String className) - throws InvalidAttributeValueException, IOException { - - byte[] data = mEntryCache.get(className); - if (data != null) { - return data; - } - - // The name is a binary name. Something like "android.R", or "android.R$id". - // Make a path out of it. - String entryName = className.replaceAll("\\.", "/") + AndroidConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ - - // create streams to read the intermediary archive - FileInputStream fis = new FileInputStream(mOsFrameworkLocation); - ZipInputStream zis = new ZipInputStream(fis); - - // loop on the entries of the intermediary package and put them in the final package. - ZipEntry entry; - - while ((entry = zis.getNextEntry()) != null) { - // get the name of the entry. - String currEntryName = entry.getName(); - - if (currEntryName.equals(entryName)) { - long entrySize = entry.getSize(); - if (entrySize > Integer.MAX_VALUE) { - throw new InvalidAttributeValueException(); - } - - data = readZipData(zis, (int)entrySize); - return data; - } - } - - return null; - } - - /** - * Reads data for the current entry from the zip input stream. - * - * @param zis The Zip input stream - * @param entrySize The entry size. -1 if unknown. - * @return The new data for the current entry. - * @throws IOException If ZipInputStream.read() fails. - */ - private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException { - int block_size = 1024; - int data_size = entrySize < 1 ? block_size : entrySize; - int offset = 0; - byte[] data = new byte[data_size]; - - while(zis.available() != 0) { - int count = zis.read(data, offset, data_size - offset); - if (count < 0) { // read data is done - break; - } - offset += count; - - if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done - break; - } - - // if we don't know the entry size and we're not done reading, - // expand the data buffer some more. - if (offset >= data_size) { - byte[] temp = new byte[data_size + block_size]; - System.arraycopy(data, 0, temp, 0, data_size); - data_size += block_size; - data = temp; - block_size *= 2; - } - } - - if (offset < data_size) { - // buffer was allocated too large, trim it - byte[] temp = new byte[offset]; - if (offset > 0) { - System.arraycopy(data, 0, temp, 0, offset); - } - data = temp; - } - - return data; - } - - /** - * Returns a {@link IClass} by its fully-qualified name. - * @param className the fully-qualified name of the class to return. - * @throws ClassNotFoundException - */ - public IClass getClass(String className) throws ClassNotFoundException { - try { - return new ClassWrapper(loadClass(className)); - } catch (ClassNotFoundException e) { - throw e; // useful for debugging - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceParser.java deleted file mode 100644 index 703efcf..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceParser.java +++ /dev/null @@ -1,576 +0,0 @@ -/* - * Copyright (C) 2007 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.resources; - -import com.android.ide.eclipse.adt.AdtPlugin; -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.AttrsXmlParser; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.common.resources.ViewClassInfo; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.SubMonitor; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.management.InvalidAttributeValueException; - -/** - * Parser for the framework library. - *

          - * This gather the following information: - *

            - *
          • Resource ID from android.R
          • - *
          • The list of permissions values from android.Manifest$permission
          • - *
          • - *
          - */ -public final class FrameworkResourceParser { - - private static final String TAG = "Framework Resource Parser"; - - /** - * Creates a framework resource parser. - */ - public FrameworkResourceParser() { - } - - /** - * Parses the framework, collects all interesting information and stores them in the - * {@link FrameworkResourceManager} given to the constructor. - * - * @param osSdkPath the OS path of the SDK directory. - * @param resourceManager the {@link FrameworkResourceManager} that will store the parsed - * resources. - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - * @return True if the SDK path was valid and parsing has been attempted. - */ - public boolean parse(String osSdkPath, FrameworkResourceManager resourceManager, - IProgressMonitor monitor) { - if (osSdkPath == null || osSdkPath.length() == 0) { - return false; - } - - try { - SubMonitor progress = SubMonitor.convert(monitor, 100); - - AndroidJarLoader classLoader = - new AndroidJarLoader(osSdkPath + AndroidConstants.FN_FRAMEWORK_LIBRARY); - - progress.subTask("Preloading"); - preload(classLoader, progress.newChild(40)); - progress.setWorkRemaining(60); - - if (progress.isCanceled()) { - return false; - } - - // get the resource Ids. - progress.subTask("Resource IDs"); - FrameworkResourceRepository systemResourceRepository = new FrameworkResourceRepository( - collectResourceIds(classLoader)); - progress.worked(5); - - if (progress.isCanceled()) { - return false; - } - - // get the permissions - progress.subTask("Permissions"); - String[] permissionValues = collectPermissions(classLoader); - progress.worked(5); - - if (progress.isCanceled()) { - return false; - } - - String osLibPath = osSdkPath + AndroidConstants.OS_SDK_LIBS_FOLDER; - - // get the action and category values for the Intents. - progress.subTask("Intents"); - ArrayList activity_actions = new ArrayList(); - ArrayList broadcast_actions = new ArrayList(); - ArrayList service_actions = new ArrayList(); - ArrayList categories = new ArrayList(); - collectIntentFilterActionsAndCategories(osLibPath, - activity_actions, broadcast_actions, service_actions, categories); - progress.worked(5); - - if (progress.isCanceled()) { - return false; - } - - progress.subTask("Layouts"); - AttrsXmlParser attrsXmlParser = new AttrsXmlParser( - osSdkPath + AndroidConstants.OS_SDK_ATTRS_XML); - attrsXmlParser.preload(); - - AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser( - osSdkPath + AndroidConstants.OS_SDK_ATTRS_MANIFEST_XML, - attrsXmlParser); - attrsManifestXmlParser.preload(); - - Collection mainList = new ArrayList(); - Collection groupList = new ArrayList(); - - collectLayoutClasses(osLibPath, classLoader, attrsXmlParser, mainList, groupList, - progress.newChild(40)); - - if (progress.isCanceled()) { - return false; - } - - ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]); - ViewClassInfo[] layoutGroupsInfo = groupList.toArray( - new ViewClassInfo[groupList.size()]); - - mainList.clear(); - groupList.clear(); - collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList, - progress.newChild(5)); - - if (progress.isCanceled()) { - return false; - } - - ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]); - ViewClassInfo[] preferenceGroupsInfo = groupList.toArray( - new ViewClassInfo[groupList.size()]); - - Map xmlMenuMap = collectMenuDefinitions(attrsXmlParser); - Map xmlSearchableMap = collectSearchableDefinitions( - attrsXmlParser); - Map manifestMap = collectManifestDefinitions( - attrsManifestXmlParser); - Map> enumValueMap = attrsXmlParser.getEnumFlagValues(); - - if (progress.isCanceled()) { - return false; - } - - String docBaseUrl = getDocumentationBaseUrl( - osSdkPath + AndroidConstants.OS_SDK_DOCS_FOLDER); - - FrameworkResourceManager.getInstance().setResources(systemResourceRepository, - layoutViewsInfo, - layoutGroupsInfo, - preferencesInfo, - preferenceGroupsInfo, - xmlMenuMap, - xmlSearchableMap, - manifestMap, - enumValueMap, - permissionValues, - activity_actions.toArray(new String[activity_actions.size()]), - broadcast_actions.toArray(new String[broadcast_actions.size()]), - service_actions.toArray(new String[service_actions.size()]), - categories.toArray(new String[categories.size()]), - docBaseUrl); - - return true; - } catch (Exception e) { - AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$ - } - - return false; - } - - /** - * Preloads all "interesting" classes from the framework SDK jar. - *

          - * Currently this preloads all classes from the framework jar - * - * @param classLoader The framework SDK jar classloader - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - */ - private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) { - try { - classLoader.preLoadClasses("" /* all classes */, monitor); //$NON-NLS-1$ - } catch (InvalidAttributeValueException e) { - AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$ - } catch (IOException e) { - AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$ - } - } - - /** - * Collects the resources IDs found in the SDK. - * - * @param classLoader The framework SDK jar classloader - * @return a map of the resources, or null if it failed. - */ - private Map> collectResourceIds( - AndroidJarLoader classLoader) { - try { - Class r = classLoader.loadClass(AndroidConstants.CLASS_R); - - if (r != null) { - return parseRClass(r); - } - } catch (ClassNotFoundException e) { - AdtPlugin.logAndPrintError(e, TAG, - "Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$ - AndroidConstants.CLASS_R, - classLoader.getSource()); - } - - return null; - } - - /** - * Parse the R class and build the resource map. - * - * @param rClass the Class object representing the Resources. - * @return a map of the resource or null - */ - private Map> parseRClass(Class rClass) { - // get the sub classes. - Class[] classes = rClass.getClasses(); - - if (classes.length > 0) { - HashMap> map = - new HashMap>(); - - // get the fields of each class. - for (int c = 0 ; c < classes.length ; c++) { - Class subClass = classes[c]; - String name = subClass.getSimpleName(); - - // get the matching ResourceType - ResourceType type = ResourceType.getEnum(name); - if (type != null) { - List list = new ArrayList(); - map.put(type, list); - - Field[] fields = subClass.getFields(); - - for (Field f : fields) { - list.add(new ResourceItem(f.getName())); - } - } - } - - return map; - } - - return null; - } - - /** - * Loads, collects and returns the list of default permissions from the framework. - * - * @param classLoader The framework SDK jar classloader - * @return a non null (but possibly empty) array containing the permission values. - */ - private String[] collectPermissions(AndroidJarLoader classLoader) { - try { - Class permissionClass = - classLoader.loadClass(AndroidConstants.CLASS_MANIFEST_PERMISSION); - - if (permissionClass != null) { - ArrayList list = new ArrayList(); - - Field[] fields = permissionClass.getFields(); - - for (Field f : fields) { - int modifiers = f.getModifiers(); - if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) && - Modifier.isPublic(modifiers)) { - try { - Object value = f.get(null); - if (value instanceof String) { - list.add((String)value); - } - } catch (IllegalArgumentException e) { - // since we provide null this should not happen - } catch (IllegalAccessException e) { - // if the field is inaccessible we ignore it. - } catch (NullPointerException npe) { - // looks like this is not a static field. we can ignore. - } catch (ExceptionInInitializerError eiie) { - // lets just ignore the field again - } - } - } - - return list.toArray(new String[list.size()]); - } - } catch (ClassNotFoundException e) { - AdtPlugin.logAndPrintError(e, TAG, - "Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$ - AndroidConstants.CLASS_MANIFEST_PERMISSION, - classLoader.getSource()); - } - - return new String[0]; - } - - /** - * Loads and collects the action and category default values from the framework. - * The values are added to the actions and categories lists. - * - * @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator. - * @param activityActions the list which will receive the activity action values. - * @param broadcastActions the list which will receive the broadcast action values. - * @param serviceActions the list which will receive the service action values. - * @param categories the list which will receive the category values. - */ - private void collectIntentFilterActionsAndCategories(String osLibPath, - ArrayList activityActions, ArrayList broadcastActions, - ArrayList serviceActions, ArrayList categories) { - collectValues(osLibPath + "activity_actions.txt" , activityActions); - collectValues(osLibPath + "broadcast_actions.txt" , broadcastActions); - collectValues(osLibPath + "service_actions.txt" , serviceActions); - collectValues(osLibPath + "categories.txt" , categories); - } - - /** - * Collects values from a text file located in the SDK - * @param osFilePath The path to the text file. - * @param values the {@link ArrayList} to fill with the values. - */ - private void collectValues(String osFilePath, ArrayList values) { - FileReader fr = null; - BufferedReader reader = null; - try { - fr = new FileReader(osFilePath); - reader = new BufferedReader(fr); - - String line; - while ((line = reader.readLine()) != null) { - line = line.trim(); - if (line.length() > 0 && line.startsWith("#") == false) { //$NON-NLS-1$ - values.add(line); - } - } - } catch (IOException e) { - AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ - } finally { - try { - if (reader != null) { - reader.close(); - } - } catch (IOException e) { - AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ - } - - try { - if (fr != null) { - fr.close(); - } - } catch (IOException e) { - AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ - } - } - } - - /** - * Collects all layout classes information from the class loader and the - * attrs.xml and sets the corresponding structures in the resource manager. - * - * @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator. - * @param classLoader The framework SDK jar classloader - * @param attrsXmlParser The parser of the attrs.xml file - * @param mainList the Collection to receive the main list of {@link ViewClassInfo}. - * @param groupList the Collection to receive the group list of {@link ViewClassInfo}. - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - */ - private void collectLayoutClasses(String osLibPath, - AndroidJarLoader classLoader, - AttrsXmlParser attrsXmlParser, - Collection mainList, Collection groupList, - IProgressMonitor monitor) { - LayoutParamsParser ldp = null; - try { - WidgetListLoader loader = new WidgetListLoader(osLibPath + "widgets.txt"); - if (loader.parseWidgetList(monitor)) { - ldp = new LayoutParamsParser(loader, attrsXmlParser); - } - // if the parsing failed, we'll use the old loader below. - } catch (FileNotFoundException e) { - AdtPlugin.log(e, "Android Framework Parser"); //$NON-NLS-1$ - // the file does not exist, we'll use the old loader below. - } - - if (ldp == null) { - ldp = new LayoutParamsParser(classLoader, attrsXmlParser); - } - ldp.parseLayoutClasses(monitor); - - List views = ldp.getViews(); - List groups = ldp.getGroups(); - - if (views != null && groups != null) { - mainList.addAll(views); - groupList.addAll(groups); - } - } - - /** - * Collects all preferences definition information from the attrs.xml and - * sets the corresponding structures in the resource manager. - * - * @param classLoader The framework SDK jar classloader - * @param attrsXmlParser The parser of the attrs.xml file - * @param mainList the Collection to receive the main list of {@link ViewClassInfo}. - * @param groupList the Collection to receive the group list of {@link ViewClassInfo}. - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - */ - private void collectPreferenceClasses(AndroidJarLoader classLoader, - AttrsXmlParser attrsXmlParser, Collection mainList, - Collection groupList, IProgressMonitor monitor) { - LayoutParamsParser ldp = new LayoutParamsParser(classLoader, attrsXmlParser); - - try { - ldp.parsePreferencesClasses(monitor); - - List prefs = ldp.getViews(); - List groups = ldp.getGroups(); - - if (prefs != null && groups != null) { - mainList.addAll(prefs); - groupList.addAll(groups); - } - } catch (NoClassDefFoundError e) { - AdtPlugin.logAndPrintError(e, TAG, - "Collect preferences failed, class %1$s not found in %2$s", //$NON-NLS-1$ - e.getMessage(), - classLoader.getSource()); - } catch (Throwable e) { - AdtPlugin.log(e, "Android Framework Parser: failed to collect preference classes"); //$NON-NLS-1$ - AdtPlugin.printErrorToConsole("Android Framework Parser", "failed to collect preference classes"); - } - } - - /** - * Collects all menu definition information from the attrs.xml and returns it. - * - * @param attrsXmlParser The parser of the attrs.xml file - */ - private Map collectMenuDefinitions( - AttrsXmlParser attrsXmlParser) { - Map map = attrsXmlParser.getDeclareStyleableList(); - Map map2 = new HashMap(); - for (String key : new String[] { "Menu", //$NON-NLS-1$ - "MenuItem", //$NON-NLS-1$ - "MenuGroup" }) { //$NON-NLS-1$ - if (map.containsKey(key)) { - map2.put(key, map.get(key)); - } else { - AdtPlugin.log(IStatus.WARNING, - "Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ - key, attrsXmlParser.getOsAttrsXmlPath()); - AdtPlugin.printErrorToConsole("Android Framework Parser", - String.format("Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ - key, attrsXmlParser.getOsAttrsXmlPath())); - } - } - - return Collections.unmodifiableMap(map2); - } - - /** - * Collects all searchable definition information from the attrs.xml and returns it. - * - * @param attrsXmlParser The parser of the attrs.xml file - */ - private Map collectSearchableDefinitions( - AttrsXmlParser attrsXmlParser) { - Map map = attrsXmlParser.getDeclareStyleableList(); - Map map2 = new HashMap(); - for (String key : new String[] { "Searchable", //$NON-NLS-1$ - "SearchableActionKey" }) { //$NON-NLS-1$ - if (map.containsKey(key)) { - map2.put(key, map.get(key)); - } else { - AdtPlugin.log(IStatus.WARNING, - "Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ - key, attrsXmlParser.getOsAttrsXmlPath()); - AdtPlugin.printErrorToConsole("Android Framework Parser", - String.format("Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ - key, attrsXmlParser.getOsAttrsXmlPath())); - } - } - - return Collections.unmodifiableMap(map2); - } - - /** - * Collects all manifest definition information from the attrs_manifest.xml and returns it. - */ - private Map collectManifestDefinitions( - AttrsXmlParser attrsXmlParser) { - - return attrsXmlParser.getDeclareStyleableList(); - } - - /** - * Returns the URL to the local documentation. - * Can return null if no documentation is found in the current SDK. - * - * @param osDocsPath Path to the documentation folder in the current SDK. - * The folder may not actually exist. - * @return A file:// URL on the local documentation folder if it exists or null. - */ - private String getDocumentationBaseUrl(String osDocsPath) { - File f = new File(osDocsPath); - - if (f.isDirectory()) { - try { - // Note: to create a file:// URL, one would typically use something like - // f.toURI().toURL().toString(). However this generates a broken path on - // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of - // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll - // do the correct thing manually. - - String path = f.getAbsolutePath(); - if (File.separatorChar != '/') { - path = path.replace(File.separatorChar, '/'); - } - - // For some reason the URL class doesn't add the mandatory "//" after - // the "file:" protocol name, so it has to be hacked into the path. - URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$ - String result = url.toString(); - return result; - } catch (MalformedURLException e) { - // ignore malformed URLs - } - } - - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceRepository.java deleted file mode 100644 index 19a7de3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/FrameworkResourceRepository.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; - -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Implementation of the {@link IResourceRepository} interface to hold the system resource Ids - * parsed by {@link FrameworkResourceParser}. - */ -public final class FrameworkResourceRepository implements IResourceRepository { - - private Map> mResourcesMap; - - public FrameworkResourceRepository(Map> systemResourcesMap) { - mResourcesMap = systemResourcesMap; - } - - public ResourceType[] getAvailableResourceTypes() { - if (mResourcesMap != null) { - Set types = mResourcesMap.keySet(); - - if (types != null) { - return types.toArray(new ResourceType[types.size()]); - } - } - - return null; - } - - public ResourceItem[] getResources(ResourceType type) { - if (mResourcesMap != null) { - List items = mResourcesMap.get(type); - - if (items != null) { - return items.toArray(new ResourceItem[items.size()]); - } - } - - return null; - } - - public boolean hasResources(ResourceType type) { - if (mResourcesMap != null) { - List items = mResourcesMap.get(type); - - return (items != null && items.size() > 0); - } - - return false; - } - - public boolean isSystemRepository() { - return true; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/IAndroidLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/IAndroidLoader.java deleted file mode 100644 index f0f48ca..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/IAndroidLoader.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; - -import javax.management.InvalidAttributeValueException; - -/** - * Classes which implements this interface provide methods to access framework resource - * data loaded from the SDK. - */ -public interface IAndroidLoader { - - /** - * Finds and loads all classes that derive from a given set of super classes. - * - * @param rootPackage Root package of classes to find. Use an empty string to find everyting. - * @param superClasses The super classes of all the classes to find. - * @return An hash map which keys are the super classes looked for and which values are - * ArrayList of the classes found. The array lists are always created for all the - * valid keys, they are simply empty if no deriving class is found for a given - * super class. - * @throws IOException - * @throws InvalidAttributeValueException - * @throws ClassFormatError - */ - public HashMap> findClassesDerivingFrom( - String rootPackage, String[] superClasses) - throws IOException, InvalidAttributeValueException, ClassFormatError; - - /** - * Returns a {@link IClass} by its fully-qualified name. - * @param className the fully-qualified name of the class to return. - * @throws ClassNotFoundException - */ - public IClass getClass(String className) throws ClassNotFoundException; - - /** - * Returns a string indicating the source of the classes, typically for debugging - * or in error messages. This would typically be a JAR file name or some kind of - * identifier that would mean something to the user when looking at error messages. - * - * @return An informal string representing the source of the classes. - */ - public String getSource(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/LayoutParamsParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/LayoutParamsParser.java deleted file mode 100644 index ee71b60..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/LayoutParamsParser.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.CommonPlugin; -import com.android.ide.eclipse.common.resources.AttrsXmlParser; -import com.android.ide.eclipse.common.resources.ViewClassInfo; -import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.SubMonitor; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.management.InvalidAttributeValueException; - -/* - * TODO: refactor this. Could use some cleanup. - */ - -/** - * Parser for the framework library. - *

          - * This gather the following information: - *

            - *
          • Resource ID from android.R
          • - *
          • The list of permissions values from android.Manifest$permission
          • - *
          • - *
          - */ -public class LayoutParamsParser { - - /** - * Classes which implement this interface provide methods to describe a class. - */ - public interface IClass { - - public String getCanonicalName(); - - public IClass getSuperclass(); - - public String getSimpleName(); - - public IClass getEnclosingClass(); - - public IClass[] getDeclaredClasses(); - - public boolean isInstantiable(); - } - - final static class ExtViewClassInfo extends ViewClassInfo { - - private boolean mIsInstantiable; - - ExtViewClassInfo(boolean instantiable, boolean isLayout, String canonicalClassName, - String shortClassName) { - super(isLayout, canonicalClassName, shortClassName); - mIsInstantiable = instantiable; - } - - boolean isInstantiable() { - return mIsInstantiable; - } - } - - /* Note: protected members/methods are overridden in unit tests */ - - /** Reference to android.view.View */ - protected IClass mTopViewClass; - /** Reference to android.view.ViewGroup */ - protected IClass mTopGroupClass; - /** Reference to android.view.ViewGroup$LayoutParams */ - protected IClass mTopLayoutParamsClass; - - /** Input list of all classes deriving from android.view.View */ - protected ArrayList mViewList; - /** Input list of all classes deriving from android.view.ViewGroup */ - protected ArrayList mGroupList; - - /** Output map of FQCN => info on View classes */ - protected TreeMap mViewMap; - /** Output map of FQCN => info on ViewGroup classes */ - protected TreeMap mGroupMap; - /** Output map of FQCN => info on LayoutParams classes */ - protected HashMap mLayoutParamsMap; - - /** The attrs.xml parser */ - protected AttrsXmlParser mAttrsXmlParser; - - /** The android.jar class loader */ - protected IAndroidLoader mClassLoader; - - /** - * Instantiate a new LayoutParamsParser. - * @param classLoader The android.jar class loader - * @param attrsXmlParser The parser of the attrs.xml file - */ - public LayoutParamsParser(IAndroidLoader classLoader, - AttrsXmlParser attrsXmlParser) { - mClassLoader = classLoader; - mAttrsXmlParser = attrsXmlParser; - } - - /** Returns the map of FQCN => info on View classes */ - public List getViews() { - return getInstantiables(mViewMap); - } - - /** Returns the map of FQCN => info on ViewGroup classes */ - public List getGroups() { - return getInstantiables(mGroupMap); - } - - /** - * TODO: doc here. - *

          - * Note: on output we should have NO dependency on IClass, otherwise we wouldn't be able - * to unload the class loader later. - *

          - * Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass") - * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. - */ - public void parseLayoutClasses(IProgressMonitor monitor) { - parseClasses(monitor, - AndroidConstants.CLASS_VIEW, - AndroidConstants.CLASS_VIEWGROUP, - AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS); - } - - public void parsePreferencesClasses(IProgressMonitor monitor) { - parseClasses(monitor, - AndroidConstants.CLASS_PREFERENCE, - AndroidConstants.CLASS_PREFERENCEGROUP, - null /* paramsClassName */ ); - } - - private void parseClasses(IProgressMonitor monitor, - String rootClassName, - String groupClassName, - String paramsClassName) { - try { - SubMonitor progress = SubMonitor.convert(monitor, 100); - - String[] superClasses = new String[2 + (paramsClassName == null ? 0 : 1)]; - superClasses[0] = groupClassName; - superClasses[1] = rootClassName; - if (paramsClassName != null) { - superClasses[2] = paramsClassName; - } - HashMap> found = mClassLoader.findClassesDerivingFrom( - "android.", superClasses); - mTopViewClass = mClassLoader.getClass(rootClassName); - mTopGroupClass = mClassLoader.getClass(groupClassName); - if (paramsClassName != null) { - mTopLayoutParamsClass = mClassLoader.getClass(paramsClassName); - } - - mViewList = found.get(rootClassName); - mGroupList = found.get(groupClassName); - - mViewMap = new TreeMap(); - mGroupMap = new TreeMap(); - if (mTopLayoutParamsClass != null) { - mLayoutParamsMap = new HashMap(); - } - - // Add top classes to the maps since by design they are not listed in classes deriving - // from themselves. - addGroup(mTopGroupClass); - addView(mTopViewClass); - - // ViewGroup derives from View - mGroupMap.get(groupClassName).setSuperClass( - mViewMap.get(rootClassName)); - - progress.setWorkRemaining(mGroupList.size() + mViewList.size()); - - for (IClass groupChild : mGroupList) { - addGroup(groupChild); - progress.worked(1); - } - - for (IClass viewChild : mViewList) { - if (viewChild != mTopGroupClass) { - addView(viewChild); - } - progress.worked(1); - } - } catch (ClassNotFoundException e) { - CommonPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$ - rootClassName, groupClassName); - } catch (InvalidAttributeValueException e) { - CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ - } catch (ClassFormatError e) { - CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ - } catch (IOException e) { - CommonPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ - } - } - - /** - * Parses a View class and adds a ExtViewClassInfo for it in mViewMap. - * It calls itself recursively to handle super classes which are also Views. - */ - private ExtViewClassInfo addView(IClass viewClass) { - String fqcn = viewClass.getCanonicalName(); - if (mViewMap.containsKey(fqcn)) { - return mViewMap.get(fqcn); - } else if (mGroupMap.containsKey(fqcn)) { - return mGroupMap.get(fqcn); - } - - ExtViewClassInfo info = new ExtViewClassInfo(viewClass.isInstantiable(), - false /* layout */, fqcn, viewClass.getSimpleName()); - mViewMap.put(fqcn, info); - - // All view classes derive from mTopViewClass by design. - // Do not lookup the super class for mTopViewClass itself. - if (viewClass.equals(mTopViewClass) == false) { - IClass superClass = viewClass.getSuperclass(); - ExtViewClassInfo superClassInfo = addView(superClass); - info.setSuperClass(superClassInfo); - } - - mAttrsXmlParser.loadViewAttributes(info); - return info; - } - - /** - * Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap. - * It calls itself recursively to handle super classes which are also ViewGroups. - */ - private ExtViewClassInfo addGroup(IClass groupClass) { - String fqcn = groupClass.getCanonicalName(); - if (mGroupMap.containsKey(fqcn)) { - return mGroupMap.get(fqcn); - } - - ExtViewClassInfo info = new ExtViewClassInfo(groupClass.isInstantiable(), - true /* layout */, fqcn, groupClass.getSimpleName()); - mGroupMap.put(fqcn, info); - - // All groups derive from android.view.ViewGroup, which in turns derives from - // android.view.View (i.e. mTopViewClass here). So the only group that can have View as - // its super class is the ViewGroup base class and we don't try to resolve it since groups - // are loaded before views. - IClass superClass = groupClass.getSuperclass(); - - // Assertion: at this point, we should have - // superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP); - - if (superClass != null && superClass.equals(mTopViewClass) == false) { - ExtViewClassInfo superClassInfo = addGroup(superClass); - - // Assertion: we should have superClassInfo != null && superClassInfo != info; - if (superClassInfo != null && superClassInfo != info) { - info.setSuperClass(superClassInfo); - } - } - - mAttrsXmlParser.loadViewAttributes(info); - if (mTopLayoutParamsClass != null) { - info.setLayoutParams(addLayoutParams(groupClass)); - } - return info; - } - - /** - * Parses a ViewGroup class and returns an info object on its inner LayoutParams. - * - * @return The {@link LayoutParamsInfo} for the ViewGroup class or null. - */ - private LayoutParamsInfo addLayoutParams(IClass groupClass) { - - // Is there a LayoutParams in this group class? - IClass layoutParamsClass = findLayoutParams(groupClass); - - // if there's no layout data in the group class, link to the one from the - // super class. - if (layoutParamsClass == null) { - for (IClass superClass = groupClass.getSuperclass(); - layoutParamsClass == null && - superClass != null && - superClass.equals(mTopViewClass) == false; - superClass = superClass.getSuperclass()) { - layoutParamsClass = findLayoutParams(superClass); - } - } - - if (layoutParamsClass != null) { - return getLayoutParamsInfo(layoutParamsClass); - } - - return null; - } - - /** - * Parses a LayoutParams class and returns a LayoutParamsInfo object for it. - * It calls itself recursively to handle the super class of the LayoutParams. - */ - private LayoutParamsInfo getLayoutParamsInfo(IClass layoutParamsClass) { - String fqcn = layoutParamsClass.getCanonicalName(); - LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn); - - if (layoutParamsInfo != null) { - return layoutParamsInfo; - } - - // Find the link on the LayoutParams super class - LayoutParamsInfo superClassInfo = null; - if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) { - IClass superClass = layoutParamsClass.getSuperclass(); - superClassInfo = getLayoutParamsInfo(superClass); - } - - // Find the link on the enclosing ViewGroup - ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass()); - - layoutParamsInfo = new ExtViewClassInfo.LayoutParamsInfo( - enclosingGroupInfo, layoutParamsClass.getSimpleName(), superClassInfo); - mLayoutParamsMap.put(fqcn, layoutParamsInfo); - - mAttrsXmlParser.loadLayoutParamsAttributes(layoutParamsInfo); - - return layoutParamsInfo; - } - - /** - * Given a ViewGroup-derived class, looks for an inner class named LayoutParams - * and if found returns its class definition. - *

          - * This uses the actual defined inner classes and does not look at inherited classes. - * - * @param groupClass The ViewGroup derived class - * @return The Class of the inner LayoutParams or null if none is declared. - */ - private IClass findLayoutParams(IClass groupClass) { - IClass[] innerClasses = groupClass.getDeclaredClasses(); - for (IClass innerClass : innerClasses) { - if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) { - return innerClass; - } - } - return null; - } - - private List getInstantiables(SortedMap map) { - Collection values = map.values(); - ArrayList list = new ArrayList(); - - for (ExtViewClassInfo info : values) { - if (info.mIsInstantiable) { - list.add(info); - } - } - - return list; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/WidgetListLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/WidgetListLoader.java deleted file mode 100644 index c85a50e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/resources/WidgetListLoader.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass; -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.runtime.IProgressMonitor; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; - -import javax.management.InvalidAttributeValueException; - -/** - * Parser for the text file containing the list of widgets, layouts and layout params. - *

          - * The file is a straight text file containing one class per line.
          - * Each line is in the following format
          - * [code][class name] [super class name] [super class name]... - * where code is a single letter (W for widget, L for layout, P for layout params), and class names - * are the fully qualified name of the classes. - */ -public final class WidgetListLoader implements IAndroidLoader { - - /** - * Basic class containing the class descriptions found in the text file. - */ - private final static class ClassDescriptor implements IClass { - - private String mName; - private String mSimpleName; - private ClassDescriptor mSuperClass; - private ClassDescriptor mEnclosingClass; - private final ArrayList mDeclaredClasses = new ArrayList(); - private boolean mIsInstantiable = false; - - ClassDescriptor(String fqcn) { - mName = fqcn; - mSimpleName = getSimpleName(fqcn); - } - - public String getCanonicalName() { - return mName; - } - - public String getSimpleName() { - return mSimpleName; - } - - public IClass[] getDeclaredClasses() { - return mDeclaredClasses.toArray(new IClass[mDeclaredClasses.size()]); - } - - private void addDeclaredClass(ClassDescriptor declaredClass) { - mDeclaredClasses.add(declaredClass); - } - - public IClass getEnclosingClass() { - return mEnclosingClass; - } - - void setEnclosingClass(ClassDescriptor enclosingClass) { - // set the enclosing class. - mEnclosingClass = enclosingClass; - - // add this to the list of declared class in the enclosing class. - mEnclosingClass.addDeclaredClass(this); - - // finally change the name of declared class to make sure it uses the - // convention: package.enclosing$declared instead of package.enclosing.declared - mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1); - } - - public IClass getSuperclass() { - return mSuperClass; - } - - void setSuperClass(ClassDescriptor superClass) { - mSuperClass = superClass; - } - - @Override - public boolean equals(Object clazz) { - if (clazz instanceof ClassDescriptor) { - return mName.equals(((ClassDescriptor)clazz).mName); - } - return super.equals(clazz); - } - - @Override - public int hashCode() { - return mName.hashCode(); - } - - public boolean isInstantiable() { - return mIsInstantiable; - } - - void setInstantiable(boolean state) { - mIsInstantiable = state; - } - - private String getSimpleName(String fqcn) { - String[] segments = fqcn.split("\\."); - return segments[segments.length-1]; - } - } - - private BufferedReader mReader; - - /** Output map of FQCN => descriptor on all classes */ - private final Map mMap = new TreeMap(); - /** Output map of FQCN => descriptor on View classes */ - private final Map mWidgetMap = new TreeMap(); - /** Output map of FQCN => descriptor on ViewGroup classes */ - private final Map mLayoutMap = new TreeMap(); - /** Output map of FQCN => descriptor on LayoutParams classes */ - private final Map mLayoutParamsMap = - new HashMap(); - /** File path of the source text file */ - private String mOsFilePath; - - /** - * Creates a loader with a given file path. - * @param osFilePath the OS path of the file to load. - * @throws FileNotFoundException if the file is not found. - */ - WidgetListLoader(String osFilePath) throws FileNotFoundException { - mOsFilePath = osFilePath; - mReader = new BufferedReader(new FileReader(osFilePath)); - } - - public String getSource() { - return mOsFilePath; - } - - /** - * Parses the text file and return true if the file was successfully parsed. - * @param monitor - */ - boolean parseWidgetList(IProgressMonitor monitor) { - try { - String line; - while ((line = mReader.readLine()) != null) { - if (line.length() > 0) { - char prefix = line.charAt(0); - String[] classes = null; - ClassDescriptor clazz = null; - switch (prefix) { - case 'W': - classes = line.substring(1).split(" "); - clazz = processClass(classes, 0, null /* map */); - if (clazz != null) { - clazz.setInstantiable(true); - mWidgetMap.put(classes[0], clazz); - } - break; - case 'L': - classes = line.substring(1).split(" "); - clazz = processClass(classes, 0, null /* map */); - if (clazz != null) { - clazz.setInstantiable(true); - mLayoutMap.put(classes[0], clazz); - } - break; - case 'P': - classes = line.substring(1).split(" "); - clazz = processClass(classes, 0, mLayoutParamsMap); - if (clazz != null) { - clazz.setInstantiable(true); - } - break; - case '#': - // comment, do nothing - break; - default: - throw new IllegalArgumentException(); - } - } - } - - // reconciliate the layout and their layout params - postProcess(); - - return true; - } catch (IOException e) { - } finally { - try { - mReader.close(); - } catch (IOException e) { - } - } - - return false; - } - - /** - * Parses a View class and adds a ViewClassInfo for it in mWidgetMap. - * It calls itself recursively to handle super classes which are also Views. - * @param classes the inheritance list of the class to process. - * @param index the index of the class to process in the classes array. - * @param map an optional map in which to put every {@link ClassDescriptor} created. - */ - private ClassDescriptor processClass(String[] classes, int index, - Map map) { - if (index >= classes.length) { - return null; - } - - String fqcn = classes[index]; - - if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$ - return null; - } - - // check if the ViewInfoClass has not yet been created. - if (mMap.containsKey(fqcn)) { - return mMap.get(fqcn); - } - - // create the custom class. - ClassDescriptor clazz = new ClassDescriptor(fqcn); - mMap.put(fqcn, clazz); - if (map != null) { - map.put(fqcn, clazz); - } - - // get the super class - ClassDescriptor superClass = processClass(classes, index+1, map); - if (superClass != null) { - clazz.setSuperClass(superClass); - } - - return clazz; - } - - /** - * Goes through the layout params and look for the enclosed class. If the layout params - * has no known enclosed type it is dropped. - */ - private void postProcess() { - Collection params = mLayoutParamsMap.values(); - - for (ClassDescriptor param : params) { - String fqcn = param.getCanonicalName(); - - // get the enclosed name. - String enclosed = getEnclosedName(fqcn); - - // look for a match in the layouts. We don't use the layout map as it only contains the - // end classes, but in this case we also need to process the layout params for the base - // layout classes. - ClassDescriptor enclosingType = mMap.get(enclosed); - if (enclosingType != null) { - param.setEnclosingClass(enclosingType); - - // remove the class from the map, and put it back with the fixed name - mMap.remove(fqcn); - mMap.put(param.getCanonicalName(), param); - } - } - } - - private String getEnclosedName(String fqcn) { - int index = fqcn.lastIndexOf('.'); - return fqcn.substring(0, index); - } - - /** - * Finds and loads all classes that derive from a given set of super classes. - * - * @param rootPackage Root package of classes to find. Use an empty string to find everyting. - * @param superClasses The super classes of all the classes to find. - * @return An hash map which keys are the super classes looked for and which values are - * ArrayList of the classes found. The array lists are always created for all the - * valid keys, they are simply empty if no deriving class is found for a given - * super class. - * @throws IOException - * @throws InvalidAttributeValueException - * @throws ClassFormatError - */ - public HashMap> findClassesDerivingFrom(String rootPackage, - String[] superClasses) throws IOException, InvalidAttributeValueException, - ClassFormatError { - HashMap> map = new HashMap>(); - - ArrayList list = new ArrayList(); - list.addAll(mWidgetMap.values()); - map.put(AndroidConstants.CLASS_VIEW, list); - - list = new ArrayList(); - list.addAll(mLayoutMap.values()); - map.put(AndroidConstants.CLASS_VIEWGROUP, list); - - list = new ArrayList(); - list.addAll(mLayoutParamsMap.values()); - map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list); - - return map; - } - - /** - * Returns a {@link IClass} by its fully-qualified name. - * @param className the fully-qualified name of the class to return. - * @throws ClassNotFoundException - */ - public IClass getClass(String className) throws ClassNotFoundException { - return mMap.get(className); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java new file mode 100644 index 0000000..fad4f19 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidJarLoader.java @@ -0,0 +1,430 @@ +/* + * Copyright (C) 2007 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.sdk; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; + +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.management.InvalidAttributeValueException; + +/** + * Custom class loader able to load a class from the SDK jar file. + */ +public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader { + + /** + * Wrapper around a {@link Class} to provide the methods of {@link IClassDescriptor}. + */ + public final static class ClassWrapper implements IClassDescriptor { + private Class mClass; + + public ClassWrapper(Class clazz) { + mClass = clazz; + } + + public String getCanonicalName() { + return mClass.getCanonicalName(); + } + + public IClassDescriptor[] getDeclaredClasses() { + Class[] classes = mClass.getDeclaredClasses(); + IClassDescriptor[] iclasses = new IClassDescriptor[classes.length]; + for (int i = 0 ; i < classes.length ; i++) { + iclasses[i] = new ClassWrapper(classes[i]); + } + + return iclasses; + } + + public IClassDescriptor getEnclosingClass() { + return new ClassWrapper(mClass.getEnclosingClass()); + } + + public String getSimpleName() { + return mClass.getSimpleName(); + } + + public IClassDescriptor getSuperclass() { + return new ClassWrapper(mClass.getSuperclass()); + } + + @Override + public boolean equals(Object clazz) { + if (clazz instanceof ClassWrapper) { + return mClass.equals(((ClassWrapper)clazz).mClass); + } + return super.equals(clazz); + } + + @Override + public int hashCode() { + return mClass.hashCode(); + } + + + public boolean isInstantiable() { + int modifiers = mClass.getModifiers(); + return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true; + } + + public Class wrappedClass() { + return mClass; + } + + } + + private String mOsFrameworkLocation; + + /** A cache for binary data extracted from the zip */ + private final HashMap mEntryCache = new HashMap(); + /** A cache for already defined Classes */ + private final HashMap > mClassCache = new HashMap >(); + + /** + * Creates the class loader by providing the os path to the framework jar archive + * + * @param osFrameworkLocation OS Path of the framework JAR file + */ + public AndroidJarLoader(String osFrameworkLocation) { + super(); + mOsFrameworkLocation = osFrameworkLocation; + } + + public String getSource() { + return mOsFrameworkLocation; + } + + /** + * Pre-loads all class binary data that belong to the given package by reading the archive + * once and caching them internally. + *

          + * This does not actually preload "classes", it just reads the unzipped bytes for a given + * class. To obtain a class, one must call {@link #findClass(String)} later. + *

          + * All classes which package name starts with "packageFilter" will be included and can be + * found later. + *

          + * May throw some exceptions if the framework JAR cannot be read. + * + * @param packageFilter The package that contains all the class data to preload, using a fully + * qualified binary name (.e.g "com.my.package."). The matching algorithm + * is simple "startsWith". Use an empty string to include everything. + * @param taskLabel An optional task name for the sub monitor. Can be null. + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + * @throws IOException + * @throws InvalidAttributeValueException + * @throws ClassFormatError + */ + public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor) + throws IOException, InvalidAttributeValueException, ClassFormatError { + // Transform the package name into a zip entry path + String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ + + SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100); + + // 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. + String entryPath = entry.getName(); + + if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) { + // only accept class files + continue; + } + + // check if it is part of the package to preload + if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) { + continue; + } + String className = entryPathToClassName(entryPath); + + if (!mEntryCache.containsKey(className)) { + long entrySize = entry.getSize(); + if (entrySize > Integer.MAX_VALUE) { + throw new InvalidAttributeValueException(); + } + byte[] data = readZipData(zis, (int)entrySize); + mEntryCache.put(className, data); + } + + // advance 5% of whatever is allocated on the progress bar + progress.setWorkRemaining(100); + progress.worked(5); + progress.subTask(String.format("Preload %1$s", className)); + } + } + + /** + * Finds and loads all classes that derive from a given set of super classes. + *

          + * As a side-effect this will load and cache most, if not all, classes in the input JAR file. + * + * @param packageFilter Base name of package of classes to find. + * Use an empty string to find everyting. + * @param superClasses The super classes of all the classes to find. + * @return An hash map which keys are the super classes looked for and which values are + * ArrayList of the classes found. The array lists are always created for all the + * valid keys, they are simply empty if no deriving class is found for a given + * super class. + * @throws IOException + * @throws InvalidAttributeValueException + * @throws ClassFormatError + */ + public HashMap> findClassesDerivingFrom( + String packageFilter, + String[] superClasses) + throws IOException, InvalidAttributeValueException, ClassFormatError { + + packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ + + HashMap> mClassesFound = + new HashMap>(); + + for (String className : superClasses) { + mClassesFound.put(className, new ArrayList()); + } + + // 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(AndroidConstants.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); + } + 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; + } + } + } + + return mClassesFound; + } + + /** Helper method that converts a Zip entry path into a corresponding + * Java full qualified binary class name. + *

          + * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo". + */ + private String entryPathToClassName(String entryPath) { + return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Finds the class with the specified binary name. + * + * {@inheritDoc} + */ + @Override + protected Class findClass(String name) throws ClassNotFoundException { + try { + // try to find the class in the cache + Class cached_class = mClassCache.get(name); + if (cached_class == ClassNotFoundException.class) { + // we already know we can't find this class, don't try again + throw new ClassNotFoundException(name); + } else if (cached_class != null) { + return cached_class; + } + + // if not found, look it up and cache it + byte[] data = loadClassData(name); + if (data != null) { + return defineAndCacheClass(name, data); + } else { + // if the class can't be found, record a CNFE class in the map so + // that we don't try to reload it next time + mClassCache.put(name, ClassNotFoundException.class); + throw new ClassNotFoundException(name); + } + } catch (ClassNotFoundException e) { + throw e; + } catch (Exception e) { + throw new ClassNotFoundException(e.getMessage()); + } + } + + /** + * Defines a class based on its binary data and caches the resulting class object. + * + * @param name The binary name of the class (i.e. package.class1$class2) + * @param data The binary data from the loader. + * @return The class defined + * @throws ClassFormatError if defineClass failed. + */ + private Class defineAndCacheClass(String name, byte[] data) throws ClassFormatError { + Class cached_class; + cached_class = defineClass(null, data, 0, data.length); + + if (cached_class != null) { + // Add new class to the cache class and remove it from the zip entry data cache + mClassCache.put(name, cached_class); + mEntryCache.remove(name); + } + return cached_class; + } + + /** + * Loads a class data from its binary name. + *

          + * This uses the class binary data that has been preloaded earlier by the preLoadClasses() + * method if possible. + * + * @param className the binary name + * @return an array of bytes representing the class data or null if not found + * @throws InvalidAttributeValueException + * @throws IOException + */ + private synchronized byte[] loadClassData(String className) + throws InvalidAttributeValueException, IOException { + + byte[] data = mEntryCache.get(className); + if (data != null) { + return data; + } + + // The name is a binary name. Something like "android.R", or "android.R$id". + // Make a path out of it. + String entryName = className.replaceAll("\\.", "/") + AndroidConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ + + // create streams to read the intermediary archive + FileInputStream fis = new FileInputStream(mOsFrameworkLocation); + ZipInputStream zis = new ZipInputStream(fis); + + // loop on the entries of the intermediary package and put them in the final package. + ZipEntry entry; + + while ((entry = zis.getNextEntry()) != null) { + // get the name of the entry. + String currEntryName = entry.getName(); + + if (currEntryName.equals(entryName)) { + long entrySize = entry.getSize(); + if (entrySize > Integer.MAX_VALUE) { + throw new InvalidAttributeValueException(); + } + + data = readZipData(zis, (int)entrySize); + return data; + } + } + + return null; + } + + /** + * Reads data for the current entry from the zip input stream. + * + * @param zis The Zip input stream + * @param entrySize The entry size. -1 if unknown. + * @return The new data for the current entry. + * @throws IOException If ZipInputStream.read() fails. + */ + private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException { + int block_size = 1024; + int data_size = entrySize < 1 ? block_size : entrySize; + int offset = 0; + byte[] data = new byte[data_size]; + + while(zis.available() != 0) { + int count = zis.read(data, offset, data_size - offset); + if (count < 0) { // read data is done + break; + } + offset += count; + + if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done + break; + } + + // if we don't know the entry size and we're not done reading, + // expand the data buffer some more. + if (offset >= data_size) { + byte[] temp = new byte[data_size + block_size]; + System.arraycopy(data, 0, temp, 0, data_size); + data_size += block_size; + data = temp; + block_size *= 2; + } + } + + if (offset < data_size) { + // buffer was allocated too large, trim it + byte[] temp = new byte[offset]; + if (offset > 0) { + System.arraycopy(data, 0, temp, 0, offset); + } + data = temp; + } + + return data; + } + + /** + * Returns a {@link IClass} by its fully-qualified name. + * @param className the fully-qualified name of the class to return. + * @throws ClassNotFoundException + */ + public IClassDescriptor getClass(String className) throws ClassNotFoundException { + try { + return new ClassWrapper(loadClass(className)); + } catch (ClassNotFoundException e) { + throw e; // useful for debugging + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java new file mode 100644 index 0000000..60561ab --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetData.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2007 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.sdk; + +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; +import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; +import com.android.layoutlib.api.ILayoutBridge; +import com.android.sdklib.IAndroidTarget; + +import java.util.Hashtable; +import java.util.Map; + +/** + * This class contains the data of an Android Target as loaded from the SDK. + */ +public class AndroidTargetData { + + public final static int DESCRIPTOR_MANIFEST = 1; + public final static int DESCRIPTOR_LAYOUT = 2; + public final static int DESCRIPTOR_MENU = 3; + public final static int DESCRIPTOR_XML = 4; + public final static int DESCRIPTOR_RESOURCES = 5; + public final static int DESCRIPTOR_SEARCHABLE = 6; + public final static int DESCRIPTOR_PREFERENCES = 7; + + public final static class LayoutBridge { + /** Link to the layout bridge */ + public ILayoutBridge bridge; + + public LoadStatus status = LoadStatus.LOADING; + + public ClassLoader classLoader; + } + + private final IAndroidTarget mTarget; + + /** + * mAttributeValues is a map { key => list [ values ] }. + * The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)". + * The attribute namespace prefix must be: + * - "android" for AndroidConstants.NS_RESOURCES + * - "xmlns" for the XMLNS URI. + * + * This is used for attributes that do not have a unique name, but still need to be populated + * with values in the UI. Uniquely named attributes have their values in {@link #mEnumValueMap}. + */ + private final Hashtable mAttributeValues = new Hashtable(); + + private IResourceRepository mSystemResourceRepository; + + private final AndroidManifestDescriptors mManifestDescriptors; + private final LayoutDescriptors mLayoutDescriptors; + private final MenuDescriptors mMenuDescriptors; + private final XmlDescriptors mXmlDescriptors; + + private final Map> mEnumValueMap; + + private final ProjectResources mFrameworkResources; + private final LayoutBridge mLayoutBridge; + + private boolean mLayoutBridgeInit = false; + + /** + * Creates an AndroidTargetData object. + */ + AndroidTargetData(IAndroidTarget androidTarget, + IResourceRepository systemResourceRepository, + AndroidManifestDescriptors manifestDescriptors, + LayoutDescriptors layoutDescriptors, + MenuDescriptors menuDescriptors, + XmlDescriptors xmlDescriptors, + Map> enumValueMap, + String[] permissionValues, + String[] activityIntentActionValues, + String[] broadcastIntentActionValues, + String[] serviceIntentActionValues, + String[] intentCategoryValues, + ProjectResources resources, + LayoutBridge layoutBridge) { + + mTarget = androidTarget; + mSystemResourceRepository = systemResourceRepository; + mManifestDescriptors = manifestDescriptors; + mLayoutDescriptors = layoutDescriptors; + mMenuDescriptors = menuDescriptors; + mXmlDescriptors = xmlDescriptors; + mEnumValueMap = enumValueMap; + mFrameworkResources = resources; + mLayoutBridge = layoutBridge; + + setPermissions(permissionValues); + setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues, + serviceIntentActionValues, intentCategoryValues); + } + + public IResourceRepository getSystemResources() { + return mSystemResourceRepository; + } + + /** + * Returns an {@link IDescriptorProvider} from a given Id. + * The Id can be one of {@link #DESCRIPTOR_MANIFEST}, {@link #DESCRIPTOR_LAYOUT}, + * {@link #DESCRIPTOR_MENU}, or {@link #DESCRIPTOR_XML}. + * All other values will throw an {@link IllegalArgumentException}. + */ + public IDescriptorProvider getDescriptorProvider(int descriptorId) { + switch (descriptorId) { + case DESCRIPTOR_MANIFEST: + return mManifestDescriptors; + case DESCRIPTOR_LAYOUT: + return mLayoutDescriptors; + case DESCRIPTOR_MENU: + return mMenuDescriptors; + case DESCRIPTOR_XML: + return mXmlDescriptors; + case DESCRIPTOR_RESOURCES: + // FIXME: since it's hard-coded the Resources Descriptors are not platform dependent. + return ResourcesDescriptors.getInstance(); + case DESCRIPTOR_PREFERENCES: + return mXmlDescriptors.getPreferencesProvider(); + case DESCRIPTOR_SEARCHABLE: + return mXmlDescriptors.getSearchableProvider(); + default : + throw new IllegalArgumentException(); + } + } + + /** + * Returns the manifest descriptors. + */ + public AndroidManifestDescriptors getManifestDescriptors() { + return mManifestDescriptors; + } + + /** + * Returns the layout Descriptors. + */ + public LayoutDescriptors getLayoutDescriptors() { + return mLayoutDescriptors; + } + + /** + * Returns the menu descriptors. + */ + public MenuDescriptors getMenuDescriptors() { + return mMenuDescriptors; + } + + /** + * Returns the XML descriptors + */ + public XmlDescriptors getXmlDescriptors() { + return mXmlDescriptors; + } + + /** + * Returns this list of possible values for an XML attribute. + *

          This should only be called for attributes for which possible values depend on the + * parent element node. + *

          For attributes that have the same values no matter the parent node, use + * {@link #getEnumValueMap()}. + * @param elementName the name of the element containing the attribute. + * @param attributeName the name of the attribute + * @return an array of String with the possible values, or null if no values were + * found. + */ + public String[] getAttributeValues(String elementName, String attributeName) { + String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$ + return mAttributeValues.get(key); + } + + /** + * Returns this list of possible values for an XML attribute. + *

          This should only be called for attributes for which possible values depend on the + * parent and great-grand-parent element node. + *

          The typical example of this is for the 'name' attribute under + * activity/intent-filter/action + *

          For attributes that have the same values no matter the parent node, use + * {@link #getEnumValueMap()}. + * @param elementName the name of the element containing the attribute. + * @param attributeName the name of the attribute + * @param greatGrandParentElementName the great-grand-parent node. + * @return an array of String with the possible values, or null if no values were + * found. + */ + public String[] getAttributeValues(String elementName, String attributeName, + String greatGrandParentElementName) { + if (greatGrandParentElementName != null) { + String key = String.format("(%1$s,%2$s,%3$s)", //$NON-NLS-1$ + greatGrandParentElementName, elementName, attributeName); + String[] values = mAttributeValues.get(key); + if (values != null) { + return values; + } + } + + return getAttributeValues(elementName, attributeName); + } + + /** + * Returns the enum values map. + *

          The map defines the possible values for XML attributes. The key is the attribute name + * and the value is a map of (string, integer) in which the key (string) is the name of + * the value, and the Integer is the numerical value in the compiled binary XML files. + */ + public Map> getEnumValueMap() { + return mEnumValueMap; + } + + /** + * Returns the {@link ProjectResources} containing the Framework Resources. + */ + public ProjectResources getFrameworkResources() { + return mFrameworkResources; + } + + /** + * Returns a {@link LayoutBridge} object possibly containing a {@link ILayoutBridge} object. + *

          If {@link LayoutBridge#bridge} is null, {@link LayoutBridge#status} will + * contain the reason (either {@link LoadStatus#LOADING} or {@link LoadStatus#FAILED}). + *

          Valid {@link ILayoutBridge} objects are always initialized before being returned. + */ + public synchronized LayoutBridge getLayoutBridge() { + if (mLayoutBridgeInit == false && mLayoutBridge.bridge != null) { + mLayoutBridge.bridge.init(mTarget.getPath(IAndroidTarget.FONTS), + getEnumValueMap()); + mLayoutBridgeInit = true; + } + return mLayoutBridge; + } + + /** + * Sets the permission values + * @param permissionValues the list of permissions + */ + private void setPermissions(String[] permissionValues) { + setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$ + setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$ + setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$ + setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$ + setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$ + setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$ + } + + private void setIntentFilterActionsAndCategories(String[] activityIntentActions, + String[] broadcastIntentActions, String[] serviceIntentActions, + String[] intentCategoryValues) { + setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$ + setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$ + setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$ + setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$ + } + + /** + * Sets a (name, values) pair in the hash map. + *

          + * If the name is already present in the map, it is first removed. + * @param name the name associated with the values. + * @param values The values to add. + */ + private void setValues(String name, String[] values) { + mAttributeValues.remove(name); + mAttributeValues.put(name, values); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java new file mode 100644 index 0000000..232b9e8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/AndroidTargetParser.java @@ -0,0 +1,651 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.AttrsXmlParser; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; +import com.android.layoutlib.api.ILayoutBridge; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.management.InvalidAttributeValueException; + +/** + * Parser for the platform data in an SDK. + *

          + * This gather the following information: + *

            + *
          • Resource ID from android.R
          • + *
          • The list of permissions values from android.Manifest$permission
          • + *
          • + *
          + */ +public final class AndroidTargetParser { + + private static final String TAG = "Framework Resource Parser"; + private final IAndroidTarget mAndroidTarget; + + /** + * Creates a platform data parser. + */ + public AndroidTargetParser(IAndroidTarget platformTarget) { + mAndroidTarget = platformTarget; + } + + /** + * Parses the framework, collects all interesting information and stores them in the + * {@link FrameworkResourceManager} given to the constructor. + * + * @param osSdkPath the OS path of the SDK directory. + * @param resourceManager the {@link FrameworkResourceManager} that will store the parsed + * resources. + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + * @return True if the SDK path was valid and parsing has been attempted. + */ + public IStatus run(IProgressMonitor monitor) { + try { + SubMonitor progress = SubMonitor.convert(monitor, + String.format("Parsing SDK %1$s", mAndroidTarget.getName()), + 120); + + AndroidJarLoader classLoader = + new AndroidJarLoader(mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR)); + + preload(classLoader, progress.newChild(40, SubMonitor.SUPPRESS_NONE)); + progress.setWorkRemaining(80); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // get the resource Ids. + progress.subTask("Resource IDs"); + IResourceRepository frameworkRepository = collectResourceIds(classLoader); + progress.worked(5); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // get the permissions + progress.subTask("Permissions"); + String[] permissionValues = collectPermissions(classLoader); + progress.worked(5); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // get the action and category values for the Intents. + progress.subTask("Intents"); + ArrayList activity_actions = new ArrayList(); + ArrayList broadcast_actions = new ArrayList(); + ArrayList service_actions = new ArrayList(); + ArrayList categories = new ArrayList(); + collectIntentFilterActionsAndCategories(activity_actions, broadcast_actions, + service_actions, categories); + progress.worked(5); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // gather the attribute definition + progress.subTask("Attributes definitions"); + AttrsXmlParser attrsXmlParser = new AttrsXmlParser( + mAndroidTarget.getPath(IAndroidTarget.ATTRIBUTES)); + attrsXmlParser.preload(); + + progress.subTask("Manifest definitions"); + AttrsXmlParser attrsManifestXmlParser = new AttrsXmlParser( + mAndroidTarget.getPath(IAndroidTarget.MANIFEST_ATTRIBUTES), + attrsXmlParser); + attrsManifestXmlParser.preload(); + + Collection mainList = new ArrayList(); + Collection groupList = new ArrayList(); + + // collect the layout/widgets classes + progress.subTask("Widgets and layouts"); + collectLayoutClasses(classLoader, attrsXmlParser, mainList, groupList, + progress.newChild(40)); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + ViewClassInfo[] layoutViewsInfo = mainList.toArray(new ViewClassInfo[mainList.size()]); + ViewClassInfo[] layoutGroupsInfo = groupList.toArray( + new ViewClassInfo[groupList.size()]); + + // collect the preferences classes. + mainList.clear(); + groupList.clear(); + collectPreferenceClasses(classLoader, attrsXmlParser, mainList, groupList, + progress.newChild(5)); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + ViewClassInfo[] preferencesInfo = mainList.toArray(new ViewClassInfo[mainList.size()]); + ViewClassInfo[] preferenceGroupsInfo = groupList.toArray( + new ViewClassInfo[groupList.size()]); + + Map xmlMenuMap = collectMenuDefinitions(attrsXmlParser); + Map xmlSearchableMap = collectSearchableDefinitions( + attrsXmlParser); + Map manifestMap = collectManifestDefinitions( + attrsManifestXmlParser); + Map> enumValueMap = attrsXmlParser.getEnumFlagValues(); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + // From the information that was collected, create the pieces that will be put in + // the PlatformData object. + AndroidManifestDescriptors manifestDescriptors = new AndroidManifestDescriptors(); + manifestDescriptors.updateDescriptors(manifestMap); + progress.worked(10); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + LayoutDescriptors layoutDescriptors = new LayoutDescriptors(); + layoutDescriptors.updateDescriptors(layoutViewsInfo, layoutGroupsInfo); + progress.worked(10); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + MenuDescriptors menuDescriptors = new MenuDescriptors(); + menuDescriptors.updateDescriptors(xmlMenuMap); + progress.worked(10); + + if (progress.isCanceled()) { + return Status.CANCEL_STATUS; + } + + XmlDescriptors xmlDescriptors = new XmlDescriptors(); + xmlDescriptors.updateDescriptors(xmlSearchableMap, preferencesInfo, + preferenceGroupsInfo); + progress.worked(10); + + // load the framework resources. + ProjectResources resources = ResourceManager.getInstance().loadFrameworkResources( + mAndroidTarget); + progress.worked(10); + + // now load the layout lib bridge + LayoutBridge layoutBridge = loadLayoutBridge(); + progress.worked(10); + + // and finally create the PlatformData with all that we loaded. + AndroidTargetData targetData = new AndroidTargetData(mAndroidTarget, + frameworkRepository, + manifestDescriptors, + layoutDescriptors, + menuDescriptors, + xmlDescriptors, + enumValueMap, + permissionValues, + activity_actions.toArray(new String[activity_actions.size()]), + broadcast_actions.toArray(new String[broadcast_actions.size()]), + service_actions.toArray(new String[service_actions.size()]), + categories.toArray(new String[categories.size()]), + resources, + layoutBridge); + + Sdk.getCurrent().setTargetData(mAndroidTarget, targetData); + + return Status.OK_STATUS; + } catch (Exception e) { + AdtPlugin.logAndPrintError(e, TAG, "SDK parser failed"); //$NON-NLS-1$ + AdtPlugin.printToConsole("SDK parser failed", e.getMessage()); + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, "SDK parser failed", e); + } finally { + if (monitor != null) { + monitor.done(); + } + } + } + + /** + * Preloads all "interesting" classes from the framework SDK jar. + *

          + * Currently this preloads all classes from the framework jar + * + * @param classLoader The framework SDK jar classloader + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + */ + private void preload(AndroidJarLoader classLoader, IProgressMonitor monitor) { + try { + classLoader.preLoadClasses("" /* all classes */, //$NON-NLS-1$ + mAndroidTarget.getName(), // monitor task label + monitor); + } catch (InvalidAttributeValueException e) { + AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$ + } catch (IOException e) { + AdtPlugin.log(e, "Problem preloading classes"); //$NON-NLS-1$ + } + } + + /** + * Creates an IResourceRepository for the framework resources. + * + * @param classLoader The framework SDK jar classloader + * @return a map of the resources, or null if it failed. + */ + private IResourceRepository collectResourceIds( + AndroidJarLoader classLoader) { + try { + Class r = classLoader.loadClass(AndroidConstants.CLASS_R); + + if (r != null) { + Map> map = parseRClass(r); + if (map != null) { + return new FrameworkResourceRepository(map); + } + } + } catch (ClassNotFoundException e) { + AdtPlugin.logAndPrintError(e, TAG, + "Collect resource IDs failed, class %1$s not found in %2$s", //$NON-NLS-1$ + AndroidConstants.CLASS_R, + mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR)); + } + + return null; + } + + /** + * Parse the R class and build the resource map. + * + * @param rClass the Class object representing the Resources. + * @return a map of the resource or null + */ + private Map> parseRClass(Class rClass) { + // get the sub classes. + Class[] classes = rClass.getClasses(); + + if (classes.length > 0) { + HashMap> map = + new HashMap>(); + + // get the fields of each class. + for (int c = 0 ; c < classes.length ; c++) { + Class subClass = classes[c]; + String name = subClass.getSimpleName(); + + // get the matching ResourceType + ResourceType type = ResourceType.getEnum(name); + if (type != null) { + List list = new ArrayList(); + map.put(type, list); + + Field[] fields = subClass.getFields(); + + for (Field f : fields) { + list.add(new ResourceItem(f.getName())); + } + } + } + + return map; + } + + return null; + } + + /** + * Loads, collects and returns the list of default permissions from the framework. + * + * @param classLoader The framework SDK jar classloader + * @return a non null (but possibly empty) array containing the permission values. + */ + private String[] collectPermissions(AndroidJarLoader classLoader) { + try { + Class permissionClass = + classLoader.loadClass(AndroidConstants.CLASS_MANIFEST_PERMISSION); + + if (permissionClass != null) { + ArrayList list = new ArrayList(); + + Field[] fields = permissionClass.getFields(); + + for (Field f : fields) { + int modifiers = f.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers) && + Modifier.isPublic(modifiers)) { + try { + Object value = f.get(null); + if (value instanceof String) { + list.add((String)value); + } + } catch (IllegalArgumentException e) { + // since we provide null this should not happen + } catch (IllegalAccessException e) { + // if the field is inaccessible we ignore it. + } catch (NullPointerException npe) { + // looks like this is not a static field. we can ignore. + } catch (ExceptionInInitializerError eiie) { + // lets just ignore the field again + } + } + } + + return list.toArray(new String[list.size()]); + } + } catch (ClassNotFoundException e) { + AdtPlugin.logAndPrintError(e, TAG, + "Collect permissions failed, class %1$s not found in %2$s", //$NON-NLS-1$ + AndroidConstants.CLASS_MANIFEST_PERMISSION, + mAndroidTarget.getPath(IAndroidTarget.ANDROID_JAR)); + } + + return new String[0]; + } + + /** + * Loads and collects the action and category default values from the framework. + * The values are added to the actions and categories lists. + * + * @param osLibPath The OS path to the SDK tools/lib folder, ending with a separator. + * @param activityActions the list which will receive the activity action values. + * @param broadcastActions the list which will receive the broadcast action values. + * @param serviceActions the list which will receive the service action values. + * @param categories the list which will receive the category values. + */ + private void collectIntentFilterActionsAndCategories(ArrayList activityActions, + ArrayList broadcastActions, + ArrayList serviceActions, ArrayList categories) { + collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_ACTIVITY), + activityActions); + collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_BROADCAST), + broadcastActions); + collectValues(mAndroidTarget.getPath(IAndroidTarget.ACTIONS_SERVICE), + serviceActions); + collectValues(mAndroidTarget.getPath(IAndroidTarget.CATEGORIES), + categories); + } + + /** + * Collects values from a text file located in the SDK + * @param osFilePath The path to the text file. + * @param values the {@link ArrayList} to fill with the values. + */ + private void collectValues(String osFilePath, ArrayList values) { + FileReader fr = null; + BufferedReader reader = null; + try { + fr = new FileReader(osFilePath); + reader = new BufferedReader(fr); + + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.length() > 0 && line.startsWith("#") == false) { //$NON-NLS-1$ + values.add(line); + } + } + } catch (IOException e) { + AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (IOException e) { + AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ + } + + try { + if (fr != null) { + fr.close(); + } + } catch (IOException e) { + AdtPlugin.log(e, "Failed to read SDK values"); //$NON-NLS-1$ + } + } + } + + /** + * Collects all layout classes information from the class loader and the + * attrs.xml and sets the corresponding structures in the resource manager. + * + * @param classLoader The framework SDK jar classloader in case we cannot get the widget from + * the platform directly + * @param attrsXmlParser The parser of the attrs.xml file + * @param mainList the Collection to receive the main list of {@link ViewClassInfo}. + * @param groupList the Collection to receive the group list of {@link ViewClassInfo}. + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + */ + private void collectLayoutClasses(AndroidJarLoader classLoader, + AttrsXmlParser attrsXmlParser, + Collection mainList, Collection groupList, + IProgressMonitor monitor) { + LayoutParamsParser ldp = null; + try { + WidgetClassLoader loader = new WidgetClassLoader( + mAndroidTarget.getPath(IAndroidTarget.WIDGETS)); + if (loader.parseWidgetList(monitor)) { + ldp = new LayoutParamsParser(loader, attrsXmlParser); + } + // if the parsing failed, we'll use the old loader below. + } catch (FileNotFoundException e) { + AdtPlugin.log(e, "Android Framework Parser"); //$NON-NLS-1$ + // the file does not exist, we'll use the old loader below. + } + + if (ldp == null) { + ldp = new LayoutParamsParser(classLoader, attrsXmlParser); + } + ldp.parseLayoutClasses(monitor); + + List views = ldp.getViews(); + List groups = ldp.getGroups(); + + if (views != null && groups != null) { + mainList.addAll(views); + groupList.addAll(groups); + } + } + + /** + * Collects all preferences definition information from the attrs.xml and + * sets the corresponding structures in the resource manager. + * + * @param classLoader The framework SDK jar classloader + * @param attrsXmlParser The parser of the attrs.xml file + * @param mainList the Collection to receive the main list of {@link ViewClassInfo}. + * @param groupList the Collection to receive the group list of {@link ViewClassInfo}. + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + */ + private void collectPreferenceClasses(AndroidJarLoader classLoader, + AttrsXmlParser attrsXmlParser, Collection mainList, + Collection groupList, IProgressMonitor monitor) { + LayoutParamsParser ldp = new LayoutParamsParser(classLoader, attrsXmlParser); + + try { + ldp.parsePreferencesClasses(monitor); + + List prefs = ldp.getViews(); + List groups = ldp.getGroups(); + + if (prefs != null && groups != null) { + mainList.addAll(prefs); + groupList.addAll(groups); + } + } catch (NoClassDefFoundError e) { + AdtPlugin.logAndPrintError(e, TAG, + "Collect preferences failed, class %1$s not found in %2$s", + e.getMessage(), + classLoader.getSource()); + } catch (Throwable e) { + AdtPlugin.log(e, "Android Framework Parser: failed to collect preference classes"); //$NON-NLS-1$ + AdtPlugin.printErrorToConsole("Android Framework Parser", + "failed to collect preference classes"); + } + } + + /** + * Collects all menu definition information from the attrs.xml and returns it. + * + * @param attrsXmlParser The parser of the attrs.xml file + */ + private Map collectMenuDefinitions( + AttrsXmlParser attrsXmlParser) { + Map map = attrsXmlParser.getDeclareStyleableList(); + Map map2 = new HashMap(); + for (String key : new String[] { "Menu", //$NON-NLS-1$ + "MenuItem", //$NON-NLS-1$ + "MenuGroup" }) { //$NON-NLS-1$ + if (map.containsKey(key)) { + map2.put(key, map.get(key)); + } else { + AdtPlugin.log(IStatus.WARNING, + "Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath()); + AdtPlugin.printErrorToConsole("Android Framework Parser", + String.format("Menu declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath())); + } + } + + return Collections.unmodifiableMap(map2); + } + + /** + * Collects all searchable definition information from the attrs.xml and returns it. + * + * @param attrsXmlParser The parser of the attrs.xml file + */ + private Map collectSearchableDefinitions( + AttrsXmlParser attrsXmlParser) { + Map map = attrsXmlParser.getDeclareStyleableList(); + Map map2 = new HashMap(); + for (String key : new String[] { "Searchable", //$NON-NLS-1$ + "SearchableActionKey" }) { //$NON-NLS-1$ + if (map.containsKey(key)) { + map2.put(key, map.get(key)); + } else { + AdtPlugin.log(IStatus.WARNING, + "Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath()); + AdtPlugin.printErrorToConsole("Android Framework Parser", + String.format("Searchable declare-styleable %1$s not found in file %2$s", //$NON-NLS-1$ + key, attrsXmlParser.getOsAttrsXmlPath())); + } + } + + return Collections.unmodifiableMap(map2); + } + + /** + * Collects all manifest definition information from the attrs_manifest.xml and returns it. + */ + private Map collectManifestDefinitions( + AttrsXmlParser attrsXmlParser) { + + return attrsXmlParser.getDeclareStyleableList(); + } + + /** + * Loads the layout bridge from the dynamically loaded layoutlib.jar + */ + private LayoutBridge loadLayoutBridge() { + LayoutBridge layoutBridge = new LayoutBridge(); + + try { + // get the URL for the file. + File f = new File(mAndroidTarget.getPath(IAndroidTarget.LAYOUT_LIB)); + if (f.isFile() == false) { + AdtPlugin.log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$ + } else { + URL url = f.toURL(); + + // create a class loader. Because this jar reference interfaces + // that are in the editors plugin, it's important to provide + // a parent class loader. + layoutBridge.classLoader = new URLClassLoader(new URL[] { url }, + this.getClass().getClassLoader()); + + // load the class + Class clazz = layoutBridge.classLoader.loadClass(AndroidConstants.CLASS_BRIDGE); + if (clazz != null) { + // instantiate an object of the class. + Constructor constructor = clazz.getConstructor(); + if (constructor != null) { + Object bridge = constructor.newInstance(); + if (bridge instanceof ILayoutBridge) { + layoutBridge.bridge = (ILayoutBridge)bridge; + } + } + } + + if (layoutBridge.bridge == null) { + layoutBridge.status = LoadStatus.FAILED; + AdtPlugin.log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$ + } else { + layoutBridge.status = LoadStatus.LOADED; + } + } + } catch (Throwable t) { + layoutBridge.status = LoadStatus.FAILED; + // log the error. + AdtPlugin.log(t, "Failed to load the LayoutLib"); + } + + return layoutBridge; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/FrameworkResourceRepository.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/FrameworkResourceRepository.java new file mode 100644 index 0000000..f4b10df --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/FrameworkResourceRepository.java @@ -0,0 +1,76 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Implementation of the {@link IResourceRepository} interface to hold the system resource Ids + * parsed by {@link AndroidTargetParser}. + */ +final class FrameworkResourceRepository implements IResourceRepository { + + private Map> mResourcesMap; + + public FrameworkResourceRepository(Map> systemResourcesMap) { + mResourcesMap = systemResourcesMap; + } + + public ResourceType[] getAvailableResourceTypes() { + if (mResourcesMap != null) { + Set types = mResourcesMap.keySet(); + + if (types != null) { + return types.toArray(new ResourceType[types.size()]); + } + } + + return null; + } + + public ResourceItem[] getResources(ResourceType type) { + if (mResourcesMap != null) { + List items = mResourcesMap.get(type); + + if (items != null) { + return items.toArray(new ResourceItem[items.size()]); + } + } + + return null; + } + + public boolean hasResources(ResourceType type) { + if (mResourcesMap != null) { + List items = mResourcesMap.get(type); + + return (items != null && items.size() > 0); + } + + return false; + } + + public boolean isSystemRepository() { + return true; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java new file mode 100644 index 0000000..50d319e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/IAndroidClassLoader.java @@ -0,0 +1,81 @@ +/* + * 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.sdk; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.management.InvalidAttributeValueException; + +/** + * Classes which implements this interface provide methods to access framework resource + * data loaded from the SDK. + */ +public interface IAndroidClassLoader { + + /** + * Classes which implement this interface provide methods to describe a class. + */ + public interface IClassDescriptor { + + String getCanonicalName(); + + IClassDescriptor getSuperclass(); + + String getSimpleName(); + + IClassDescriptor getEnclosingClass(); + + IClassDescriptor[] getDeclaredClasses(); + + boolean isInstantiable(); + } + + /** + * Finds and loads all classes that derive from a given set of super classes. + * + * @param rootPackage Root package of classes to find. Use an empty string to find everyting. + * @param superClasses The super classes of all the classes to find. + * @return An hash map which keys are the super classes looked for and which values are + * ArrayList of the classes found. The array lists are always created for all the + * valid keys, they are simply empty if no deriving class is found for a given + * super class. + * @throws IOException + * @throws InvalidAttributeValueException + * @throws ClassFormatError + */ + public HashMap> findClassesDerivingFrom( + String rootPackage, String[] superClasses) + throws IOException, InvalidAttributeValueException, ClassFormatError; + + /** + * Returns a {@link IClass} by its fully-qualified name. + * @param className the fully-qualified name of the class to return. + * @throws ClassNotFoundException + */ + public IClassDescriptor getClass(String className) throws ClassNotFoundException; + + /** + * Returns a string indicating the source of the classes, typically for debugging + * or in error messages. This would typically be a JAR file name or some kind of + * identifier that would mean something to the user when looking at error messages. + * + * @return An informal string representing the source of the classes. + */ + public String getSource(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LayoutParamsParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LayoutParamsParser.java new file mode 100644 index 0000000..dc600d7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LayoutParamsParser.java @@ -0,0 +1,372 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.AttrsXmlParser; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.management.InvalidAttributeValueException; + +/* + * TODO: refactor this. Could use some cleanup. + */ + +/** + * Parser for the framework library. + *

          + * This gather the following information: + *

            + *
          • Resource ID from android.R
          • + *
          • The list of permissions values from android.Manifest$permission
          • + *
          • + *
          + */ +public class LayoutParamsParser { + + /** + * Class extending {@link ViewClassInfo} by adding the notion of instantiability. + * {@link LayoutParamsParser#getViews()} and {@link LayoutParamsParser#getGroups()} should + * only return classes that can be instantiated. + */ + final static class ExtViewClassInfo extends ViewClassInfo { + + private boolean mIsInstantiable; + + ExtViewClassInfo(boolean instantiable, boolean isLayout, String canonicalClassName, + String shortClassName) { + super(isLayout, canonicalClassName, shortClassName); + mIsInstantiable = instantiable; + } + + boolean isInstantiable() { + return mIsInstantiable; + } + } + + /* Note: protected members/methods are overridden in unit tests */ + + /** Reference to android.view.View */ + protected IClassDescriptor mTopViewClass; + /** Reference to android.view.ViewGroup */ + protected IClassDescriptor mTopGroupClass; + /** Reference to android.view.ViewGroup$LayoutParams */ + protected IClassDescriptor mTopLayoutParamsClass; + + /** Input list of all classes deriving from android.view.View */ + protected ArrayList mViewList; + /** Input list of all classes deriving from android.view.ViewGroup */ + protected ArrayList mGroupList; + + /** Output map of FQCN => info on View classes */ + protected TreeMap mViewMap; + /** Output map of FQCN => info on ViewGroup classes */ + protected TreeMap mGroupMap; + /** Output map of FQCN => info on LayoutParams classes */ + protected HashMap mLayoutParamsMap; + + /** The attrs.xml parser */ + protected AttrsXmlParser mAttrsXmlParser; + + /** The android.jar class loader */ + protected IAndroidClassLoader mClassLoader; + + /** + * Instantiate a new LayoutParamsParser. + * @param classLoader The android.jar class loader + * @param attrsXmlParser The parser of the attrs.xml file + */ + public LayoutParamsParser(IAndroidClassLoader classLoader, + AttrsXmlParser attrsXmlParser) { + mClassLoader = classLoader; + mAttrsXmlParser = attrsXmlParser; + } + + /** Returns the map of FQCN => info on View classes */ + public List getViews() { + return getInstantiables(mViewMap); + } + + /** Returns the map of FQCN => info on ViewGroup classes */ + public List getGroups() { + return getInstantiables(mGroupMap); + } + + /** + * TODO: doc here. + *

          + * Note: on output we should have NO dependency on {@link IClassDescriptor}, + * otherwise we wouldn't be able to unload the class loader later. + *

          + * Note on Vocabulary: FQCN=Fully Qualified Class Name (e.g. "my.package.class$innerClass") + * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. + */ + public void parseLayoutClasses(IProgressMonitor monitor) { + parseClasses(monitor, + AndroidConstants.CLASS_VIEW, + AndroidConstants.CLASS_VIEWGROUP, + AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS); + } + + public void parsePreferencesClasses(IProgressMonitor monitor) { + parseClasses(monitor, + AndroidConstants.CLASS_PREFERENCE, + AndroidConstants.CLASS_PREFERENCEGROUP, + null /* paramsClassName */ ); + } + + private void parseClasses(IProgressMonitor monitor, + String rootClassName, + String groupClassName, + String paramsClassName) { + try { + SubMonitor progress = SubMonitor.convert(monitor, 100); + + String[] superClasses = new String[2 + (paramsClassName == null ? 0 : 1)]; + superClasses[0] = groupClassName; + superClasses[1] = rootClassName; + if (paramsClassName != null) { + superClasses[2] = paramsClassName; + } + HashMap> found = + mClassLoader.findClassesDerivingFrom("android.", superClasses); + mTopViewClass = mClassLoader.getClass(rootClassName); + mTopGroupClass = mClassLoader.getClass(groupClassName); + if (paramsClassName != null) { + mTopLayoutParamsClass = mClassLoader.getClass(paramsClassName); + } + + mViewList = found.get(rootClassName); + mGroupList = found.get(groupClassName); + + mViewMap = new TreeMap(); + mGroupMap = new TreeMap(); + if (mTopLayoutParamsClass != null) { + mLayoutParamsMap = new HashMap(); + } + + // Add top classes to the maps since by design they are not listed in classes deriving + // from themselves. + addGroup(mTopGroupClass); + addView(mTopViewClass); + + // ViewGroup derives from View + mGroupMap.get(groupClassName).setSuperClass( + mViewMap.get(rootClassName)); + + progress.setWorkRemaining(mGroupList.size() + mViewList.size()); + + for (IClassDescriptor groupChild : mGroupList) { + addGroup(groupChild); + progress.worked(1); + } + + for (IClassDescriptor viewChild : mViewList) { + if (viewChild != mTopGroupClass) { + addView(viewChild); + } + progress.worked(1); + } + } catch (ClassNotFoundException e) { + AdtPlugin.log(e, "Problem loading class %1$s or %2$s", //$NON-NLS-1$ + rootClassName, groupClassName); + } catch (InvalidAttributeValueException e) { + AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ + } catch (ClassFormatError e) { + AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ + } catch (IOException e) { + AdtPlugin.log(e, "Problem loading classes"); //$NON-NLS-1$ + } + } + + /** + * Parses a View class and adds a ExtViewClassInfo for it in mViewMap. + * It calls itself recursively to handle super classes which are also Views. + */ + private ExtViewClassInfo addView(IClassDescriptor viewClass) { + String fqcn = viewClass.getCanonicalName(); + if (mViewMap.containsKey(fqcn)) { + return mViewMap.get(fqcn); + } else if (mGroupMap.containsKey(fqcn)) { + return mGroupMap.get(fqcn); + } + + ExtViewClassInfo info = new ExtViewClassInfo(viewClass.isInstantiable(), + false /* layout */, fqcn, viewClass.getSimpleName()); + mViewMap.put(fqcn, info); + + // All view classes derive from mTopViewClass by design. + // Do not lookup the super class for mTopViewClass itself. + if (viewClass.equals(mTopViewClass) == false) { + IClassDescriptor superClass = viewClass.getSuperclass(); + ExtViewClassInfo superClassInfo = addView(superClass); + info.setSuperClass(superClassInfo); + } + + mAttrsXmlParser.loadViewAttributes(info); + return info; + } + + /** + * Parses a ViewGroup class and adds a ExtViewClassInfo for it in mGroupMap. + * It calls itself recursively to handle super classes which are also ViewGroups. + */ + private ExtViewClassInfo addGroup(IClassDescriptor groupClass) { + String fqcn = groupClass.getCanonicalName(); + if (mGroupMap.containsKey(fqcn)) { + return mGroupMap.get(fqcn); + } + + ExtViewClassInfo info = new ExtViewClassInfo(groupClass.isInstantiable(), + true /* layout */, fqcn, groupClass.getSimpleName()); + mGroupMap.put(fqcn, info); + + // All groups derive from android.view.ViewGroup, which in turns derives from + // android.view.View (i.e. mTopViewClass here). So the only group that can have View as + // its super class is the ViewGroup base class and we don't try to resolve it since groups + // are loaded before views. + IClassDescriptor superClass = groupClass.getSuperclass(); + + // Assertion: at this point, we should have + // superClass != mTopViewClass || fqcn.equals(AndroidConstants.CLASS_VIEWGROUP); + + if (superClass != null && superClass.equals(mTopViewClass) == false) { + ExtViewClassInfo superClassInfo = addGroup(superClass); + + // Assertion: we should have superClassInfo != null && superClassInfo != info; + if (superClassInfo != null && superClassInfo != info) { + info.setSuperClass(superClassInfo); + } + } + + mAttrsXmlParser.loadViewAttributes(info); + if (mTopLayoutParamsClass != null) { + info.setLayoutParams(addLayoutParams(groupClass)); + } + return info; + } + + /** + * Parses a ViewGroup class and returns an info object on its inner LayoutParams. + * + * @return The {@link LayoutParamsInfo} for the ViewGroup class or null. + */ + private LayoutParamsInfo addLayoutParams(IClassDescriptor groupClass) { + + // Is there a LayoutParams in this group class? + IClassDescriptor layoutParamsClass = findLayoutParams(groupClass); + + // if there's no layout data in the group class, link to the one from the + // super class. + if (layoutParamsClass == null) { + for (IClassDescriptor superClass = groupClass.getSuperclass(); + layoutParamsClass == null && + superClass != null && + superClass.equals(mTopViewClass) == false; + superClass = superClass.getSuperclass()) { + layoutParamsClass = findLayoutParams(superClass); + } + } + + if (layoutParamsClass != null) { + return getLayoutParamsInfo(layoutParamsClass); + } + + return null; + } + + /** + * Parses a LayoutParams class and returns a LayoutParamsInfo object for it. + * It calls itself recursively to handle the super class of the LayoutParams. + */ + private LayoutParamsInfo getLayoutParamsInfo(IClassDescriptor layoutParamsClass) { + String fqcn = layoutParamsClass.getCanonicalName(); + LayoutParamsInfo layoutParamsInfo = mLayoutParamsMap.get(fqcn); + + if (layoutParamsInfo != null) { + return layoutParamsInfo; + } + + // Find the link on the LayoutParams super class + LayoutParamsInfo superClassInfo = null; + if (layoutParamsClass.equals(mTopLayoutParamsClass) == false) { + IClassDescriptor superClass = layoutParamsClass.getSuperclass(); + superClassInfo = getLayoutParamsInfo(superClass); + } + + // Find the link on the enclosing ViewGroup + ExtViewClassInfo enclosingGroupInfo = addGroup(layoutParamsClass.getEnclosingClass()); + + layoutParamsInfo = new ExtViewClassInfo.LayoutParamsInfo( + enclosingGroupInfo, layoutParamsClass.getSimpleName(), superClassInfo); + mLayoutParamsMap.put(fqcn, layoutParamsInfo); + + mAttrsXmlParser.loadLayoutParamsAttributes(layoutParamsInfo); + + return layoutParamsInfo; + } + + /** + * Given a ViewGroup-derived class, looks for an inner class named LayoutParams + * and if found returns its class definition. + *

          + * This uses the actual defined inner classes and does not look at inherited classes. + * + * @param groupClass The ViewGroup derived class + * @return The Class of the inner LayoutParams or null if none is declared. + */ + private IClassDescriptor findLayoutParams(IClassDescriptor groupClass) { + IClassDescriptor[] innerClasses = groupClass.getDeclaredClasses(); + for (IClassDescriptor innerClass : innerClasses) { + if (innerClass.getSimpleName().equals(AndroidConstants.CLASS_LAYOUTPARAMS)) { + return innerClass; + } + } + return null; + } + + /** + * Computes and return a list of ViewClassInfo from a map by filtering out the class that + * cannot be instantiated. + */ + private List getInstantiables(SortedMap map) { + Collection values = map.values(); + ArrayList list = new ArrayList(); + + for (ExtViewClassInfo info : values) { + if (info.isInstantiable()) { + list.add(info); + } + } + + return list; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LoadStatus.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LoadStatus.java new file mode 100644 index 0000000..6bf0272 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/LoadStatus.java @@ -0,0 +1,24 @@ +/* + * 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.sdk; + +/** + * Enum for loading status of various SDK parts. + */ +public enum LoadStatus { + LOADING, LOADED, FAILED; +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java new file mode 100644 index 0000000..3b9d10e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/Sdk.java @@ -0,0 +1,282 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.project.internal.AndroidClasspathContainerInitializer; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.ISdkLog; +import com.android.sdklib.SdkConstants; +import com.android.sdklib.SdkManager; +import com.android.sdklib.project.ProjectProperties; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Central point to load, manipulate and deal with the Android SDK. Only one SDK can be used + * at the same time. + * + * To start using an SDK, call {@link #loadSdk(String)} which returns the instance of + * the Sdk object. + * + * To get the list of platforms present in the SDK, call {@link #getPlatforms()}. + * To get the list of add-ons present in the SDK, call {@link #getAddons()}. + * + */ +public class Sdk { + private final static String PROPERTY_PROJECT_TARGET = "androidTarget"; //$NON-NLS-1$ + + private static Sdk sCurrentSdk = null; + + private final SdkManager mManager; + private final HashMap mProjectMap = + new HashMap(); + private final HashMap mTargetMap = + new HashMap(); + private final String mDocBaseUrl; + + /** + * Loads an SDK and returns an {@link Sdk} object if success. + * @param sdkLocation the OS path to the SDK. + */ + public static Sdk loadSdk(String sdkLocation) { + if (sCurrentSdk != null) { + // manual unload? + sCurrentSdk = null; + } + + final ArrayList logMessages = new ArrayList(); + ISdkLog log = new ISdkLog() { + public void error(String errorFormat, Object... arg) { + logMessages.add(String.format(errorFormat, arg)); + } + public void warning(String warningFormat, Object... arg) { + logMessages.add(String.format(warningFormat, arg)); + } + }; + + // get an SdkManager object for the location + SdkManager manager = SdkManager.createManager(sdkLocation, log); + if (manager != null) { + sCurrentSdk = new Sdk(manager); + return sCurrentSdk; + } else { + StringBuilder sb = new StringBuilder("Error Loading the SDK:\n"); + for (String msg : logMessages) { + sb.append('\n'); + sb.append(msg); + } + AdtPlugin.displayError("Android SDK", sb.toString()); + } + return null; + } + + /** + * Returns the current {@link Sdk} object. + */ + public static Sdk getCurrent() { + return sCurrentSdk; + } + + /** + * Returns the URL to the local documentation. + * Can return null if no documentation is found in the current SDK. + * + * @return A file:// URL on the local documentation folder if it exists or null. + */ + public String getDocumentationBaseUrl() { + return mDocBaseUrl; + } + + /** + * Returns the list of targets that are available in the SDK. + */ + public IAndroidTarget[] getTargets() { + return mManager.getTargets(); + } + + /** + * Returns a target from a hash that was generated by {@link IAndroidTarget#hashString()}. + * @param hash the hash + */ + public IAndroidTarget getTargetFromHashString(String hash) { + return mManager.getTargetFromHashString(hash); + } + + /** + * Associates an {@link IProject} and an {@link IAndroidTarget}. + */ + public void setProject(IProject project, IAndroidTarget target) { + synchronized (mProjectMap) { + // look for the current target of the project + IAndroidTarget previousTarget = mProjectMap.get(project); + + if (target != previousTarget) { + // save the target hash string in the project persistent property + setProjectTargetHashString(project, target.hashString()); + + // put it in a local map for easy access. + mProjectMap.put(project, target); + + // recompile the project if needed. + IJavaProject javaProject = JavaCore.create(project); + AndroidClasspathContainerInitializer.updateProjects( + new IJavaProject[] { javaProject }); + } + } + } + + /** + * Returns the {@link IAndroidTarget} object associated with the given {@link IProject}. + */ + public IAndroidTarget getTarget(IProject project) { + synchronized (mProjectMap) { + IAndroidTarget target = mProjectMap.get(project); + if (target == null) { + // get the value from the project persistent property. + String targetHashString = getProjectTargetHashString(project); + + if (targetHashString != null) { + target = mManager.getTargetFromHashString(targetHashString); + } + } + + return target; + } + } + + /** + * Returns the hash string uniquely identifying the target of a project. This methods reads + * the string from the project persistent preferences/properties. + *

          The string is equivalent to the return of {@link IAndroidTarget#hashString()}. + * @param project The project for which to return the target hash string. + * @return the hash string or null if the project does not have a target set. + */ + public static String getProjectTargetHashString(IProject project) { + // load the default.properties from the project folder. + ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString()); + if (properties == null) { + AdtPlugin.log(IStatus.ERROR, "Failed to load properties file for project '%s'", + project.getName()); + return null; + } + + return properties.getProperty(ProjectProperties.PROPERTY_TARGET); + } + + /** + * Sets a target hash string in a project's persistent preferences/property storage. + * @param project The project in which to save the hash string. + * @param targetHashString The target hash string to save. This must be the result from + * {@link IAndroidTarget#hashString()}. + */ + public static void setProjectTargetHashString(IProject project, String targetHashString) { + // because we don't want to erase other properties from default.properties, we first load + // them + ProjectProperties properties = ProjectProperties.load(project.getLocation().toOSString()); + if (properties == null) { + // doesn't exist yet? we create it. + properties = ProjectProperties.create(project.getLocation().toOSString()); + } + + // add/change the target hash string. + properties.setProperty(ProjectProperties.PROPERTY_TARGET, targetHashString); + + // and rewrite the file. + try { + properties.save(); + } catch (IOException e) { + AdtPlugin.log(e, "Failed to save default.properties for project '%s'", + project.getName()); + } + } + /** + * Return the {@link PlatformData} for a given {@link IAndroidTarget}. + */ + public AndroidTargetData getTargetData(IAndroidTarget target) { + synchronized (mTargetMap) { + return mTargetMap.get(target); + } + } + + private Sdk(SdkManager manager) { + mManager = manager; + + // pre-compute some paths + mDocBaseUrl = getDocumentationBaseUrl(mManager.getLocation() + + SdkConstants.OS_SDK_DOCS_FOLDER); + } + + void setTargetData(IAndroidTarget target, AndroidTargetData data) { + synchronized (mTargetMap) { + mTargetMap.put(target, data); + } + } + + /** + * Returns the URL to the local documentation. + * Can return null if no documentation is found in the current SDK. + * + * @param osDocsPath Path to the documentation folder in the current SDK. + * The folder may not actually exist. + * @return A file:// URL on the local documentation folder if it exists or null. + */ + private String getDocumentationBaseUrl(String osDocsPath) { + File f = new File(osDocsPath); + + if (f.isDirectory()) { + try { + // Note: to create a file:// URL, one would typically use something like + // f.toURI().toURL().toString(). However this generates a broken path on + // Windows, namely "C:\\foo" is converted to "file:/C:/foo" instead of + // "file:///C:/foo" (i.e. there should be 3 / after "file:"). So we'll + // do the correct thing manually. + + String path = f.getAbsolutePath(); + if (File.separatorChar != '/') { + path = path.replace(File.separatorChar, '/'); + } + + // For some reason the URL class doesn't add the mandatory "//" after + // the "file:" protocol name, so it has to be hacked into the path. + URL url = new URL("file", null, "//" + path); //$NON-NLS-1$ //$NON-NLS-2$ + String result = url.toString(); + return result; + } catch (MalformedURLException e) { + // ignore malformed URLs + } + } + + return null; + } + +} + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java new file mode 100644 index 0000000..8db09f2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/sdk/WidgetClassLoader.java @@ -0,0 +1,334 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.runtime.IProgressMonitor; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import javax.management.InvalidAttributeValueException; + +/** + * Parser for the text file containing the list of widgets, layouts and layout params. + *

          + * The file is a straight text file containing one class per line.
          + * Each line is in the following format
          + * [code][class name] [super class name] [super class name]... + * where code is a single letter (W for widget, L for layout, P for layout params), and class names + * are the fully qualified name of the classes. + */ +public final class WidgetClassLoader implements IAndroidClassLoader { + + /** + * Basic class containing the class descriptions found in the text file. + */ + private final static class ClassDescriptor implements IClassDescriptor { + + private String mName; + private String mSimpleName; + private ClassDescriptor mSuperClass; + private ClassDescriptor mEnclosingClass; + private final ArrayList mDeclaredClasses = + new ArrayList(); + private boolean mIsInstantiable = false; + + ClassDescriptor(String fqcn) { + mName = fqcn; + mSimpleName = getSimpleName(fqcn); + } + + public String getCanonicalName() { + return mName; + } + + public String getSimpleName() { + return mSimpleName; + } + + public IClassDescriptor[] getDeclaredClasses() { + return mDeclaredClasses.toArray(new IClassDescriptor[mDeclaredClasses.size()]); + } + + private void addDeclaredClass(ClassDescriptor declaredClass) { + mDeclaredClasses.add(declaredClass); + } + + public IClassDescriptor getEnclosingClass() { + return mEnclosingClass; + } + + void setEnclosingClass(ClassDescriptor enclosingClass) { + // set the enclosing class. + mEnclosingClass = enclosingClass; + + // add this to the list of declared class in the enclosing class. + mEnclosingClass.addDeclaredClass(this); + + // finally change the name of declared class to make sure it uses the + // convention: package.enclosing$declared instead of package.enclosing.declared + mName = enclosingClass.mName + "$" + mName.substring(enclosingClass.mName.length() + 1); + } + + public IClassDescriptor getSuperclass() { + return mSuperClass; + } + + void setSuperClass(ClassDescriptor superClass) { + mSuperClass = superClass; + } + + @Override + public boolean equals(Object clazz) { + if (clazz instanceof ClassDescriptor) { + return mName.equals(((ClassDescriptor)clazz).mName); + } + return super.equals(clazz); + } + + @Override + public int hashCode() { + return mName.hashCode(); + } + + public boolean isInstantiable() { + return mIsInstantiable; + } + + void setInstantiable(boolean state) { + mIsInstantiable = state; + } + + private String getSimpleName(String fqcn) { + String[] segments = fqcn.split("\\."); + return segments[segments.length-1]; + } + } + + private BufferedReader mReader; + + /** Output map of FQCN => descriptor on all classes */ + private final Map mMap = new TreeMap(); + /** Output map of FQCN => descriptor on View classes */ + private final Map mWidgetMap = new TreeMap(); + /** Output map of FQCN => descriptor on ViewGroup classes */ + private final Map mLayoutMap = new TreeMap(); + /** Output map of FQCN => descriptor on LayoutParams classes */ + private final Map mLayoutParamsMap = + new HashMap(); + /** File path of the source text file */ + private String mOsFilePath; + + /** + * Creates a loader with a given file path. + * @param osFilePath the OS path of the file to load. + * @throws FileNotFoundException if the file is not found. + */ + WidgetClassLoader(String osFilePath) throws FileNotFoundException { + mOsFilePath = osFilePath; + mReader = new BufferedReader(new FileReader(osFilePath)); + } + + public String getSource() { + return mOsFilePath; + } + + /** + * Parses the text file and return true if the file was successfully parsed. + * @param monitor + */ + boolean parseWidgetList(IProgressMonitor monitor) { + try { + String line; + while ((line = mReader.readLine()) != null) { + if (line.length() > 0) { + char prefix = line.charAt(0); + String[] classes = null; + ClassDescriptor clazz = null; + switch (prefix) { + case 'W': + classes = line.substring(1).split(" "); + clazz = processClass(classes, 0, null /* map */); + if (clazz != null) { + clazz.setInstantiable(true); + mWidgetMap.put(classes[0], clazz); + } + break; + case 'L': + classes = line.substring(1).split(" "); + clazz = processClass(classes, 0, null /* map */); + if (clazz != null) { + clazz.setInstantiable(true); + mLayoutMap.put(classes[0], clazz); + } + break; + case 'P': + classes = line.substring(1).split(" "); + clazz = processClass(classes, 0, mLayoutParamsMap); + if (clazz != null) { + clazz.setInstantiable(true); + } + break; + case '#': + // comment, do nothing + break; + default: + throw new IllegalArgumentException(); + } + } + } + + // reconciliate the layout and their layout params + postProcess(); + + return true; + } catch (IOException e) { + } finally { + try { + mReader.close(); + } catch (IOException e) { + } + } + + return false; + } + + /** + * Parses a View class and adds a ViewClassInfo for it in mWidgetMap. + * It calls itself recursively to handle super classes which are also Views. + * @param classes the inheritance list of the class to process. + * @param index the index of the class to process in the classes array. + * @param map an optional map in which to put every {@link ClassDescriptor} created. + */ + private ClassDescriptor processClass(String[] classes, int index, + Map map) { + if (index >= classes.length) { + return null; + } + + String fqcn = classes[index]; + + if ("java.lang.Object".equals(fqcn)) { //$NON-NLS-1$ + return null; + } + + // check if the ViewInfoClass has not yet been created. + if (mMap.containsKey(fqcn)) { + return mMap.get(fqcn); + } + + // create the custom class. + ClassDescriptor clazz = new ClassDescriptor(fqcn); + mMap.put(fqcn, clazz); + if (map != null) { + map.put(fqcn, clazz); + } + + // get the super class + ClassDescriptor superClass = processClass(classes, index+1, map); + if (superClass != null) { + clazz.setSuperClass(superClass); + } + + return clazz; + } + + /** + * Goes through the layout params and look for the enclosed class. If the layout params + * has no known enclosed type it is dropped. + */ + private void postProcess() { + Collection params = mLayoutParamsMap.values(); + + for (ClassDescriptor param : params) { + String fqcn = param.getCanonicalName(); + + // get the enclosed name. + String enclosed = getEnclosedName(fqcn); + + // look for a match in the layouts. We don't use the layout map as it only contains the + // end classes, but in this case we also need to process the layout params for the base + // layout classes. + ClassDescriptor enclosingType = mMap.get(enclosed); + if (enclosingType != null) { + param.setEnclosingClass(enclosingType); + + // remove the class from the map, and put it back with the fixed name + mMap.remove(fqcn); + mMap.put(param.getCanonicalName(), param); + } + } + } + + private String getEnclosedName(String fqcn) { + int index = fqcn.lastIndexOf('.'); + return fqcn.substring(0, index); + } + + /** + * Finds and loads all classes that derive from a given set of super classes. + * + * @param rootPackage Root package of classes to find. Use an empty string to find everyting. + * @param superClasses The super classes of all the classes to find. + * @return An hash map which keys are the super classes looked for and which values are + * ArrayList of the classes found. The array lists are always created for all the + * valid keys, they are simply empty if no deriving class is found for a given + * super class. + * @throws IOException + * @throws InvalidAttributeValueException + * @throws ClassFormatError + */ + public HashMap> findClassesDerivingFrom(String rootPackage, + String[] superClasses) throws IOException, InvalidAttributeValueException, + ClassFormatError { + HashMap> map = + new HashMap>(); + + ArrayList list = new ArrayList(); + list.addAll(mWidgetMap.values()); + map.put(AndroidConstants.CLASS_VIEW, list); + + list = new ArrayList(); + list.addAll(mLayoutMap.values()); + map.put(AndroidConstants.CLASS_VIEWGROUP, list); + + list = new ArrayList(); + list.addAll(mLayoutParamsMap.values()); + map.put(AndroidConstants.CLASS_VIEWGROUP_LAYOUTPARAMS, list); + + return map; + } + + /** + * Returns a {@link IClass} by its fully-qualified name. + * @param className the fully-qualified name of the class to return. + * @throws ClassNotFoundException + */ + public IClassDescriptor getClass(String className) throws ClassNotFoundException { + return mMap.get(className); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java new file mode 100644 index 0000000..8044bcb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectCreationPage.java @@ -0,0 +1,1116 @@ +/* + * Copyright (C) 2007 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. + */ + +/* + * References: + * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizard + * org.eclipse.jdt.internal.ui.wizards.JavaProjectWizardFirstPage + */ + +package com.android.ide.eclipse.adt.wizards.newproject; + +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.sdklib.IAndroidTarget; +import com.android.sdkuilib.SdkTargetSelector; + +import org.eclipse.core.filesystem.URIUtil; +import org.eclipse.core.resources.IProject; +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.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.jdt.core.JavaConventions; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.osgi.util.TextProcessor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +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.DirectoryDialog; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +import java.io.File; +import java.io.FileFilter; +import java.net.URI; +import java.util.regex.Pattern; + +/** + * NewAndroidProjectCreationPage is a project creation page that provides the + * following fields: + *

            + *
          • Package name + *
          • Activity name + *
          • Location of the SDK + *
          + * Note: this class is public so that it can be accessed from unit tests. + * It is however an internal class. Its API may change without notice. + * It should semantically be considered as a private final class. + * Do not derive from this class. + */ +public class NewProjectCreationPage extends WizardPage { + + // constants + /** Initial value for all name fields (project, activity, application, package). Used + * whenever a value is requested before controls are created. */ + private static final String INITIAL_NAME = ""; //$NON-NLS-1$ + /** Initial value for the Create New Project radio; False means Create From Existing would be + * the default.*/ + private static final boolean INITIAL_CREATE_NEW_PROJECT = true; + /** Initial value for the Use Default Location check box. */ + private static final boolean INITIAL_USE_DEFAULT_LOCATION = true; + /** Initial value for the Create Activity check box. */ + private static final boolean INITIAL_CREATE_ACTIVITY = true; + + + /** Pattern for characters accepted in a project name. Since this will be used as a + * directory name, we're being a bit conservative on purpose. It cannot start with a space. */ + private static final Pattern sProjectNamePattern = Pattern.compile("^[\\w][\\w. -]*$"); //$NON-NLS-1$ + /** Last user-browsed location, static so that it be remembered for the whole session */ + private static String sCustomLocationOsPath = ""; //$NON-NLS-1$ + + private final int MSG_NONE = 0; + private final int MSG_WARNING = 1; + private final int MSG_ERROR = 2; + + private String mUserPackageName = ""; //$NON-NLS-1$ + private String mUserActivityName = ""; //$NON-NLS-1$ + private boolean mUserCreateActivityCheck = INITIAL_CREATE_ACTIVITY; + private String mSourceFolder = ""; //$NON-NLS-1$ + + // widgets + private Text mProjectNameField; + private Text mPackageNameField; + private Text mActivityNameField; + private Text mApplicationNameField; + private Button mCreateNewProjectRadio; + private Button mUseDefaultLocation; + private Label mLocationLabel; + private Text mLocationPathField; + private Button mBrowseButton; + private Button mCreateActivityCheck; + + private boolean mInternalLocationPathUpdate; + protected boolean mInternalProjectNameUpdate; + protected boolean mInternalApplicationNameUpdate; + private boolean mInternalCreateActivityUpdate; + private boolean mInternalActivityNameUpdate; + protected boolean mProjectNameModifiedByUser; + protected boolean mApplicationNameModifiedByUser; + private SdkTargetSelector mSdkTargetSelector; + + + /** + * Creates a new project creation wizard page. + * + * @param pageName the name of this page + */ + public NewProjectCreationPage(String pageName) { + super(pageName); + setPageComplete(false); + if (sCustomLocationOsPath == null || + sCustomLocationOsPath.length() == 0 || + !new File(sCustomLocationOsPath).isDirectory()) { + // FIXME location of samples is pretty much impossible here. + //sCustomLocationOsPath = AdtPlugin.getOsSdkSamplesFolder(); + sCustomLocationOsPath = File.listRoots()[0].getAbsolutePath(); + } + } + + // --- Getters used by NewProjectWizard --- + + /** + * Returns the current project location path as entered by the user, or its + * anticipated initial value. Note that if the default has been returned the + * path in a project description used to create a project should not be set. + * + * @return the project location path or its anticipated initial value. + */ + public IPath getLocationPath() { + return new Path(getProjectLocation()); + } + + /** Returns the value of the project name field with leading and trailing spaces removed. */ + public String getProjectName() { + return mProjectNameField == null ? INITIAL_NAME : mProjectNameField.getText().trim(); + } + + /** Returns the value of the package name field with spaces trimmed. */ + public String getPackageName() { + return mPackageNameField == null ? INITIAL_NAME : mPackageNameField.getText().trim(); + } + + /** Returns the value of the activity name field with spaces trimmed. */ + public String getActivityName() { + return mActivityNameField == null ? INITIAL_NAME : mActivityNameField.getText().trim(); + } + + /** Returns the value of the application name field with spaces trimmed. */ + public String getApplicationName() { + // Return the name of the activity as default application name. + return mApplicationNameField == null ? getActivityName() + : mApplicationNameField.getText().trim(); + + } + + /** Returns the value of the "Create New Project" radio. */ + public boolean isNewProject() { + return mCreateNewProjectRadio == null ? INITIAL_CREATE_NEW_PROJECT + : mCreateNewProjectRadio.getSelection(); + } + + /** Returns the value of the "Create Activity" checkbox. */ + public boolean isCreateActivity() { + return mCreateActivityCheck == null ? INITIAL_CREATE_ACTIVITY + : mCreateActivityCheck.getSelection(); + } + + /** Returns the value of the Use Default Location field. */ + public boolean useDefaultLocation() { + return mUseDefaultLocation == null ? INITIAL_USE_DEFAULT_LOCATION + : mUseDefaultLocation.getSelection(); + } + + /** Returns the internal source folder (for the "existing project" mode) or the default + * "src" constant. */ + public String getSourceFolder() { + if (isNewProject() || mSourceFolder == null || mSourceFolder.length() == 0) { + return AndroidConstants.FD_SOURCES; + } else { + return mSourceFolder; + } + } + + /** Returns the current sdk target or null if none has been selected yet. */ + public IAndroidTarget getSdkTarget() { + return mSdkTargetSelector == null ? null : mSdkTargetSelector.getFirstSelected(); + } + + /** + * Overrides @DialogPage.setVisible(boolean) to put the focus in the project name when + * the dialog is made visible. + */ + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + mProjectNameField.setFocus(); + } + } + + // --- UI creation --- + + /** + * Creates the top level control for this dialog page under the given parent + * composite. + * + * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setFont(parent.getFont()); + + initializeDialogUnits(parent); + + composite.setLayout(new GridLayout()); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createProjectNameGroup(composite); + createLocationGroup(composite); + createTargetGroup(composite); + createPropertiesGroup(composite); + + // Update state the first time + enableLocationWidgets(); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + // Validate. This will complain about the first empty field. + setPageComplete(validatePage()); + } + + /** + * Creates the group for the project name: + * [label: "Project Name"] [text field] + * + * @param parent the parent composite + */ + private final void createProjectNameGroup(Composite parent) { + Composite group = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + // new project label + Label label = new Label(group, SWT.NONE); + label.setText("Project name:"); + label.setFont(parent.getFont()); + label.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); + + // new project name entry field + mProjectNameField = new Text(group, SWT.BORDER); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + mProjectNameField.setToolTipText("Name of the Eclipse project to create. It cannot be empty."); + mProjectNameField.setLayoutData(data); + mProjectNameField.setFont(parent.getFont()); + mProjectNameField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + if (!mInternalProjectNameUpdate) { + mProjectNameModifiedByUser = true; + } + updateLocationPathField(null); + } + }); + } + + + /** + * Creates the group for the Project options: + * [radio] Create new project + * [radio] Create project from existing sources + * [check] Use default location + * Location [text field] [browse button] + * + * @param parent the parent composite + */ + private final void createLocationGroup(Composite parent) { + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + // Layout has 4 columns of non-equal size + group.setLayout(new GridLayout()); + group.setLayoutData(new GridData(GridData.FILL_BOTH)); + group.setFont(parent.getFont()); + group.setText("Contents"); + + mCreateNewProjectRadio = new Button(group, SWT.RADIO); + mCreateNewProjectRadio.setText("Create new project in workspace"); + mCreateNewProjectRadio.setSelection(INITIAL_CREATE_NEW_PROJECT); + Button existing_project_radio = new Button(group, SWT.RADIO); + existing_project_radio.setText("Create project from existing source"); + existing_project_radio.setSelection(!INITIAL_CREATE_NEW_PROJECT); + + mUseDefaultLocation = new Button(group, SWT.CHECK); + mUseDefaultLocation.setText("Use default location"); + mUseDefaultLocation.setSelection(INITIAL_USE_DEFAULT_LOCATION); + + SelectionListener location_listener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + enableLocationWidgets(); + extractNamesFromAndroidManifest(); + setPageComplete(validatePage()); + } + }; + + mCreateNewProjectRadio.addSelectionListener(location_listener); + existing_project_radio.addSelectionListener(location_listener); + mUseDefaultLocation.addSelectionListener(location_listener); + + Composite location_group = new Composite(group, SWT.NONE); + location_group.setLayout(new GridLayout(4, /* num columns */ + false /* columns of not equal size */)); + location_group.setLayoutData(new GridData(GridData.FILL_BOTH)); + location_group.setFont(parent.getFont()); + + mLocationLabel = new Label(location_group, SWT.NONE); + mLocationLabel.setText("Location:"); + + mLocationPathField = new Text(location_group, SWT.BORDER); + GridData data = new GridData(GridData.FILL, /* horizontal alignment */ + GridData.BEGINNING, /* vertical alignment */ + true, /* grabExcessHorizontalSpace */ + false, /* grabExcessVerticalSpace */ + 2, /* horizontalSpan */ + 1); /* verticalSpan */ + mLocationPathField.setLayoutData(data); + mLocationPathField.setFont(parent.getFont()); + mLocationPathField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + onLocationPathFieldModified(); + } + }); + + mBrowseButton = new Button(location_group, SWT.PUSH); + mBrowseButton.setText("Browse..."); + setButtonLayoutData(mBrowseButton); + mBrowseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + openDirectoryBrowser(); + } + }); + } + + /** + * Creates the target group. + * It only contains an SdkTargetSelector. + */ + private void createTargetGroup(Composite parent) { + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + // Layout has 1 column + group.setLayout(new GridLayout()); + group.setLayoutData(new GridData(GridData.FILL_BOTH)); + group.setFont(parent.getFont()); + group.setText("Target"); + + // get the targets from the sdk + IAndroidTarget[] targets = null; + if (Sdk.getCurrent() != null) { + targets = Sdk.getCurrent().getTargets(); + } + + mSdkTargetSelector = new SdkTargetSelector(group, targets, false /*multi-selection*/); + mSdkTargetSelector.setSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + setPageComplete(validatePage()); + } + }); + } + + /** + * Display a directory browser and update the location path field with the selected path + */ + private void openDirectoryBrowser() { + + String existing_dir = getLocationPathFieldValue(); + + // Disable the path if it doesn't exist + if (existing_dir.length() == 0) { + existing_dir = null; + } else { + File f = new File(existing_dir); + if (!f.exists()) { + existing_dir = null; + } + } + + DirectoryDialog dd = new DirectoryDialog(mLocationPathField.getShell()); + dd.setMessage("Browse for folder"); + dd.setFilterPath(existing_dir); + String abs_dir = dd.open(); + + if (abs_dir != null) { + updateLocationPathField(abs_dir); + extractNamesFromAndroidManifest(); + setPageComplete(validatePage()); + } + } + + /** + * Creates the group for the project properties: + * - Package name [text field] + * - Activity name [text field] + * - Application name [text field] + * + * @param parent the parent composite + */ + private final void createPropertiesGroup(Composite parent) { + // package specification group + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + group.setFont(parent.getFont()); + group.setText("Properties"); + + // new application label + Label label = new Label(group, SWT.NONE); + label.setText("Application name:"); + label.setFont(parent.getFont()); + label.setToolTipText("Name of the Application. This is a free string. It can be empty."); + + // new application name entry field + mApplicationNameField = new Text(group, SWT.BORDER); + GridData data = new GridData(GridData.FILL_HORIZONTAL); + mApplicationNameField.setToolTipText("Name of the Application. This is a free string. It can be empty."); + mApplicationNameField.setLayoutData(data); + mApplicationNameField.setFont(parent.getFont()); + mApplicationNameField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + if (!mInternalApplicationNameUpdate) { + mApplicationNameModifiedByUser = true; + } + } + }); + + // new package label + label = new Label(group, SWT.NONE); + label.setText("Package name:"); + label.setFont(parent.getFont()); + label.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); + + // new package name entry field + mPackageNameField = new Text(group, SWT.BORDER); + data = new GridData(GridData.FILL_HORIZONTAL); + mPackageNameField.setToolTipText("Namespace of the Package to create. This must be a Java namespace with at least two components."); + mPackageNameField.setLayoutData(data); + mPackageNameField.setFont(parent.getFont()); + mPackageNameField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + onPackageNameFieldModified(); + } + }); + + // new activity label + mCreateActivityCheck = new Button(group, SWT.CHECK); + mCreateActivityCheck.setText("Create Activity:"); + mCreateActivityCheck.setToolTipText("Specifies if you want to create a default Activity."); + mCreateActivityCheck.setFont(parent.getFont()); + mCreateActivityCheck.setSelection(INITIAL_CREATE_ACTIVITY); + mCreateActivityCheck.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + onCreateActivityCheckModified(); + enableLocationWidgets(); + } + }); + + // new activity name entry field + mActivityNameField = new Text(group, SWT.BORDER); + data = new GridData(GridData.FILL_HORIZONTAL); + mActivityNameField.setToolTipText("Name of the Activity class to create. Must be a valid Java identifier."); + mActivityNameField.setLayoutData(data); + mActivityNameField.setFont(parent.getFont()); + mActivityNameField.addListener(SWT.Modify, new Listener() { + public void handleEvent(Event event) { + onActivityNameFieldModified(); + } + }); + } + + + //--- Internal getters & setters ------------------ + + /** Returns the location path field value with spaces trimmed. */ + private String getLocationPathFieldValue() { + return mLocationPathField == null ? "" : mLocationPathField.getText().trim(); + } + + /** Returns the current project location, depending on the Use Default Location check box. */ + public String getProjectLocation() { + if (isNewProject() && useDefaultLocation()) { + return Platform.getLocation().toString(); + } else { + return getLocationPathFieldValue(); + } + } + + /** + * Creates a project resource handle for the current project name field + * value. + *

          + * This method does not create the project resource; this is the + * responsibility of IProject::create invoked by the new + * project resource wizard. + *

          + * + * @return the new project resource handle + */ + private IProject getProjectHandle() { + return ResourcesPlugin.getWorkspace().getRoot().getProject(getProjectName()); + } + + // --- UI Callbacks ---- + + /** + * Enables or disable the location widgets depending on the user selection: + * the location path is enabled when using the "existing source" mode (i.e. not new project) + * or in new project mode with the "use default location" turned off. + */ + private void enableLocationWidgets() { + boolean is_new_project = isNewProject(); + boolean use_default = useDefaultLocation(); + boolean location_enabled = !is_new_project || !use_default; + boolean create_activity = isCreateActivity(); + + mUseDefaultLocation.setEnabled(is_new_project); + + mLocationLabel.setEnabled(location_enabled); + mLocationPathField.setEnabled(location_enabled); + mBrowseButton.setEnabled(location_enabled); + + mPackageNameField.setEnabled(is_new_project); + mCreateActivityCheck.setEnabled(is_new_project); + mActivityNameField.setEnabled(is_new_project & create_activity); + + updateLocationPathField(null); + updatePackageAndActivityFields(); + } + + /** + * Updates the location directory path field. + *
          + * When custom user selection is enabled, use the abs_dir argument if not null and also + * save it internally. If abs_dir is null, restore the last saved abs_dir. This allows the + * user selection to be remembered when the user switches from default to custom. + *
          + * When custom user selection is disabled, use the workspace default location with the + * current project name. This does not change the internally cached abs_dir. + * + * @param abs_dir A new absolute directory path or null to use the default. + */ + private void updateLocationPathField(String abs_dir) { + boolean is_new_project = isNewProject(); + boolean use_default = useDefaultLocation(); + boolean custom_location = !is_new_project || !use_default; + + if (!mInternalLocationPathUpdate) { + mInternalLocationPathUpdate = true; + if (custom_location) { + if (abs_dir != null) { + sCustomLocationOsPath = TextProcessor.process(abs_dir); + } + if (!mLocationPathField.getText().equals(sCustomLocationOsPath)) { + mLocationPathField.setText(sCustomLocationOsPath); + } + } else { + String value = Platform.getLocation().append(getProjectName()).toString(); + value = TextProcessor.process(value); + if (!mLocationPathField.getText().equals(value)) { + mLocationPathField.setText(value); + } + } + setPageComplete(validatePage()); + mInternalLocationPathUpdate = false; + } + } + + /** + * The location path field is either modified internally (from updateLocationPathField) + * or manually by the user when the custom_location mode is not set. + * + * Ignore the internal modification. When modified by the user, memorize the choice and + * validate the page. + */ + private void onLocationPathFieldModified() { + if (!mInternalLocationPathUpdate) { + // When the updates doesn't come from updateLocationPathField, it must be the user + // editing the field manually, in which case we want to save the value internally. + sCustomLocationOsPath = getLocationPathFieldValue(); + extractNamesFromAndroidManifest(); + setPageComplete(validatePage()); + } + } + + /** + * The package name field is either modified internally (from extractNamesFromAndroidManifest) + * or manually by the user when the custom_location mode is not set. + * + * Ignore the internal modification. When modified by the user, memorize the choice and + * validate the page. + */ + private void onPackageNameFieldModified() { + if (isNewProject()) { + mUserPackageName = getPackageName(); + setPageComplete(validatePage()); + } + } + + /** + * The create activity checkbox is either modified internally (from + * extractNamesFromAndroidManifest) or manually by the user. + * + * Ignore the internal modification. When modified by the user, memorize the choice and + * validate the page. + */ + private void onCreateActivityCheckModified() { + if (isNewProject() && !mInternalCreateActivityUpdate) { + mUserCreateActivityCheck = isCreateActivity(); + } + setPageComplete(validatePage()); + } + + /** + * The activity name field is either modified internally (from extractNamesFromAndroidManifest) + * or manually by the user when the custom_location mode is not set. + * + * Ignore the internal modification. When modified by the user, memorize the choice and + * validate the page. + */ + private void onActivityNameFieldModified() { + if (isNewProject() && !mInternalActivityNameUpdate) { + mUserActivityName = getActivityName(); + setPageComplete(validatePage()); + } + } + + /** + * Called when the radio buttons are changed between the "create new project" and the + * "use existing source" mode. This reverts the fields to whatever the user manually + * entered before. + */ + private void updatePackageAndActivityFields() { + if (isNewProject()) { + if (mUserPackageName.length() > 0 && + !mPackageNameField.getText().equals(mUserPackageName)) { + mPackageNameField.setText(mUserPackageName); + } + + if (mUserActivityName.length() > 0 && + !mActivityNameField.getText().equals(mUserActivityName)) { + mInternalActivityNameUpdate = true; + mActivityNameField.setText(mUserActivityName); + mInternalActivityNameUpdate = false; + } + + if (mUserCreateActivityCheck != mCreateActivityCheck.getSelection()) { + mInternalCreateActivityUpdate = true; + mCreateActivityCheck.setSelection(mUserCreateActivityCheck); + mInternalCreateActivityUpdate = false; + } + } + } + + /** + * Extract names from an android manifest. + * This is done only if the user selected the "use existing source" and a manifest xml file + * can actually be found in the custom user directory. + */ + private void extractNamesFromAndroidManifest() { + if (!isNewProject()) { + File f = new File(getProjectLocation()); + if (f.isDirectory()) { + Path path = new Path(f.getPath()); + String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); + AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); + if (manifest.exists()) { + String packageName = null; + String activityName = null; + try { + packageName = manifest.getPackageName(); + activityName = manifest.getActivityName(1); + } catch (Exception e) { + // pass + } + + + if (packageName != null && packageName.length() > 0) { + mPackageNameField.setText(packageName); + } + + if (activityName != null && activityName.length() > 0) { + mInternalActivityNameUpdate = true; + mInternalCreateActivityUpdate = true; + mActivityNameField.setText(activityName); + mCreateActivityCheck.setSelection(true); + mInternalCreateActivityUpdate = false; + mInternalActivityNameUpdate = false; + + // If project name and application names are empty, use the activity + // name as a default. If the activity name has dots, it's a part of a + // package specification and only the last identifier must be used. + if (activityName.indexOf('.') != -1) { + String[] ids = activityName.split(AndroidConstants.RE_DOT); + activityName = ids[ids.length - 1]; + } + if (mProjectNameField.getText().length() == 0 || + !mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(activityName); + mInternalProjectNameUpdate = false; + } + if (mApplicationNameField.getText().length() == 0 || + !mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(activityName); + mInternalApplicationNameUpdate = false; + } + } else { + mInternalActivityNameUpdate = true; + mInternalCreateActivityUpdate = true; + mActivityNameField.setText(""); + mCreateActivityCheck.setSelection(false); + mInternalCreateActivityUpdate = false; + mInternalActivityNameUpdate = false; + + // There is no activity name to use to fill in the project and application + // name. However if there's a package name, we can use this as a base. + if (packageName != null && packageName.length() > 0) { + // Package name is a java identifier, so it's most suitable for + // an application name. + + if (mApplicationNameField.getText().length() == 0 || + !mApplicationNameModifiedByUser) { + mInternalApplicationNameUpdate = true; + mApplicationNameField.setText(packageName); + mInternalApplicationNameUpdate = false; + } + + // For the project name, remove any dots + packageName = packageName.replace('.', '_'); + if (mProjectNameField.getText().length() == 0 || + !mProjectNameModifiedByUser) { + mInternalProjectNameUpdate = true; + mProjectNameField.setText(packageName); + mInternalProjectNameUpdate = false; + } + + } + } + } + } + } + } + + /** + * Returns whether this page's controls currently all contain valid values. + * + * @return true if all controls are valid, and + * false if at least one is invalid + */ + protected boolean validatePage() { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + int status = validateProjectField(workspace); + if ((status & MSG_ERROR) == 0) { + status |= validateLocationPath(workspace); + } + if ((status & MSG_ERROR) == 0) { + status |= validateSdkTarget(); + } + if ((status & MSG_ERROR) == 0) { + status |= validatePackageField(); + } + if ((status & MSG_ERROR) == 0) { + status |= validateActivityField(); + } + if ((status & MSG_ERROR) == 0) { + status |= validateSourceFolder(); + } + if (status == MSG_NONE) { + setStatus(null, MSG_NONE); + } + + // Return false if there's an error so that the finish button be disabled. + return (status & MSG_ERROR) == 0; + } + + /** + * Validates the project name field. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateProjectField(IWorkspace workspace) { + // Validate project field + String projectFieldContents = getProjectName(); + if (projectFieldContents.length() == 0) { + return setStatus("Project name must be specified", MSG_ERROR); + } + + // Limit the project name to shell-agnostic characters since it will be used to + // generate the final package + if (!sProjectNamePattern.matcher(projectFieldContents).matches()) { + return setStatus("The project name must start with an alphanumeric characters, followed by one or more alphanumerics, digits, dots, dashes, underscores or spaces.", + MSG_ERROR); + } + + IStatus nameStatus = workspace.validateName(projectFieldContents, IResource.PROJECT); + if (!nameStatus.isOK()) { + return setStatus(nameStatus.getMessage(), MSG_ERROR); + } + + if (getProjectHandle().exists()) { + return setStatus("A project with that name already exists in the workspace", + MSG_ERROR); + } + + return MSG_NONE; + } + + /** + * Validates the location path field. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateLocationPath(IWorkspace workspace) { + Path path = new Path(getProjectLocation()); + if (isNewProject()) { + if (!useDefaultLocation()) { + // If not using the default value validate the location. + URI uri = URIUtil.toURI(path.toOSString()); + IStatus locationStatus = workspace.validateProjectLocationURI(getProjectHandle(), + uri); + if (!locationStatus.isOK()) { + return setStatus(locationStatus.getMessage(), MSG_ERROR); + } else { + // The location is valid as far as Eclipse is concerned (i.e. mostly not + // an existing workspace project.) Check it either doesn't exist or is + // a directory that is empty. + File f = path.toFile(); + if (f.exists() && !f.isDirectory()) { + return setStatus("A directory name must be specified.", MSG_ERROR); + } else if (f.isDirectory()) { + // However if the directory exists, we should put a warning if it is not + // empty. We don't put an error (we'll ask the user again for confirmation + // before using the directory.) + String[] l = f.list(); + if (l.length != 0) { + return setStatus("The selected output directory is not empty.", + MSG_WARNING); + } + } + } + } else { + // Otherwise validate the path string is not empty + if (getProjectLocation().length() == 0) { + return setStatus("A directory name must be specified.", MSG_ERROR); + } + + File dest = path.append(getProjectName()).toFile(); + if (dest.exists()) { + return setStatus(String.format("There is already a file or directory named \"%1$s\" in the selected location.", + getProjectName()), MSG_ERROR); + } + } + } else { + // Must be an existing directory + File f = path.toFile(); + if (!f.isDirectory()) { + return setStatus("An existing directory name must be specified.", MSG_ERROR); + } + + // Check there's an android manifest in the directory + String osPath = path.append(AndroidConstants.FN_ANDROID_MANIFEST).toOSString(); + AndroidManifestHelper manifest = new AndroidManifestHelper(osPath); + if (!manifest.exists()) { + return setStatus( + String.format("File %1$s not found in %2$s.", + AndroidConstants.FN_ANDROID_MANIFEST, f.getName()), + MSG_ERROR); + } + + // Parse it and check the important fields. + String packageName = manifest.getPackageName(); + if (packageName == null || packageName.length() == 0) { + return setStatus( + String.format("No package name defined in %1$s.", osPath), + MSG_ERROR); + } + + String activityName = manifest.getActivityName(1); + if (activityName == null || activityName.length() == 0) { + // This is acceptable now as long as no activity needs to be created + if (isCreateActivity()) { + return setStatus( + String.format("No activity name defined in %1$s.", osPath), + MSG_ERROR); + } + } + + // If there's already a .project, tell the user to use import instead. + if (path.append(".project").toFile().exists()) { //$NON-NLS-1$ + return setStatus("An Eclipse project already exists in this directory. Consider using File > Import > Existing Project instead.", + MSG_WARNING); + } + } + + return MSG_NONE; + } + + /** + * Validates the sdk target choice. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateSdkTarget() { + if (getSdkTarget() == null) { + return setStatus("An SDK Target must be specified.", MSG_ERROR); + } + return MSG_NONE; + } + + /** + * Validates the activity name field. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateActivityField() { + // Disregard if not creating an activity + if (!isCreateActivity()) { + return MSG_NONE; + } + + // Validate activity field + String activityFieldContents = getActivityName(); + if (activityFieldContents.length() == 0) { + return setStatus("Activity name must be specified.", MSG_ERROR); + } + + // The activity field can actually contain part of a sub-package name + // or it can start with a dot "." to indicates it comes from the parent package name. + String packageName = ""; + int pos = activityFieldContents.lastIndexOf('.'); + if (pos >= 0) { + packageName = activityFieldContents.substring(0, pos); + if (packageName.startsWith(".")) { //$NON-NLS-1$ + packageName = packageName.substring(1); + } + + activityFieldContents = activityFieldContents.substring(pos + 1); + } + + // the activity field can contain a simple java identifier, or a + // package name or one that starts with a dot. So if it starts with a dot, + // ignore this dot -- the rest must look like a package name. + if (activityFieldContents.charAt(0) == '.') { + activityFieldContents = activityFieldContents.substring(1); + } + + // Check it's a valid activity string + int result = MSG_NONE; + IStatus status = JavaConventions.validateTypeVariableName(activityFieldContents, + "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ + if (!status.isOK()) { + result = setStatus(status.getMessage(), + status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); + } + + // Check it's a valid package string + if (result != MSG_ERROR && packageName.length() > 0) { + status = JavaConventions.validatePackageName(packageName, + "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ + if (!status.isOK()) { + result = setStatus(status.getMessage() + " (in the activity name)", + status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); + } + } + + + return result; + } + + /** + * Validates the package name field. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validatePackageField() { + // Validate package field + String packageFieldContents = getPackageName(); + if (packageFieldContents.length() == 0) { + return setStatus("Package name must be specified.", MSG_ERROR); + } + + // Check it's a valid package string + int result = MSG_NONE; + IStatus status = JavaConventions.validatePackageName(packageFieldContents, "1.5", "1.5"); //$NON-NLS-1$ $NON-NLS-2$ + if (!status.isOK()) { + result = setStatus(status.getMessage(), + status.getSeverity() == IStatus.ERROR ? MSG_ERROR : MSG_WARNING); + } + + // The Android Activity Manager does not accept packages names with only one + // identifier. Check the package name has at least one dot in them (the previous rule + // validated that if such a dot exist, it's not the first nor last characters of the + // string.) + if (result != MSG_ERROR && packageFieldContents.indexOf('.') == -1) { + return setStatus("Package name must have at least two identifiers.", MSG_ERROR); + } + + return result; + } + + /** + * Validates that an existing project actually has a source folder. + * + * For project in "use existing source" mode, this tries to find the source folder. + * A source folder should be just under the project directory and it should have all + * the directories composing the package+activity name. + * + * As a side effect, it memorizes the source folder in mSourceFolder. + * + * TODO: support multiple source folders for multiple activities. + * + * @return The wizard message type, one of MSG_ERROR, MSG_WARNING or MSG_NONE. + */ + private int validateSourceFolder() { + // This check does nothing when creating a new project. + // This check is also useless when no activity is present or created. + if (isNewProject() || !isCreateActivity()) { + return MSG_NONE; + } + + String osTarget = getActivityName(); + + if (osTarget.indexOf('.') == -1) { + osTarget = getPackageName() + File.separator + osTarget; + } else if (osTarget.indexOf('.') == 0) { + osTarget = getPackageName() + osTarget; + } + osTarget = osTarget.replace('.', File.separatorChar) + AndroidConstants.DOT_JAVA; + + String projectPath = getProjectLocation(); + File projectDir = new File(projectPath); + File[] all_dirs = projectDir.listFiles(new FileFilter() { + public boolean accept(File pathname) { + return pathname.isDirectory(); + } + }); + for (File f : all_dirs) { + Path path = new Path(f.getAbsolutePath()); + File java_activity = path.append(osTarget).toFile(); + if (java_activity.isFile()) { + mSourceFolder = f.getName(); + return MSG_NONE; + } + } + + if (all_dirs.length > 0) { + return setStatus( + String.format("%1$s can not be found under %2$s.", osTarget, projectPath), + MSG_ERROR); + } else { + return setStatus( + String.format("No source folders can be found in %1$s.", projectPath), + MSG_ERROR); + } + } + + /** + * Sets the error message for the wizard with the given message icon. + * + * @param message The wizard message type, one of MSG_ERROR or MSG_WARNING. + * @return As a convenience, always returns messageType so that the caller can return + * immediately. + */ + private int setStatus(String message, int messageType) { + if (message == null) { + setErrorMessage(null); + setMessage(null); + } else if (!message.equals(getMessage())) { + setMessage(message, messageType == MSG_WARNING ? WizardPage.WARNING : WizardPage.ERROR); + } + return messageType; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java new file mode 100644 index 0000000..a582217 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java @@ -0,0 +1,714 @@ +/* + * Copyright (C) 2007 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.wizards.newproject; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.project.AndroidNature; +import com.android.ide.eclipse.adt.project.ProjectHelper; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceStatus; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +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.core.runtime.Platform; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +/** + * A "New Android Project" Wizard. + *

          + * Note: this class is public so that it can be accessed from unit tests. + * It is however an internal class. Its API may change without notice. + * It should semantically be considered as a private final class. + * Do not derive from this class. + + */ +public class NewProjectWizard extends Wizard implements INewWizard { + + private static final String PARAM_SDK_TOOLS_DIR = "ANDROID_SDK_TOOLS"; //$NON-NLS-1$ + private static final String PARAM_ACTIVITY = "ACTIVITY_NAME"; //$NON-NLS-1$ + private static final String PARAM_APPLICATION = "APPLICATION_NAME"; //$NON-NLS-1$ + private static final String PARAM_PACKAGE = "PACKAGE"; //$NON-NLS-1$ + private static final String PARAM_PROJECT = "PROJECT_NAME"; //$NON-NLS-1$ + private static final String PARAM_STRING_NAME = "STRING_NAME"; //$NON-NLS-1$ + private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ + private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$ + private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$ + private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$ + + private static final String PH_ACTIVITIES = "ACTIVITIES"; //$NON-NLS-1$ + private static final String PH_INTENT_FILTERS = "INTENT_FILTERS"; //$NON-NLS-1$ + private static final String PH_STRINGS = "STRINGS"; //$NON-NLS-1$ + + private static final String BIN_DIRECTORY = + AndroidConstants.FD_BINARIES + AndroidConstants.WS_SEP; + private static final String RES_DIRECTORY = + AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + private static final String ASSETS_DIRECTORY = + AndroidConstants.FD_ASSETS + AndroidConstants.WS_SEP; + private static final String DRAWABLE_DIRECTORY = + AndroidConstants.FD_DRAWABLE + AndroidConstants.WS_SEP; + private static final String LAYOUT_DIRECTORY = + AndroidConstants.FD_LAYOUT + AndroidConstants.WS_SEP; + private static final String VALUES_DIRECTORY = + AndroidConstants.FD_VALUES + AndroidConstants.WS_SEP; + + private static final String TEMPLATES_DIRECTORY = "templates/"; //$NON-NLS-1$ + private static final String TEMPLATE_MANIFEST = TEMPLATES_DIRECTORY + + "AndroidManifest.template"; //$NON-NLS-1$ + private static final String TEMPLATE_ACTIVITIES = TEMPLATES_DIRECTORY + + "activity.template"; //$NON-NLS-1$ + private static final String TEMPLATE_INTENT_LAUNCHER = TEMPLATES_DIRECTORY + + "launcher_intent_filter.template"; //$NON-NLS-1$ + + private static final String TEMPLATE_STRINGS = TEMPLATES_DIRECTORY + + "strings.template"; //$NON-NLS-1$ + private static final String TEMPLATE_STRING = TEMPLATES_DIRECTORY + + "string.template"; //$NON-NLS-1$ + private static final String ICON = "icon.png"; //$NON-NLS-1$ + + private static final String STRINGS_FILE = "strings.xml"; //$NON-NLS-1$ + + private static final String STRING_RSRC_PREFIX = "@string/"; //$NON-NLS-1$ + private static final String STRING_APP_NAME = "app_name"; //$NON-NLS-1$ + private static final String STRING_HELLO_WORLD = "hello"; //$NON-NLS-1$ + + private static final String[] DEFAULT_DIRECTORIES = new String[] { + BIN_DIRECTORY, RES_DIRECTORY, ASSETS_DIRECTORY }; + private static final String[] RES_DIRECTORIES = new String[] { + DRAWABLE_DIRECTORY, LAYOUT_DIRECTORY, VALUES_DIRECTORY}; + + private static final String PROJECT_LOGO_LARGE = "icons/android_large.png"; //$NON-NLS-1$ + private static final String JAVA_ACTIVITY_TEMPLATE = "java_file.template"; //$NON-NLS-1$ + private static final String LAYOUT_TEMPLATE = "layout.template"; //$NON-NLS-1$ + private static final String MAIN_LAYOUT_XML = "main.xml"; //$NON-NLS-1$ + + protected static final String MAIN_PAGE_NAME = "newAndroidProjectPage"; //$NON-NLS-1$ + + private NewProjectCreationPage mMainPage; + + /** + * Initializes this creation wizard using the passed workbench and object + * selection. Inherited from org.eclipse.ui.IWorkbenchWizard + */ + public void init(IWorkbench workbench, IStructuredSelection selection) { + setHelpAvailable(false); // TODO have help + setWindowTitle("New Android Project"); + setImageDescriptor(); + + mMainPage = createMainPage(); + mMainPage.setTitle("New Android Project"); + mMainPage.setDescription("Creates a new Android Project resource."); + } + + /** + * Creates the wizard page. + *

          + * Please do NOT override this method. + *

          + * This is protected so that it can be overridden by unit tests. + * However the contract of this class is private and NO ATTEMPT will be made + * to maintain compatibility between different versions of the plugin. + */ + protected NewProjectCreationPage createMainPage() { + return new NewProjectCreationPage(MAIN_PAGE_NAME); + } + + // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- + // The Wizard class implements most defaults and boilerplate code needed by + // IWizard + + /** + * Adds pages to this wizard. + */ + @Override + public void addPages() { + addPage(mMainPage); + } + + /** + * Performs any actions appropriate in response to the user having pressed + * the Finish button, or refuse if finishing now is not permitted: here, it + * actually creates the workspace project and then switch to the Java + * perspective. + * + * @return True + */ + @Override + public boolean performFinish() { + if (!createAndroidProject()) { + return false; + } + + // Open the default Java Perspective + OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction(); + action.run(); + return true; + } + + // -- Custom Methods -- + + /** + * Before actually creating the project for a new project (as opposed to using an + * existing project), we check if the target location is a directory that either does + * not exist or is empty. + * + * If it's not empty, ask the user for confirmation. + * + * @param destination The destination folder where the new project is to be created. + * @return True if the destination doesn't exist yet or is an empty directory or is + * accepted by the user. + */ + private boolean validateNewProjectLocationIsEmpty(IPath destination) { + File f = new File(destination.toOSString()); + if (f.isDirectory() && f.list().length > 0) { + return AdtPlugin.displayPrompt("New Android Project", + "You are going to create a new Android Project in an existing, non-empty, directory. Are you sure you want to proceed?"); + } + return true; + } + + /** + * Creates the android project. + * @return True if the project could be created. + */ + private boolean createAndroidProject() { + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + final IProject project = workspace.getRoot().getProject(mMainPage.getProjectName()); + final IProjectDescription description = workspace.newProjectDescription(project.getName()); + + final Map parameters = new HashMap(); + parameters.put(PARAM_PROJECT, mMainPage.getProjectName()); + parameters.put(PARAM_PACKAGE, mMainPage.getPackageName()); + parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); + parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); + parameters.put(PARAM_IS_NEW_PROJECT, mMainPage.isNewProject()); + parameters.put(PARAM_SRC_FOLDER, mMainPage.getSourceFolder()); + parameters.put(PARAM_SDK_TARGET, mMainPage.getSdkTarget()); + + if (mMainPage.isCreateActivity()) { + // An activity name can be of the form ".package.Class" or ".Class". + // The initial dot is ignored, as it is always added later in the templates. + String activityName = mMainPage.getActivityName(); + if (activityName.startsWith(".")) { //$NON-NLS-1$ + activityName = activityName.substring(1); + } + parameters.put(PARAM_ACTIVITY, activityName); + } + + // create a dictionary of string that will contain name+content. + // we'll put all the strings into values/strings.xml + final HashMap stringDictionary = new HashMap(); + stringDictionary.put(STRING_APP_NAME, mMainPage.getApplicationName()); + + IPath path = mMainPage.getLocationPath(); + IPath defaultLocation = Platform.getLocation(); + if (!path.equals(defaultLocation)) { + description.setLocation(path); + } + + if (mMainPage.isNewProject() && !mMainPage.useDefaultLocation() && + !validateNewProjectLocationIsEmpty(path)) { + return false; + } + + // Create a monitored operation to create the actual project + WorkspaceModifyOperation op = new WorkspaceModifyOperation() { + @Override + protected void execute(IProgressMonitor monitor) throws InvocationTargetException { + createProjectAsync(project, description, monitor, parameters, stringDictionary); + } + }; + + // Run the operation in a different thread + runAsyncOperation(op); + return true; + } + + /** + * Runs the operation in a different thread and display generated + * exceptions. + * + * @param op The asynchronous operation to run. + */ + private void runAsyncOperation(WorkspaceModifyOperation op) { + try { + getContainer().run(true /* fork */, true /* cancelable */, op); + } catch (InvocationTargetException e) { + // The runnable threw an exception + Throwable t = e.getTargetException(); + if (t instanceof CoreException) { + CoreException core = (CoreException) t; + 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(getShell(), "Error", "Error: Case Variant Exists"); + } else { + ErrorDialog.openError(getShell(), "Error", null, core.getStatus()); + } + } else { + // Some other kind of exception + MessageDialog.openError(getShell(), "Error", t.getMessage()); + } + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * Creates the actual project, sets its nature and adds the required folders + * and files to it. This is run asynchronously in a different thread. + * + * @param project The project to create. + * @param description A description of the project. + * @param monitor An existing monitor. + * @param parameters Template parameters. + * @param stringDictionary String definition. + * @throws InvocationTargetException to wrap any unmanaged exception and + * return it to the calling thread. The method can fail if it fails + * to create or modify the project or if it is canceled by the user. + */ + private void createProjectAsync(IProject project, IProjectDescription description, + IProgressMonitor monitor, Map parameters, + Map stringDictionary) + throws InvocationTargetException { + monitor.beginTask("Create Android Project", 100); + try { + // Create project and open it + project.create(description, new SubProgressMonitor(monitor, 10)); + if (monitor.isCanceled()) throw new OperationCanceledException(); + project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 10)); + + // Add the Java and android nature to the project + AndroidNature.setupProjectNatures(project, monitor); + + // Create folders in the project if they don't already exist + addDefaultDirectories(project, AndroidConstants.WS_ROOT, DEFAULT_DIRECTORIES, monitor); + String[] sourceFolder = new String[] { (String) parameters.get(PARAM_SRC_FOLDER) }; + addDefaultDirectories(project, AndroidConstants.WS_ROOT, sourceFolder, monitor); + + // Create the resource folders in the project if they don't already exist. + addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); + + // Setup class path + IJavaProject javaProject = JavaCore.create(project); + setupSourceFolder(javaProject, sourceFolder[0], monitor); + + if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) { + // Create files in the project if they don't already exist + addManifest(project, parameters, stringDictionary, monitor); + + // add the default app icon + addIcon(project, monitor); + + // Create the default package components + addSampleCode(project, sourceFolder[0], parameters, stringDictionary, monitor); + + // add the string definition file if needed + if (stringDictionary.size() > 0) { + addStringDictionaryFile(project, stringDictionary, monitor); + } + + // Set output location + javaProject.setOutputLocation(project.getFolder(BIN_DIRECTORY).getFullPath(), + monitor); + } + + Sdk.getCurrent().setProject(project, (IAndroidTarget) parameters.get(PARAM_SDK_TARGET)); + + // Fix the project to make sure all properties are as expected. + // Necessary for existing projects and good for new ones to. + ProjectHelper.fixProject(project); + + } catch (CoreException e) { + throw new InvocationTargetException(e); + } catch (IOException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); + } + } + + /** + * Adds default directories to the project. + * + * @param project The Java Project to update. + * @param parentFolder The path of the parent folder. Must end with a + * separator. + * @param folders Folders to be added. + * @param monitor An existing monitor. + * @throws CoreException if the method fails to create the directories in + * the project. + */ + private void addDefaultDirectories(IProject project, String parentFolder, + String[] folders, IProgressMonitor monitor) throws CoreException { + for (String name : folders) { + if (name.length() > 0) { + IFolder folder = project.getFolder(parentFolder + name); + if (!folder.exists()) { + folder.create(true /* force */, true /* local */, + new SubProgressMonitor(monitor, 10)); + } + } + } + } + + /** + * Adds the manifest to the project. + * + * @param project The Java Project to update. + * @param parameters Template Parameters. + * @param stringDictionary String List to be added to a string definition + * file. This map will be filled by this method. + * @param monitor An existing monitor. + * @throws CoreException if the method fails to update the project. + * @throws IOException if the method fails to create the files in the + * project. + */ + private void addManifest(IProject project, Map parameters, + Map stringDictionary, IProgressMonitor monitor) + throws CoreException, IOException { + + // get IFile to the manifest and check if it's not already there. + IFile file = project.getFile(AndroidConstants.FN_ANDROID_MANIFEST); + if (!file.exists()) { + + // Read manifest template + String manifestTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_MANIFEST); + + // Replace all keyword parameters + manifestTemplate = replaceParameters(manifestTemplate, parameters); + + if (parameters.containsKey(PARAM_ACTIVITY)) { + // now get the activity template + String activityTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_ACTIVITIES); + + // Replace all keyword parameters to make main activity. + String activities = replaceParameters(activityTemplate, parameters); + + // set the intent. + String intent = AdtPlugin.readEmbeddedTextFile(TEMPLATE_INTENT_LAUNCHER); + + // set the intent to the main activity + activities = activities.replaceAll(PH_INTENT_FILTERS, intent); + + // set the activity(ies) in the manifest + manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, activities); + } else { + // remove the activity(ies) from the manifest + manifestTemplate = manifestTemplate.replaceAll(PH_ACTIVITIES, ""); + } + + // Save in the project as UTF-8 + InputStream stream = new ByteArrayInputStream( + manifestTemplate.getBytes("UTF-8")); //$NON-NLS-1$ + file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); + } + } + + /** + * Adds the string resource file. + * + * @param project The Java Project to update. + * @param strings The list of strings to be added to the string file. + * @param monitor An existing monitor. + * @throws CoreException if the method fails to update the project. + * @throws IOException if the method fails to create the files in the + * project. + */ + private void addStringDictionaryFile(IProject project, + Map strings, IProgressMonitor monitor) + throws CoreException, IOException { + + // create the IFile object and check if the file doesn't already exist. + IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP + + VALUES_DIRECTORY + AndroidConstants.WS_SEP + STRINGS_FILE); + if (!file.exists()) { + // get the Strings.xml template + String stringDefinitionTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRINGS); + + // get the template for one string + String stringTemplate = AdtPlugin.readEmbeddedTextFile(TEMPLATE_STRING); + + // get all the string names + Set stringNames = strings.keySet(); + + // loop on it and create the string definitions + StringBuilder stringNodes = new StringBuilder(); + for (String key : stringNames) { + // get the value from the key + String value = strings.get(key); + + // place them in the template + String stringDef = stringTemplate.replace(PARAM_STRING_NAME, key); + stringDef = stringDef.replace(PARAM_STRING_CONTENT, value); + + // append to the other string + if (stringNodes.length() > 0) { + stringNodes.append("\n"); + } + stringNodes.append(stringDef); + } + + // put the string nodes in the Strings.xml template + stringDefinitionTemplate = stringDefinitionTemplate.replace(PH_STRINGS, + stringNodes.toString()); + + // write the file as UTF-8 + InputStream stream = new ByteArrayInputStream( + stringDefinitionTemplate.getBytes("UTF-8")); //$NON-NLS-1$ + file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); + } + } + + + /** + * Adds default application icon to the project. + * + * @param project The Java Project to update. + * @param monitor An existing monitor. + * @throws CoreException if the method fails to update the project. + */ + private void addIcon(IProject project, IProgressMonitor monitor) + throws CoreException { + IFile file = project.getFile(RES_DIRECTORY + AndroidConstants.WS_SEP + + DRAWABLE_DIRECTORY + AndroidConstants.WS_SEP + ICON); + if (!file.exists()) { + // read the content from the template + byte[] buffer = AdtPlugin.readEmbeddedFile(TEMPLATES_DIRECTORY + ICON); + + // if valid + if (buffer != null) { + // Save in the project + InputStream stream = new ByteArrayInputStream(buffer); + file.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); + } + } + } + + /** + * Creates the package folder and copies the sample code in the project. + * + * @param project The Java Project to update. + * @param parameters Template Parameters. + * @param stringDictionary String List to be added to a string definition + * file. This map will be filled by this method. + * @param monitor An existing monitor. + * @throws CoreException if the method fails to update the project. + * @throws IOException if the method fails to create the files in the + * project. + */ + private void addSampleCode(IProject project, String sourceFolder, + Map parameters, Map stringDictionary, + IProgressMonitor monitor) throws CoreException, IOException { + // create the java package directories. + IFolder pkgFolder = project.getFolder(sourceFolder); + String packageName = (String) parameters.get(PARAM_PACKAGE); + + // The PARAM_ACTIVITY key will be absent if no activity should be created, + // in which case activityName will be null. + String activityName = (String) parameters.get(PARAM_ACTIVITY); + Map java_activity_parameters = parameters; + if (activityName != null) { + if (activityName.indexOf('.') >= 0) { + // There are package names in the activity name. Transform packageName to add + // those sub packages and remove them from activityName. + packageName += "." + activityName; //$NON-NLS-1$ + int pos = packageName.lastIndexOf('.'); + activityName = packageName.substring(pos + 1); + packageName = packageName.substring(0, pos); + + // Also update the values used in the JAVA_FILE_TEMPLATE below + // (but not the ones from the manifest so don't change the caller's dictionary) + java_activity_parameters = new HashMap(parameters); + java_activity_parameters.put(PARAM_PACKAGE, packageName); + java_activity_parameters.put(PARAM_ACTIVITY, activityName); + } + } + + String[] components = packageName.split(AndroidConstants.RE_DOT); + for (String component : components) { + pkgFolder = pkgFolder.getFolder(component); + if (!pkgFolder.exists()) { + pkgFolder.create(true /* force */, true /* local */, + new SubProgressMonitor(monitor, 10)); + } + } + + if (activityName != null) { + // create the main activity Java file + String activityJava = activityName + AndroidConstants.DOT_JAVA; + IFile file = pkgFolder.getFile(activityJava); + if (!file.exists()) { + copyFile(JAVA_ACTIVITY_TEMPLATE, file, java_activity_parameters, monitor); + } + } + + // create the layout file + IFolder layoutfolder = project.getFolder(RES_DIRECTORY).getFolder(LAYOUT_DIRECTORY); + IFile file = layoutfolder.getFile(MAIN_LAYOUT_XML); + if (!file.exists()) { + copyFile(LAYOUT_TEMPLATE, file, parameters, monitor); + if (activityName != null) { + stringDictionary.put(STRING_HELLO_WORLD, "Hello World, " + activityName + "!"); + } else { + stringDictionary.put(STRING_HELLO_WORLD, "Hello World!"); + } + } + } + + /** + * Adds the given folder to the project's class path. + * + * @param javaProject The Java Project to update. + * @param sourceFolder Template Parameters. + * @param monitor An existing monitor. + * @throws JavaModelException if the classpath could not be set. + */ + private void setupSourceFolder(IJavaProject javaProject, String sourceFolder, + IProgressMonitor monitor) throws JavaModelException { + IProject project = javaProject.getProject(); + + // Add "src" to class path + IFolder srcFolder = project.getFolder(sourceFolder); + + IClasspathEntry[] entries = javaProject.getRawClasspath(); + entries = removeSourceClasspath(entries, srcFolder); + entries = removeSourceClasspath(entries, srcFolder.getParent()); + + entries = ProjectHelper.addEntryToClasspath(entries, + JavaCore.newSourceEntry(srcFolder.getFullPath())); + + javaProject.setRawClasspath(entries, new SubProgressMonitor(monitor, 10)); + } + + + /** + * Removes the corresponding source folder from the class path entries if + * found. + * + * @param entries The class path entries to read. A copy will be returned. + * @param folder The parent source folder to remove. + * @return A new class path entries array. + */ + private IClasspathEntry[] removeSourceClasspath(IClasspathEntry[] entries, IContainer folder) { + if (folder == null) { + return entries; + } + IClasspathEntry source = JavaCore.newSourceEntry(folder.getFullPath()); + int n = entries.length; + for (int i = n - 1; i >= 0; i--) { + if (entries[i].equals(source)) { + IClasspathEntry[] newEntries = new IClasspathEntry[n - 1]; + if (i > 0) System.arraycopy(entries, 0, newEntries, 0, i); + if (i < n - 1) System.arraycopy(entries, i + 1, newEntries, i, n - i - 1); + n--; + entries = newEntries; + } + } + return entries; + } + + + /** + * Copies the given file from our resource folder to the new project. + * Expects the file to the US-ASCII or UTF-8 encoded. + * + * @throws CoreException from IFile if failing to create the new file. + * @throws MalformedURLException from URL if failing to interpret the URL. + * @throws FileNotFoundException from RandomAccessFile. + * @throws IOException from RandomAccessFile.length() if can't determine the + * length. + */ + private void copyFile(String resourceFilename, IFile destFile, + Map parameters, IProgressMonitor monitor) + throws CoreException, IOException { + + // Read existing file. + String template = AdtPlugin.readEmbeddedTextFile( + TEMPLATES_DIRECTORY + resourceFilename); + + // Replace all keyword parameters + template = replaceParameters(template, parameters); + + // Save in the project as UTF-8 + InputStream stream = new ByteArrayInputStream(template.getBytes("UTF-8")); //$NON-NLS-1$ + destFile.create(stream, false /* force */, new SubProgressMonitor(monitor, 10)); + } + + /** + * Returns an image descriptor for the wizard logo. + */ + private void setImageDescriptor() { + ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + } + + /** + * Replaces placeholders found in a string with values. + * + * @param str the string to search for placeholders. + * @param parameters a map of to search for in the string + * @return A new String object with the placeholder replaced by the values. + */ + private String replaceParameters(String str, Map parameters) { + for (Entry entry : parameters.entrySet()) { + if (entry.getValue() instanceof String) { + str = str.replaceAll(entry.getKey(), (String) entry.getValue()); + } + } + + return str; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java new file mode 100644 index 0000000..f3f7b79 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/AndroidConstants.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2007 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.common; + +import com.android.sdklib.SdkConstants; + +import java.io.File; + +/** + * Constant definition class.
          + *
          + * Most constants have a prefix defining the content. + *

            + *
          • WS_ Workspace path constant. Those are absolute paths, + * from the project root.
          • + *
          • OS_ OS path constant. These paths are different depending on the platform.
          • + *
          • FN_ File name constant.
          • + *
          • FD_ Folder name constant.
          • + *
          • MARKER_ Resource Marker Ids constant.
          • + *
          • EXT_ File extension constant. This does NOT include a dot.
          • + *
          • DOT_ File extension constant. This start with a dot.
          • + *
          • RE_ Regexp constant.
          • + *
          • NS_ Namespace constant.
          • + *
          • CLASS_ Fully qualified class name.
          • + *
          + * + */ +public class AndroidConstants { + /** + * The old Editors Plugin ID. It is still used in some places for compatibility. + * Please do not use for new features. + */ + public static final String EDITORS_NAMESPACE = "com.android.ide.eclipse.editors"; // $NON-NLS-1$ + + /** Nature of android projects */ + public final static String NATURE = "com.android.ide.eclipse.adt.AndroidNature"; //$NON-NLS-1$ + + public final static int PLATFORM_UNKNOWN = 0; + public final static int PLATFORM_LINUX = 1; + public final static int PLATFORM_WINDOWS = 2; + public final static int PLATFORM_DARWIN = 3; + + /** + * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, + * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. + */ + public final static int CURRENT_PLATFORM = currentPlatform(); + + /** Separator for workspace path, i.e. "/". */ + public final static String WS_SEP = "/"; //$NON-NLS-1$ + /** Separator character for workspace path, i.e. '/'. */ + public final static char WS_SEP_CHAR = '/'; + + /** Extension of the Application package Files, i.e. "apk". */ + public final static String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$ + /** Extension of java files, i.e. "java" */ + public final static String EXT_JAVA = "java"; //$NON-NLS-1$ + /** Extension of compiled java files, i.e. "class" */ + public final static String EXT_CLASS = "class"; //$NON-NLS-1$ + /** Extension of xml files, i.e. "xml" */ + public final static String EXT_XML = "xml"; //$NON-NLS-1$ + /** Extension of jar files, i.e. "jar" */ + public final static String EXT_JAR = "jar"; //$NON-NLS-1$ + /** Extension of aidl files, i.e. "aidl" */ + public final static String EXT_AIDL = "aidl"; //$NON-NLS-1$ + /** Extension of native libraries, i.e. "so" */ + public final static String EXT_NATIVE_LIB = "so"; //$NON-NLS-1$ + + private final static String DOT = "."; //$NON-NLS-1$ + + /** Dot-Extension of the Application package Files, i.e. ".apk". */ + public final static String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE; + /** Dot-Extension of java files, i.e. ".java" */ + public final static String DOT_JAVA = DOT + EXT_JAVA; + /** Dot-Extension of compiled java files, i.e. ".class" */ + public final static String DOT_CLASS = DOT + EXT_CLASS; + /** Dot-Extension of xml files, i.e. ".xml" */ + public final static String DOT_XML = DOT + EXT_XML; + /** Dot-Extension of jar files, i.e. ".jar" */ + public final static String DOT_JAR = DOT + EXT_JAR; + /** Dot-Extension of aidl files, i.e. ".aidl" */ + public final static String DOT_AIDL = DOT + EXT_AIDL; + + /** Name of the manifest file, i.e. "AndroidManifest.xml". */ + public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$ + public static final String FN_PROJECT_AIDL = "project.aidl"; //$NON-NLS-1$ + + /** dex.jar file */ + public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$ + /** Name of the android sources directory */ + public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ + + /** Resource java class filename, i.e. "R.java" */ + public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$ + /** Resource class file filename, i.e. "R.class" */ + public final static String FN_COMPILED_RESOURCE_CLASS = "R.class"; //$NON-NLS-1$ + /** Manifest java class filename, i.e. "Manifest.java" */ + public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$ + /** Dex conversion output filname, i.e. "classes.dex" */ + public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$ + /** Temporary packaged resources file name, i.e. "resources.ap_" */ + public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$ + + public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$ + + public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$ + + public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$ + + public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ + + public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? + "traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$ + + /** Folder Names for Android Projects . */ + + /* Resources folder name, i.e. "res". */ + public final static String FD_RESOURCES = "res"; //$NON-NLS-1$ + /** Assets folder name, i.e. "assets" */ + public final static String FD_ASSETS = "assets"; //$NON-NLS-1$ + /** Default source folder name, i.e. "src" */ + public final static String FD_SOURCES = "src"; //$NON-NLS-1$ + /** Default native library folder name inside the project, i.e. "libs" + * While the folder inside the .apk is "lib", we call that one libs because + * that's what we use in ant for both .jar and .so and we need to make the 2 development ways + * compatible. */ + public final static String FD_NATIVE_LIBS = "libs"; //$NON-NLS-1$ + /** Native lib folder inside the APK: "lib" */ + public final static String FD_APK_NATIVE_LIBS = "lib"; //$NON-NLS-1$ + /** Default bin folder name, i.e. "bin" */ + public final static String FD_BINARIES = "bin"; //$NON-NLS-1$ + /** Default anim resource folder name, i.e. "anim" */ + public final static String FD_ANIM = "anim"; //$NON-NLS-1$ + /** Default color resource folder name, i.e. "color" */ + public final static String FD_COLOR = "color"; //$NON-NLS-1$ + /** Default drawable resource folder name, i.e. "drawable" */ + public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$ + /** Default layout resource folder name, i.e. "layout" */ + public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$ + /** Default menu resource folder name, i.e. "menu" */ + public final static String FD_MENU = "menu"; //$NON-NLS-1$ + /** Default values resource folder name, i.e. "values" */ + public final static String FD_VALUES = "values"; //$NON-NLS-1$ + /** Default xml resource folder name, i.e. "xml" */ + public final static String FD_XML = "xml"; //$NON-NLS-1$ + /** Default raw resource folder name, i.e. "raw" */ + public final static String FD_RAW = "raw"; //$NON-NLS-1$ + + /** Absolute path of the workspace root, i.e. "/" */ + public final static String WS_ROOT = WS_SEP; + + /** Absolute path of the resource folder, eg "/res".
          This is a workspace path. */ + public final static String WS_RESOURCES = WS_SEP + FD_RESOURCES; + + /** Absolute path of the resource folder, eg "/assets".
          This is a workspace path. */ + public final static String WS_ASSETS = WS_SEP + FD_ASSETS; + + /** Leaf of the javaDoc folder. Does not start with a separator. */ + public final static String WS_JAVADOC_FOLDER_LEAF = SdkConstants.FD_DOCS + "/reference"; //$NON-NLS-1$ + + /** Path of the samples directory relative to the sdk folder. + * This is an OS path, ending with a separator. + * FIXME: remove once the NPW is fixed. */ + public final static String OS_SDK_SAMPLES_FOLDER = SdkConstants.FD_SAMPLES + File.separator; + + /** Path of the dx.jar file relative to the sdk folder. */ + public final static String OS_SDK_LIBS_DX_JAR = + SdkConstants.OS_SDK_TOOLS_LIB_FOLDER + FN_DX_JAR; + + /** Regexp for single dot */ + public final static String RE_DOT = "\\."; //$NON-NLS-1$ + /** Regexp for java extension, i.e. "\.java$" */ + public final static String RE_JAVA_EXT = "\\.java$"; //$NON-NLS-1$ + /** Regexp for aidl extension, i.e. "\.aidl$" */ + public final static String RE_AIDL_EXT = "\\.aidl$"; //$NON-NLS-1$ + + /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */ + public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ + + /** Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" */ + public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$ + + /** The old common plug-in ID. Please do not use for new features. */ + public static final String COMMON_PLUGIN_ID = "com.android.ide.eclipse.common"; //$NON-NLS-1$ + + /** aapt marker error. */ + public final static String MARKER_AAPT = COMMON_PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$ + + /** XML marker error. */ + public final static String MARKER_XML = COMMON_PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$ + + /** aidl marker error. */ + public final static String MARKER_AIDL = COMMON_PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$ + + /** android marker error */ + public final static String MARKER_ANDROID = COMMON_PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$ + + /** Name for the "type" marker attribute */ + public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$ + /** Name for the "class" marker attribute */ + public final static String MARKER_ATTR_CLASS = "android.class"; //$NON-NLS-1$ + /** activity value for marker attribute "type" */ + public final static String MARKER_ATTR_TYPE_ACTIVITY = "activity"; //$NON-NLS-1$ + /** service value for marker attribute "type" */ + public final static String MARKER_ATTR_TYPE_SERVICE = "service"; //$NON-NLS-1$ + /** receiver value for marker attribute "type" */ + public final static String MARKER_ATTR_TYPE_RECEIVER = "receiver"; //$NON-NLS-1$ + /** provider value for marker attribute "type" */ + public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$ + + public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ + public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ + public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ + public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$ + public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$ + public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$ + public final static String CLASS_R = "android.R"; //$NON-NLS-1$ + public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$ + public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$ + public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$ + public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$ + public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$ + public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$ + public final static String CLASS_VIEWGROUP_LAYOUTPARAMS = + CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$ + public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$ + public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$ + public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$ + public final static String CLASS_PREFERENCES = + "android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$ + public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$ + public final static String CLASS_PARCELABLE = "android.os.Parcelable"; //$NON-NLS-1$ + + public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$ + + /** + * Prefered compiler level, i.e. "1.5". + */ + public final static String COMPILER_COMPLIANCE_PREFERRED = "1.5"; //$NON-NLS-1$ + /** + * List of valid compiler level, i.e. "1.5" and "1.6" + */ + public final static String[] COMPILER_COMPLIANCE = { + "1.5", //$NON-NLS-1$ + "1.6", //$NON-NLS-1$ + }; + + /** The base URL where to find the Android class & manifest documentation */ + public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$ + + /** + * Returns current platform + * + * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, + * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. + */ + private static int currentPlatform() { + String os = System.getProperty("os.name"); //$NON-NLS-1$ + if (os.startsWith("Mac OS")) { //$NON-NLS-1$ + return PLATFORM_DARWIN; + } else if (os.startsWith("Windows")) { //$NON-NLS-1$ + return PLATFORM_WINDOWS; + } else if (os.startsWith("Linux")) { //$NON-NLS-1$ + return PLATFORM_LINUX; + } + + return PLATFORM_UNKNOWN; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/EclipseUiHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/EclipseUiHelper.java new file mode 100644 index 0000000..6dc8562 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/EclipseUiHelper.java @@ -0,0 +1,64 @@ +/* + * 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.common; + +import org.eclipse.ui.IViewPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; + +/** + * Helpers for Eclipse UI related stuff. + */ +public final class EclipseUiHelper { + + /** View Id for the default Eclipse Content Outline view. */ + public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline"; + /** View Id for the default Eclipse Property Sheet view. */ + public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet"; + + /** This class never gets instantiated. */ + private EclipseUiHelper() { + } + + /** + * Shows the corresponding view. + *

          + * Silently fails in case of error. + * + * @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}. + * @param activate True to force activate (i.e. takes focus), false to just make visible (i.e. + * does not steal focus.) + */ + public static void showView(String viewId, boolean activate) { + IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (win != null) { + IWorkbenchPage page = win.getActivePage(); + if (page != null) { + try { + IViewPart part = page.showView(viewId, + null /* secondaryId */, + activate ? IWorkbenchPage.VIEW_ACTIVATE : IWorkbenchPage.VIEW_VISIBLE); + } catch (PartInitException e) { + // ignore + } + } + } + + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/Messages.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/Messages.java new file mode 100644 index 0000000..3f1bde4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/Messages.java @@ -0,0 +1,21 @@ + +package com.android.ide.eclipse.common; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "com.android.ide.eclipse.common.messages"; //$NON-NLS-1$ + + public static String Console_Data_Project_Tag; + + public static String Console_Date_Tag; + + + static { + // initialize resource bundle + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + private Messages() { + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java new file mode 100644 index 0000000..345c663 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/SdkStatsHelper.java @@ -0,0 +1,39 @@ +/* + * 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.common; + +import com.android.sdkstats.SdkStatsService; + +import org.osgi.framework.Version; + +/** + * Helper class to access the ping usage stat server. + */ +public class SdkStatsHelper { + + /** + * Pings the usage start server. + * @param pluginName the name of the plugin to appear in the stats + * @param pluginVersion the {@link Version} of the plugin. + */ + public static void pingUsageServer(String pluginName, Version pluginVersion) { + String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(), + pluginVersion.getMinor(), pluginVersion.getMicro()); + + SdkStatsService.ping(pluginName, versionString); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/StreamHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/StreamHelper.java new file mode 100644 index 0000000..6ccf4f2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/StreamHelper.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2007 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.common; + +import org.eclipse.ui.console.MessageConsoleStream; + +import java.util.Calendar; + +/** + * Stream helper class. + */ +public class StreamHelper { + + /** + * Prints messages, associated with a project to the specified stream + * @param stream The stream to write to + * @param tag The tag associated to the message. Can be null + * @param objects The objects to print through their toString() method (or directly for + * {@link String} objects. + */ + public static synchronized void printToStream(MessageConsoleStream stream, String tag, + Object... objects) { + String dateTag = getMessageTag(tag); + + for (Object obj : objects) { + stream.print(dateTag); + if (obj instanceof String) { + stream.println((String)obj); + } else { + stream.println(obj.toString()); + } + } + } + + /** + * Creates a string containing the current date/time, and the tag + * @param tag The tag associated to the message. Can be null + * @return The dateTag + */ + public static String getMessageTag(String tag) { + Calendar c = Calendar.getInstance(); + + if (tag == null) { + return String.format(Messages.Console_Date_Tag, c); + } + + return String.format(Messages.Console_Data_Project_Tag, c, tag); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/messages.properties b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/messages.properties new file mode 100644 index 0000000..dba6edc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/messages.properties @@ -0,0 +1,2 @@ +Console_Date_Tag=[%1$tF %1$tT] +Console_Data_Project_Tag=[%1$tF %1$tT - %2$s] diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java new file mode 100644 index 0000000..58c2f40 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java @@ -0,0 +1,123 @@ +/* + * 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.common.preferences; + +import com.android.sdkstats.SdkStatsService; + +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.PreferencePage; +import org.eclipse.jface.preference.PreferenceStore; +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.Control; +import org.eclipse.swt.widgets.Link; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + +import java.io.IOException; + +public class UsagePreferencePage extends PreferencePage implements IWorkbenchPreferencePage { + + private BooleanFieldEditor mOptInCheckBox; + + public UsagePreferencePage() { + } + + public void init(IWorkbench workbench) { + // pass + } + + @Override + protected Control createContents(Composite parent) { + Composite top = new Composite(parent, SWT.NONE); + top.setLayout(new GridLayout(1, false)); + top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Link text = new Link(top, SWT.WRAP); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.widthHint = 200; + text.setLayoutData(gd); + text.setText(SdkStatsService.BODY_TEXT); + + text.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + SdkStatsService.openUrl(event.text); + } + }); + + mOptInCheckBox = new BooleanFieldEditor(SdkStatsService.PING_OPT_IN, + SdkStatsService.CHECKBOX_TEXT, top); + mOptInCheckBox.setPage(this); + mOptInCheckBox.setPreferenceStore(SdkStatsService.getPreferenceStore()); + mOptInCheckBox.load(); + + return top; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performCancel() + */ + @Override + public boolean performCancel() { + mOptInCheckBox.load(); + return super.performCancel(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performDefaults() + */ + @Override + protected void performDefaults() { + mOptInCheckBox.loadDefault(); + super.performDefaults(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performOk() + */ + @Override + public boolean performOk() { + save(); + return super.performOk(); + } + + /* (non-Javadoc) + * @see org.eclipse.jface.preference.PreferencePage#performApply() + */ + @Override + protected void performApply() { + save(); + super.performApply(); + } + + private void save() { + try { + PreferenceStore store = SdkStatsService.getPreferenceStore(); + if (store != null) { + store.setValue(SdkStatsService.PING_OPT_IN, mOptInCheckBox.getBooleanValue()); + store.save(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java new file mode 100644 index 0000000..2db9e9b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2007 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.common.project; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.xml.sax.InputSource; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathExpressionException; + +/** + * Utility class that manages the AndroidManifest.xml file. + *

          + * All the get method work by XPath. Repeated calls to those may warrant using + * {@link AndroidManifestParser} instead. + */ +public class AndroidManifestHelper { + private IFile mManifestIFile; + private File mManifestFile; + private XPath mXPath; + + /** + * Creates an AndroidManifest based on an existing Eclipse {@link IProject} object. + *

          + * Use {@link #exists()} to check if the manifest file really exists in the project. + * + * @param project The project to search for the manifest. + */ + public AndroidManifestHelper(IProject project) { + mXPath = AndroidXPathFactory.newXPath(); + mManifestIFile = getManifest(project); + } + + /** + * Creates an AndroidManifest based on a file path. + *

          + * Use {@link #exists()} to check if the manifest file really exists. + * + * @param osManifestFilePath the os path to the AndroidManifest.xml file. + */ + public AndroidManifestHelper(String osManifestFilePath) { + mXPath = AndroidXPathFactory.newXPath(); + mManifestFile = new File(osManifestFilePath); + } + + + /** + * Returns the underlying {@link IFile} for the android manifest XML file, if found in the + * given Eclipse project. + * + * Always return null if the constructor that takes an {@link IProject} was NOT called. + * + * @return The IFile for the androidManifest.xml or null if no such file could be found. + */ + public IFile getManifestIFile() { + return mManifestIFile; + } + + /** + * Returns the underlying {@link File} for the android manifest XML file. + */ + public File getManifestFile() { + if (mManifestIFile != null) { + return mManifestIFile.getLocation().toFile(); + } + + return mManifestFile; + } + + /** + * Returns the package name defined in the manifest file. + * + * @return A String object with the package or null if any error happened. + */ + public String getPackageName() { + try { + return getPackageNameInternal(mXPath, getSource()); + } catch (XPathExpressionException e1) { + // If the XPath failed to evaluate, we'll return null. + } catch (Exception e) { + // if this happens this is due to the resource being out of sync. + // so we must refresh it and do it again + + // for any other kind of exception we must return null as well; + } + + return null; + } + + /** + * Returns the i-th activity defined in the manifest file. + * + * @param manifest The manifest's IFile object. + * @param index The 1-based index of the activity to return. + * @param xpath An optional xpath object. If null is provided a new one will + * be created. + * @return A String object with the activity or null if any error happened. + */ + public String getActivityName(int index) { + try { + return getActivityNameInternal(index, mXPath, getSource()); + } catch (XPathExpressionException e1) { + // If the XPath failed to evaluate, we'll return null. + } catch (Exception e) { + // if this happens this is due to the resource being out of sync. + // so we must refresh it and do it again + + // for any other kind of exception we must return null as well; + } + return null; + } + + /** + * Returns an IFile object representing the manifest for the specified + * project. + * + * @param project The project containing the manifest file. + * @return An IFile object pointing to the manifest or null if the manifest + * is missing. + */ + public static IFile getManifest(IProject project) { + IResource r = project.findMember(AndroidConstants.WS_SEP + + AndroidConstants.FN_ANDROID_MANIFEST); + + if (r == null || r.exists() == false || (r instanceof IFile) == false) { + return null; + } + return (IFile) r; + } + + /** + * Combines a java package, with a class value from the manifest to make a fully qualified + * class name + * @param javaPackage the java package from the manifest. + * @param className the class name from the manifest. + * @return the fully qualified class name. + */ + public static String combinePackageAndClassName(String javaPackage, String className) { + if (className == null || className.length() == 0) { + return javaPackage; + } + if (javaPackage == null || javaPackage.length() == 0) { + return className; + } + + // the class name can be a subpackage (starts with a '.' + // char), a simple class name (no dot), or a full java package + boolean startWithDot = (className.charAt(0) == '.'); + boolean hasDot = (className.indexOf('.') != -1); + if (startWithDot || hasDot == false) { + + // add the concatenation of the package and class name + if (startWithDot) { + return javaPackage + className; + } else { + return javaPackage + '.' + className; + } + } else { + // just add the class as it should be a fully qualified java name. + return className; + } + } + + + + /** + * Returns true either if an androidManifest.xml file was found in the project + * or if the given file path exists. + */ + public boolean exists() { + if (mManifestIFile != null) { + return mManifestIFile.exists(); + } else if (mManifestFile != null) { + return mManifestFile.exists(); + } + + return false; + } + + /** + * Returns an InputSource for XPath. + * + * @throws FileNotFoundException if file does not exist. + * @throws CoreException if the {@link IFile} does not exist. + */ + private InputSource getSource() throws FileNotFoundException, CoreException { + if (mManifestIFile != null) { + return new InputSource(mManifestIFile.getContents()); + } else if (mManifestFile != null) { + return new InputSource(new FileReader(mManifestFile)); + } + + return null; + } + + /** + * Performs the actual XPath evaluation to get the package name. + * Extracted so that we can share it with AndroidManifestFromProject. + */ + private static String getPackageNameInternal(XPath xpath, InputSource source) + throws XPathExpressionException { + return xpath.evaluate("/manifest/@package", source); //$NON-NLS-1$ + } + + /** + * Performs the actual XPath evaluation to get the activity name. + * Extracted so that we can share it with AndroidManifestFromProject. + */ + private static String getActivityNameInternal(int index, XPath xpath, InputSource source) + throws XPathExpressionException { + return xpath.evaluate("/manifest/application/activity[" //$NON-NLS-1$ + + index + + "]/@" //$NON-NLS-1$ + + AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$ + source); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java new file mode 100644 index 0000000..cb98525 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java @@ -0,0 +1,666 @@ +/* + * Copyright (C) 2007 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.common.project; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Set; +import java.util.TreeSet; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +public class AndroidManifestParser { + + private final static String ATTRIBUTE_PACKAGE = "package"; //$NON-NLS-1$ + private final static String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ + private final static String ATTRIBUTE_PROCESS = "process"; //$NON-NLS-$ + private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$ + private final static String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion"; //$NON-NLS-$ + private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$ + private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$ + private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$ + private final static String NODE_SERVICE = "service"; //$NON-NLS-1$ + private final static String NODE_RECEIVER = "receiver"; //$NON-NLS-1$ + private final static String NODE_PROVIDER = "provider"; //$NON-NLS-1$ + private final static String NODE_INTENT = "intent-filter"; //$NON-NLS-1$ + private final static String NODE_ACTION = "action"; //$NON-NLS-1$ + private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$ + private final static String NODE_USES_SDK = "uses-sdk"; //$NON-NLS-1$ + + private final static int LEVEL_MANIFEST = 0; + private final static int LEVEL_APPLICATION = 1; + private final static int LEVEL_ACTIVITY = 2; + private final static int LEVEL_INTENT_FILTER = 3; + private final static int LEVEL_CATEGORY = 4; + + private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ + private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ + + private static class ManifestHandler extends XmlErrorHandler { + + //--- data read from the parsing + + /** Application package */ + private String mPackage; + /** List of all activities */ + private final ArrayList mActivities = new ArrayList(); + /** Launcher activity */ + private String mLauncherActivity = null; + /** list of process names declared by the manifest */ + private Set mProcesses = null; + /** debuggable attribute value. If null, the attribute is not present. */ + private Boolean mDebuggable = null; + /** API level requirement. if 0 the attribute was not present. */ + private int mApiLevelRequirement = 0; + + //--- temporary data/flags used during parsing + private IJavaProject mJavaProject; + private boolean mGatherData = false; + private boolean mMarkErrors = false; + private int mCurrentLevel = 0; + private int mValidLevel = 0; + private boolean mFoundMainAction = false; + private boolean mFoundLauncherCategory = false; + private String mCurrentActivity = null; + private Locator mLocator; + + /** + * + * @param manifestFile + * @param errorListener + * @param gatherData + * @param javaProject + * @param markErrors + */ + ManifestHandler(IFile manifestFile, XmlErrorListener errorListener, + boolean gatherData, IJavaProject javaProject, boolean markErrors) { + super(manifestFile, errorListener); + mGatherData = gatherData; + mJavaProject = javaProject; + mMarkErrors = markErrors; + } + + /** + * Returns the package defined in the manifest, if found. + * @return The package name or null if not found. + */ + String getPackage() { + return mPackage; + } + + /** + * Returns the list of activities found in the manifest. + * @return An array of fully qualified class names, or empty if no activity were found. + */ + String[] getActivities() { + return mActivities.toArray(new String[mActivities.size()]); + } + + /** + * Returns the name of one activity found in the manifest, that is configured to show + * up in the HOME screen. + * @return the fully qualified name of a HOME activity or null if none were found. + */ + String getLauncherActivity() { + return mLauncherActivity; + } + + /** + * Returns the list of process names declared by the manifest. + */ + String[] getProcesses() { + if (mProcesses != null) { + return mProcesses.toArray(new String[mProcesses.size()]); + } + + return new String[0]; + } + + /** + * Returns the debuggable attribute value or null if it is not set. + */ + Boolean getDebuggable() { + return mDebuggable; + } + + /** + * Returns the minSdkVersion attribute, or 0 if it's not set. + */ + int getApiLevelRequirement() { + return mApiLevelRequirement; + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) + */ + @Override + public void setDocumentLocator(Locator locator) { + mLocator = locator; + super.setDocumentLocator(locator); + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, + * java.lang.String, org.xml.sax.Attributes) + */ + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) + throws SAXException { + try { + if (mGatherData == false) { + return; + } + + // if we're at a valid level + if (mValidLevel == mCurrentLevel) { + String value; + switch (mValidLevel) { + case LEVEL_MANIFEST: + if (NODE_MANIFEST.equals(localName)) { + // lets get the package name. + mPackage = getAttributeValue(attributes, ATTRIBUTE_PACKAGE, + false /* hasNamespace */); + mValidLevel++; + } + break; + case LEVEL_APPLICATION: + if (NODE_APPLICATION.equals(localName)) { + value = getAttributeValue(attributes, ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (value != null) { + addProcessName(value); + } + + value = getAttributeValue(attributes, ATTRIBUTE_DEBUGGABLE, + true /* hasNamespace*/); + if (value != null) { + mDebuggable = Boolean.parseBoolean(value); + } + + mValidLevel++; + } else if (NODE_USES_SDK.equals(localName)) { + value = getAttributeValue(attributes, ATTRIBUTE_MIN_SDK_VERSION, + true /* hasNamespace */); + + try { + mApiLevelRequirement = Integer.parseInt(value); + } catch (NumberFormatException e) { + handleError(e, -1 /* lineNumber */); + } + } + break; + case LEVEL_ACTIVITY: + if (NODE_ACTIVITY.equals(localName)) { + processActivityNode(attributes); + mValidLevel++; + } else if (NODE_SERVICE.equals(localName)) { + processNode(attributes, AndroidConstants.CLASS_SERVICE); + mValidLevel++; + } else if (NODE_RECEIVER.equals(localName)) { + processNode(attributes, AndroidConstants.CLASS_BROADCASTRECEIVER); + mValidLevel++; + } else if (NODE_PROVIDER.equals(localName)) { + processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER); + mValidLevel++; + } + break; + case LEVEL_INTENT_FILTER: + // only process this level if we are in an activity + if (mCurrentActivity != null && NODE_INTENT.equals(localName)) { + // if we're at the intent level, lets reset some flag to + // be used when parsing the children + mFoundMainAction = false; + mFoundLauncherCategory = false; + mValidLevel++; + } + break; + case LEVEL_CATEGORY: + if (mCurrentActivity != null && mLauncherActivity == null) { + if (NODE_ACTION.equals(localName)) { + // get the name attribute + if (ACTION_MAIN.equals( + getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */))) { + mFoundMainAction = true; + } + } else if (NODE_CATEGORY.equals(localName)) { + if (CATEGORY_LAUNCHER.equals( + getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */))) { + mFoundLauncherCategory = true; + } + } + + // no need to increase mValidLevel as we don't process anything + // below this level. + } + break; + } + } + + mCurrentLevel++; + } finally { + super.startElement(uri, localName, name, attributes); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, + * java.lang.String) + */ + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + try { + if (mGatherData == false) { + return; + } + + // decrement the levels. + if (mValidLevel == mCurrentLevel) { + mValidLevel--; + } + mCurrentLevel--; + + // if we're at a valid level + // process the end of the element + if (mValidLevel == mCurrentLevel) { + switch (mValidLevel) { + case LEVEL_ACTIVITY: + mCurrentActivity = null; + break; + case LEVEL_INTENT_FILTER: + // if we found both a main action and a launcher category, this is our + // launcher activity! + if (mCurrentActivity != null && + mFoundMainAction && mFoundLauncherCategory) { + mLauncherActivity = mCurrentActivity; + } + break; + default: + break; + } + + } + } finally { + super.endElement(uri, localName, name); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) + */ + @Override + public void error(SAXParseException e) throws SAXException { + if (mMarkErrors) { + handleError(e, e.getLineNumber()); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) + */ + @Override + public void fatalError(SAXParseException e) throws SAXException { + if (mMarkErrors) { + handleError(e, e.getLineNumber()); + } + } + + /* (non-Javadoc) + * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) + */ + @Override + public void warning(SAXParseException e) throws SAXException { + if (mMarkErrors) { + super.warning(e); + } + } + + /** + * Processes the activity node. + * @param attributes the attributes for the activity node. + * @throws CoreException + */ + private void processActivityNode(Attributes attributes) { + // lets get the activity name, and add it to the list + String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */); + if (activityName != null) { + mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage, + activityName); + mActivities.add(mCurrentActivity); + + if (mMarkErrors) { + checkClass(mCurrentActivity, AndroidConstants.CLASS_ACTIVITY, + true /* testVisibility */); + } + } else { + // no activity found! Aapt will output an error, + // so we don't have to do anything + mCurrentActivity = activityName; + } + + String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (processName != null) { + addProcessName(processName); + } + } + + /** + * Processes the service/receiver/provider nodes. + * @param attributes the attributes for the activity node. + * @param superClassName the fully qualified name of the super class that this + * node is representing + * @throws CoreException + */ + private void processNode(Attributes attributes, String superClassName) { + // lets get the class name, and check it if required. + String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME, + true /* hasNamespace */); + if (serviceName != null) { + serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage, + serviceName); + + if (mMarkErrors) { + checkClass(serviceName, superClassName, false /* testVisibility */); + } + } + + String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, + true /* hasNamespace */); + if (processName != null) { + addProcessName(processName); + } + } + + /** + * Checks that a class is valid and can be used in the Android Manifest. + *

          + * Errors are put as {@link IMarker} on the manifest file. + * @param className the fully qualified name of the class to test. + * @param superClassName the fully qualified name of the class it is supposed to extend. + * @param testVisibility if true, the method will check the visibility of + * the class or of its constructors. + */ + private void checkClass(String className, String superClassName, boolean testVisibility) { + // we need to check the validity of the activity. + String result = BaseProjectHelper.testClassForManifest(mJavaProject, + className, superClassName, testVisibility); + if (result != BaseProjectHelper.TEST_CLASS_OK) { + // get the line number + int line = mLocator.getLineNumber(); + + // mark the file + IMarker marker = BaseProjectHelper.addMarker(getFile(), + AndroidConstants.MARKER_ANDROID, + result, line, IMarker.SEVERITY_ERROR); + + // add custom attributes to be used by the manifest editor. + if (marker != null) { + try { + marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE, + AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY); + marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className); + } catch (CoreException e) { + } + } + } + + } + + /** + * Searches through the attributes list for a particular one and returns its value. + * @param attributes the attribute list to search through + * @param attributeName the name of the attribute to look for. + * @param hasNamespace Indicates whether the attribute has an android namespace. + * @return a String with the value or null if the attribute was not found. + * @see AndroidConstants#NS_RESOURCES + */ + private String getAttributeValue(Attributes attributes, String attributeName, + boolean hasNamespace) { + int count = attributes.getLength(); + for (int i = 0 ; i < count ; i++) { + if (attributeName.equals(attributes.getLocalName(i)) && + ((hasNamespace && + AndroidConstants.NS_RESOURCES.equals(attributes.getURI(i))) || + (hasNamespace == false && attributes.getURI(i).length() == 0))) { + return attributes.getValue(i); + } + } + + return null; + } + + private void addProcessName(String processName) { + if (mProcesses == null) { + mProcesses = new TreeSet(); + } + + mProcesses.add(processName); + } + } + + private static SAXParserFactory sParserFactory; + + private final String mJavaPackage; + private final String[] mActivities; + private final String mLauncherActivity; + private final String[] mProcesses; + private final Boolean mDebuggable; + private final int mApiLevelRequirement; + + static { + sParserFactory = SAXParserFactory.newInstance(); + sParserFactory.setNamespaceAware(true); + } + + /** + * Parses the Android Manifest, and returns an object containing + * the result of the parsing. + * @param javaProject The java project. + * @param manifestFile the {@link IFile} representing the manifest file. + * @param errorListener + * @param gatherData indicates whether the parsing will extract data from the manifest. + * @param markErrors indicates whether the error found during parsing should put a + * marker on the file. For class validation errors to put a marker, gatherData + * must be set to true + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + */ + public static AndroidManifestParser parse(IJavaProject javaProject, IFile manifestFile, + XmlErrorListener errorListener, boolean gatherData, boolean markErrors) + throws CoreException { + try { + SAXParser parser = sParserFactory.newSAXParser(); + + ManifestHandler manifestHandler = new ManifestHandler(manifestFile, + errorListener, gatherData, javaProject, markErrors); + + parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); + + // get the result from the handler + + return new AndroidManifestParser(manifestHandler.getPackage(), + manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), + manifestHandler.getProcesses(), manifestHandler.getDebuggable(), + manifestHandler.getApiLevelRequirement()); + } catch (ParserConfigurationException e) { + } catch (SAXException e) { + } catch (IOException e) { + } finally { + } + + return null; + } + + /** + * Parses the Android Manifest for the specified project, and returns an object containing + * the result of the parsing. + * @param javaProject The java project. Required if markErrors is true + * @param errorListener the {@link XmlErrorListener} object being notified of the presence + * of errors. Optional. + * @param gatherData indicates whether the parsing will extract data from the manifest. + * @param markErrors indicates whether the error found during parsing should put a + * marker on the file. For class validation errors to put a marker, gatherData + * must be set to true + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + */ + public static AndroidManifestParser parse(IJavaProject javaProject, + XmlErrorListener errorListener, boolean gatherData, boolean markErrors) + throws CoreException { + try { + SAXParser parser = sParserFactory.newSAXParser(); + + IFile manifestFile = AndroidManifestHelper.getManifest(javaProject.getProject()); + if (manifestFile != null) { + ManifestHandler manifestHandler = new ManifestHandler(manifestFile, + errorListener, gatherData, javaProject, markErrors); + + parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); + + // get the result from the handler + return new AndroidManifestParser(manifestHandler.getPackage(), + manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), + manifestHandler.getProcesses(), manifestHandler.getDebuggable(), + manifestHandler.getApiLevelRequirement()); + } + } catch (ParserConfigurationException e) { + } catch (SAXException e) { + } catch (IOException e) { + } finally { + } + + return null; + } + + /** + * Parses the manifest file, collects data, and checks for errors. + * @param javaProject The java project. Required. + * @param manifestFile + * @param errorListener the {@link XmlErrorListener} object being notified of the presence + * of errors. Optional. + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} + */ + public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile, + XmlErrorListener errorListener) throws CoreException { + return parse(javaProject, manifestFile, errorListener, true, true); + } + + /** + * Parses the manifest file, and collects data. + * @param manifestFile + * @param errorListener the {@link XmlErrorListener} object being notified of the presence + * of errors. Optional. + * @return an {@link AndroidManifestParser} or null if the parsing failed. + * @throws CoreException + * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} + */ + public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException { + return parse(null /* javaProject */, manifestFile, null /* errorListener */, + true /* gatherData */, false /* markErrors */); + } + + /** + * Returns the package defined in the manifest, if found. + * @return The package name or null if not found. + */ + public String getPackage() { + return mJavaPackage; + } + + /** + * Returns the list of activities found in the manifest. + * @return An array of fully qualified class names, or empty if no activity were found. + */ + public String[] getActivities() { + return mActivities; + } + + /** + * Returns the name of one activity found in the manifest, that is configured to show + * up in the HOME screen. + * @return the fully qualified name of a HOME activity or null if none were found. + */ + public String getLauncherActivity() { + return mLauncherActivity; + } + + /** + * Returns the list of process names declared by the manifest. + */ + public String[] getProcesses() { + return mProcesses; + } + + /** + * Returns the debuggable attribute value or null if it is not set. + */ + public Boolean getDebuggable() { + return mDebuggable; + } + + /** + * Returns the minSdkVersion attribute, or 0 if it's not set. + */ + public int getApiLevelRequirement() { + return mApiLevelRequirement; + } + + + /** + * Private constructor to enforce using + * {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)}, + * {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}, + * or {@link #parseForError(IJavaProject, IFile, XmlErrorListener)} to get an + * {@link AndroidManifestParser} object. + * @param javaPackage the package parsed from the manifest. + * @param activities the list of activities parsed from the manifest. + * @param launcherActivity the launcher activity parser from the manifest. + * @param processes the list of custom processes declared in the manifest. + * @param debuggable the debuggable attribute, or null if not set. + * @param apiLevelRequirement the minSdkVersion attribute value or 0 if not set. + */ + private AndroidManifestParser(String javaPackage, String[] activities, + String launcherActivity, String[] processes, Boolean debuggable, + int apiLevelRequirement) { + mJavaPackage = javaPackage; + mActivities = activities; + mLauncherActivity = launcherActivity; + mProcesses = processes; + mDebuggable = debuggable; + mApiLevelRequirement = apiLevelRequirement; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java new file mode 100644 index 0000000..530c89e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007 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.common.project; + +import com.android.ide.eclipse.common.AndroidConstants; + +import java.util.Iterator; + +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; + +/** + * XPath factory with automatic support for the android namespace. + */ +public class AndroidXPathFactory { + public final static String DEFAULT_NS_PREFIX = "android"; //$NON-NLS-1$ + + private final static XPathFactory sFactory = XPathFactory.newInstance(); + + /** Namespace context for Android resource XML files. */ + private static class AndroidNamespaceContext implements NamespaceContext { + private String mAndroidPrefix; + + /** + * Construct the context with the prefix associated with the android namespace. + * @param prefix the Prefix + */ + public AndroidNamespaceContext(String androidPrefix) { + mAndroidPrefix = androidPrefix; + } + + public String getNamespaceURI(String prefix) { + if (prefix != null) { + if (prefix.equals(mAndroidPrefix)) { + return AndroidConstants.NS_RESOURCES; + } + } + + return XMLConstants.NULL_NS_URI; + } + + public String getPrefix(String namespaceURI) { + // This isn't necessary for our use. + assert false; + return null; + } + + public Iterator getPrefixes(String namespaceURI) { + // This isn't necessary for our use. + assert false; + return null; + } + } + + /** + * Creates a new XPath object, specifying which prefix in the query is used for the + * android namespace. + * @param prefix The namespace prefix. + */ + public static XPath newXPath(String androidPrefix) { + XPath xpath = sFactory.newXPath(); + xpath.setNamespaceContext(new AndroidNamespaceContext(androidPrefix)); + return xpath; + } + + /** + * Creates a new XPath object using the default prefix for the android namespace. + * @see #DEFAULT_NS_PREFIX + */ + public static XPath newXPath() { + return newXPath(DEFAULT_NS_PREFIX); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java new file mode 100644 index 0000000..c69e875 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2007 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.common.project; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +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.ui.JavaUI; +import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.texteditor.IDocumentProvider; +import org.eclipse.ui.texteditor.ITextEditor; + +import java.util.ArrayList; + +/** + * Utility methods to manipulate projects. + */ +public final class BaseProjectHelper { + + public static final String TEST_CLASS_OK = null; + + /** + * returns a list of source classpath for a specified project + * @param javaProject + * @return a list of path relative to the workspace root. + */ + public static ArrayList getSourceClasspaths(IJavaProject javaProject) { + ArrayList sourceList = new ArrayList(); + IClasspathEntry[] classpaths = javaProject.readRawClasspath(); + if (classpaths != null) { + for (IClasspathEntry e : classpaths) { + if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + sourceList.add(e.getPath()); + } + } + } + return sourceList; + } + + /** + * Adds a marker to a file on a specific line + * @param file the file to be marked + * @param markerId The id of the marker to add. + * @param message the message associated with the mark + * @param lineNumber the line number where to put the mark + * @param severity the severity of the marker. + * @return the IMarker that was added or null if it failed to add one. + */ + public final static IMarker addMarker(IResource file, String markerId, + String message, int lineNumber, int severity) { + try { + IMarker marker = file.createMarker(markerId); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.SEVERITY, severity); + if (lineNumber == -1) { + lineNumber = 1; + } + marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); + + // on Windows, when adding a marker to a project, it takes a refresh for the marker + // to show. In order to fix this we're forcing a refresh of elements receiving + // markers (and only the element, not its children), to force the marker display. + file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + + return marker; + } catch (CoreException e) { + AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", + markerId, file.getFullPath()); + } + + return null; + } + + /** + * Adds a marker to a resource. + * @param resource the file to be marked + * @param markerId The id of the marker to add. + * @param message the message associated with the mark + * @param severity the severity of the marker. + * @return the IMarker that was added or null if it failed to add one. + */ + public final static IMarker addMarker(IResource resource, String markerId, + String message, int severity) { + try { + IMarker marker = resource.createMarker(markerId); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.SEVERITY, severity); + + // on Windows, when adding a marker to a project, it takes a refresh for the marker + // to show. In order to fix this we're forcing a refresh of elements receiving + // markers (and only the element, not its children), to force the marker display. + resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); + + return marker; + } catch (CoreException e) { + AdtPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", + markerId, resource.getFullPath()); + } + + return null; + } + + /** + * Tests that a class name is valid for usage in the manifest. + *

          + * This tests the class existence, that it can be instantiated (ie it must not be abstract, + * nor non static if enclosed), and that it extends the proper super class (not necessarily + * directly) + * @param javaProject the {@link IJavaProject} containing the class. + * @param className the fully qualified name of the class to test. + * @param superClassName the fully qualified name of the expected super class. + * @param testVisibility if true, the method will check the visibility of the class + * or of its constructors. + * @return {@link #TEST_CLASS_OK} or an error message. + */ + public final static String testClassForManifest(IJavaProject javaProject, String className, + String superClassName, boolean testVisibility) { + try { + // replace $ by . + String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$ + + // look for the IType object for this class + IType type = javaProject.findType(javaClassName); + if (type != null && type.exists()) { + // test that the class is not abstract + int flags = type.getFlags(); + if (Flags.isAbstract(flags)) { + return String.format("%1$s is abstract", className); + } + + // test whether the class is public or not. + if (testVisibility && Flags.isPublic(flags) == false) { + // if its not public, it may have a public default constructor, + // which would then be fine. + IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]); + if (basicConstructor != null && basicConstructor.exists()) { + int constructFlags = basicConstructor.getFlags(); + if (Flags.isPublic(constructFlags) == false) { + return String.format( + "%1$s or its default constructor must be public for the system to be able to instantiate it", + className); + } + } else { + return String.format( + "%1$s must be public, or the system will not be able to instantiate it.", + className); + } + } + + // If it's enclosed, test that it's static. If its declaring class is enclosed + // as well, test that it is also static, and public. + IType declaringType = type; + do { + IType tmpType = declaringType.getDeclaringType(); + if (tmpType != null) { + if (tmpType.exists()) { + flags = declaringType.getFlags(); + if (Flags.isStatic(flags) == false) { + return String.format("%1$s is enclosed, but not static", + declaringType.getFullyQualifiedName()); + } + + flags = tmpType.getFlags(); + if (testVisibility && Flags.isPublic(flags) == false) { + return String.format("%1$s is not public", + tmpType.getFullyQualifiedName()); + } + } else { + // if it doesn't exist, we need to exit so we may as well mark it null. + tmpType = null; + } + } + declaringType = tmpType; + } while (declaringType != null); + + // test the class inherit from the specified super class. + // get the type hierarchy + ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor()); + + // if the super class is not the reference class, it may inherit from + // it so we get its supertype. At some point it will be null and we + // will stop + IType superType = type; + boolean foundProperSuperClass = false; + while ((superType = hierarchy.getSuperclass(superType)) != null && + superType.exists()) { + if (superClassName.equals(superType.getFullyQualifiedName())) { + foundProperSuperClass = true; + } + } + + // didn't find the proper superclass? return false. + if (foundProperSuperClass == false) { + return String.format("%1$s does not extend %2$s", className, superClassName); + } + + return TEST_CLASS_OK; + } else { + return String.format("Class %1$s does not exist", className); + } + } catch (JavaModelException e) { + return String.format("%1$s: %2$s", className, e.getMessage()); + } + } + + /** + * Parses the manifest file for errors. + *

          + * This starts by removing the current XML marker, and then parses the xml for errors, both + * of XML type and of Android type (checking validity of class files). + * @param manifestFile + * @param errorListener + * @throws CoreException + */ + public static AndroidManifestParser parseManifestForError(IFile manifestFile, + XmlErrorListener errorListener) throws CoreException { + // remove previous markers + if (manifestFile.exists()) { + manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, IResource.DEPTH_ZERO); + manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); + } + + // and parse + return AndroidManifestParser.parseForError( + BaseProjectHelper.getJavaProject(manifestFile.getProject()), + manifestFile, errorListener); + } + + /** + * Returns the {@link IJavaProject} for a {@link IProject} object. + *

          + * This checks if the project has the Java Nature first. + * @param project + * @return the IJavaProject or null if the project couldn't be created or if the project + * does not have the Java Nature. + * @throws CoreException + */ + public static IJavaProject getJavaProject(IProject project) throws CoreException { + if (project != null && project.hasNature(JavaCore.NATURE_ID)) { + return JavaCore.create(project); + } + return null; + } + + /** + * Reveals a specific line in the source file defining a specified class, + * for a specific project. + * @param project + * @param className + * @param line + */ + public static void revealSource(IProject project, String className, int line) { + // in case the type is enclosed, we need to replace the $ with . + className = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$ + + // get the java project + IJavaProject javaProject = JavaCore.create(project); + + try { + // look for the IType matching the class name. + IType result = javaProject.findType(className); + if (result != null && result.exists()) { + // before we show the type in an editor window, we make sure the current + // workbench page has an editor area (typically the ddms perspective doesn't). + IWorkbench workbench = PlatformUI.getWorkbench(); + IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); + IWorkbenchPage page = window.getActivePage(); + if (page.isEditorAreaVisible() == false) { + // no editor area? we open the java perspective. + new OpenJavaPerspectiveAction().run(); + } + + IEditorPart editor = JavaUI.openInEditor(result); + if (editor instanceof ITextEditor) { + // get the text editor that was just opened. + ITextEditor textEditor = (ITextEditor)editor; + + IEditorInput input = textEditor.getEditorInput(); + + // get the location of the line to show. + IDocumentProvider documentProvider = textEditor.getDocumentProvider(); + IDocument document = documentProvider.getDocument(input); + IRegion lineInfo = document.getLineInformation(line - 1); + + // select and reveal the line. + textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength()); + } + } + } catch (JavaModelException e) { + } catch (PartInitException e) { + } catch (BadLocationException e) { + } + } + + /** + * Returns the list of android-flagged projects. This list contains projects that are opened + * in the workspace and that are flagged as android project (through the android nature) + * @return an array of IJavaProject, which can be empty if no projects match. + */ + public static IJavaProject[] getAndroidProjects() { + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IJavaModel javaModel = JavaCore.create(workspaceRoot); + + return getAndroidProjects(javaModel); + } + + /** + * Returns the list of android-flagged projects for the specified java Model. + * This list contains projects that are opened in the workspace and that are flagged as android + * project (through the android nature) + * @param javaModel the Java Model object corresponding for the current workspace root. + * @return an array of IJavaProject, which can be empty if no projects match. + */ + public static IJavaProject[] getAndroidProjects(IJavaModel javaModel) { + // get the java projects + IJavaProject[] javaProjectList = null; + try { + javaProjectList = javaModel.getJavaProjects(); + } + catch (JavaModelException jme) { + return new IJavaProject[0]; + } + + // temp list to build the android project array + ArrayList androidProjectList = new ArrayList(); + + // loop through the projects and add the android flagged projects to the temp list. + for (IJavaProject javaProject : javaProjectList) { + // get the workspace project object + IProject project = javaProject.getProject(); + + // check if it's an android project based on its nature + try { + if (project.hasNature(AndroidConstants.NATURE)) { + androidProjectList.add(javaProject); + } + } catch (CoreException e) { + // this exception, thrown by IProject.hasNature(), means the project either doesn't + // exist or isn't opened. So, in any case we just skip it (the exception will + // bypass the ArrayList.add() + } + } + + // return the android projects list. + return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]); + } + + /** + * Returns the {@link IFolder} representing the output for the project. + *

          + * The project must be a java project and be opened, or the method will return null. + * @param project the {@link IProject} + * @return an IFolder item or null. + */ + public final static IFolder getOutputFolder(IProject project) { + try { + if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) { + // get a java project from the normal project object + IJavaProject javaProject = JavaCore.create(project); + + IPath path = javaProject.getOutputLocation(); + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + IResource outputResource = wsRoot.findMember(path); + if (outputResource != null && outputResource.getType() == IResource.FOLDER) { + return (IFolder)outputResource; + } + } + } catch (JavaModelException e) { + // Let's do nothing and return null + } catch (CoreException e) { + // Let's do nothing and return null + } + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ExportHelper.java new file mode 100644 index 0000000..4b169a1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ExportHelper.java @@ -0,0 +1,188 @@ +/* + * 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.common.project; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Shell; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.JarEntry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +/** + * Export helper for project. + */ +public final class ExportHelper { + + private static IExportCallback sCallback; + + public interface IExportCallback { + void startExportWizard(IProject project); + } + + public static void setCallback(IExportCallback callback) { + sCallback = callback; + } + + public static void startExportWizard(IProject project) { + if (sCallback != null) { + sCallback.startExportWizard(project); + } + } + + /** + * Exports an unsigned version of the application created by the given project. + * @param project the project to export + */ + public static void exportProject(IProject project) { + Shell shell = Display.getCurrent().getActiveShell(); + + // get the java project to get the output directory + IFolder outputFolder = BaseProjectHelper.getOutputFolder(project); + if (outputFolder != null) { + IPath binLocation = outputFolder.getLocation(); + + // make the full path to the package + String fileName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; + + File file = new File(binLocation.toOSString() + File.separator + fileName); + + if (file.exists() == false || file.isFile() == false) { + MessageDialog.openInformation(Display.getCurrent().getActiveShell(), + "Android IDE Plug-in", + String.format("Failed to export %1$s: %2$s doesn't exist!", + project.getName(), file.getPath())); + return; + } + + // ok now pop up the file save window + FileDialog fileDialog = new FileDialog(shell, SWT.SAVE); + + fileDialog.setText("Export Project"); + fileDialog.setFileName(fileName); + + String saveLocation = fileDialog.open(); + if (saveLocation != null) { + // get the stream from the original file + + ZipInputStream zis = null; + ZipOutputStream zos = null; + FileInputStream input = null; + FileOutputStream output = null; + + try { + input = new FileInputStream(file); + zis = new ZipInputStream(input); + + // get an output stream into the new file + File saveFile = new File(saveLocation); + output = new FileOutputStream(saveFile); + zos = new ZipOutputStream(output); + } catch (FileNotFoundException e) { + // only the input/output stream are throwing this exception. + // so we only have to close zis if output is the one that threw. + if (zis != null) { + try { + zis.close(); + } catch (IOException e1) { + // pass + } + } + + MessageDialog.openInformation(shell, "Android IDE Plug-in", + String.format("Failed to export %1$s: %2$s doesn't exist!", + project.getName(), file.getPath())); + return; + } + + try { + ZipEntry entry; + + byte[] buffer = new byte[4096]; + + while ((entry = zis.getNextEntry()) != null) { + String name = entry.getName(); + + // do not take directories or anything inside the META-INF folder since + // we want to strip the signature. + if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$ + continue; + } + + ZipEntry newEntry; + + // Preserve the STORED method of the input entry. + if (entry.getMethod() == JarEntry.STORED) { + newEntry = new JarEntry(entry); + } else { + // Create a new entry so that the compressed len is recomputed. + newEntry = new JarEntry(name); + } + + // add the entry to the jar archive + zos.putNextEntry(newEntry); + + // read the content of the entry from the input stream, and write it into the archive. + int count; + while ((count = zis.read(buffer)) != -1) { + zos.write(buffer, 0, count); + } + + // close the entry for this file + zos.closeEntry(); + zis.closeEntry(); + + } + + } catch (IOException e) { + MessageDialog.openInformation(shell, "Android IDE Plug-in", + String.format("Failed to export %1$s: %2$s", + project.getName(), e.getMessage())); + } finally { + try { + zos.close(); + } catch (IOException e) { + // pass + } + try { + zis.close(); + } catch (IOException e) { + // pass + } + } + } + } else { + MessageDialog.openInformation(shell, "Android IDE Plug-in", + String.format("Failed to export %1$s: Could not get project output location", + project.getName())); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java new file mode 100644 index 0000000..0c43499 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java @@ -0,0 +1,110 @@ +/* + * 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.common.project; + +import com.android.ide.eclipse.common.project.BaseProjectHelper; + +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.ui.JavaElementLabelProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; + +/** + * Helper class to deal with displaying a project choosing dialog that lists only the + * projects with the Android nature. + */ +public class ProjectChooserHelper { + + private final Shell mParentShell; + + /** + * List of current android projects. Since the dialog is modal, we'll just get + * the list once on-demand. + */ + private IJavaProject[] mAndroidProjects; + + public ProjectChooserHelper(Shell parentShell) { + mParentShell = parentShell; + } + /** + * Displays a project chooser dialog which lists all available projects with the Android nature. + *

          + * The list of project is built from Android flagged projects currently opened in the workspace. + * + * @param projectName If non null and not empty, represents the name of an Android project + * that will be selected by default. + * @return the project chosen by the user in the dialog, or null if the dialog was canceled. + */ + public IJavaProject chooseJavaProject(String projectName) { + ILabelProvider labelProvider = new JavaElementLabelProvider( + JavaElementLabelProvider.SHOW_DEFAULT); + ElementListSelectionDialog dialog = new ElementListSelectionDialog( + mParentShell, labelProvider); + dialog.setTitle("Project Selection"); + dialog.setMessage("Select a project to constrain your search."); + + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IJavaModel javaModel = JavaCore.create(workspaceRoot); + + // set the elements in the dialog. These are opened android projects. + dialog.setElements(getAndroidProjects(javaModel)); + + // look for the project matching the given project name + IJavaProject javaProject = null; + if (projectName != null && projectName.length() > 0) { + javaProject = javaModel.getJavaProject(projectName); + } + + // if we found it, we set the initial selection in the dialog to this one. + if (javaProject != null) { + dialog.setInitialSelections(new Object[] { javaProject }); + } + + // open the dialog and return the object selected if OK was clicked, or null otherwise + if (dialog.open() == Window.OK) { + return (IJavaProject)dialog.getFirstResult(); + } + return null; + } + + /** + * Returns the list of Android projects. + *

          + * Because this list can be time consuming, this class caches the list of project. + * It is recommended to call this method instead of + * {@link BaseProjectHelper#getAndroidProjects()}. + * + * @param javaModel the java model. Can be null. + */ + public IJavaProject[] getAndroidProjects(IJavaModel javaModel) { + if (mAndroidProjects == null) { + if (javaModel == null) { + mAndroidProjects = BaseProjectHelper.getAndroidProjects(); + } else { + mAndroidProjects = BaseProjectHelper.getAndroidProjects(javaModel); + } + } + + return mAndroidProjects; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java new file mode 100644 index 0000000..26fbf42 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2007 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.common.project; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * XML error handler used by the parser to report errors/warnings. + */ +public class XmlErrorHandler extends DefaultHandler { + + /** file being parsed */ + private IFile mFile; + + /** link to the delta visitor, to set the xml error flag */ + private XmlErrorListener mErrorListener; + + /** + * Classes which implement this interface provide a method that deals + * with XML errors. + */ + public interface XmlErrorListener { + /** + * Sent when an XML error is detected. + */ + public void errorFound(); + } + + public static class BasicXmlErrorListener implements XmlErrorListener { + public boolean mHasXmlError = false; + + public void errorFound() { + mHasXmlError = true; + } + } + + public XmlErrorHandler(IFile file, XmlErrorListener errorListener) { + mFile = file; + mErrorListener = errorListener; + } + + /** + * Xml Error call back + * @param exception the parsing exception + */ + @Override + public void error(SAXParseException exception) throws SAXException { + handleError(exception, exception.getLineNumber()); + } + + /** + * Xml Fatal Error call back + * @param exception the parsing exception + */ + @Override + public void fatalError(SAXParseException exception) throws SAXException { + handleError(exception, exception.getLineNumber()); + } + + /** + * Xml Warning call back + * @param exception the parsing exception + */ + @Override + public void warning(SAXParseException exception) throws SAXException { + BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), + exception.getLineNumber(), IMarker.SEVERITY_WARNING); + } + + protected final IFile getFile() { + return mFile; + } + + /** + * Handles a parsing error and an optional line number. + * @param exception + * @param lineNumber + */ + protected void handleError(Exception exception, int lineNumber) { + if (mErrorListener != null) { + mErrorListener.errorFound(); + } + + if (lineNumber != -1) { + BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), + lineNumber, IMarker.SEVERITY_ERROR); + } else { + BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), + IMarker.SEVERITY_ERROR); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java new file mode 100644 index 0000000..3176c8e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java @@ -0,0 +1,461 @@ +/* + * 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.common.resources; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format; +import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; + +import org.eclipse.core.runtime.IStatus; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** + * Parser for attributes description files. + */ +public final class AttrsXmlParser { + + private Document mDocument; + private String mOsAttrsXmlPath; + // all attributes that have the same name are supposed to have the same + // parameters so we'll keep a cache of them to avoid processing them twice. + private HashMap mAttributeMap; + + /** Map of all attribute names for a given element */ + private HashMap mStyleMap; + + /** Map of all (constant, value) pairs for attributes of format enum or flag. + * E.g. for attribute name=gravity, this tells us there's an enum/flag called "center" + * with value 0x11. + */ + private Map> mEnumFlagValues; + + + /** + * Creates a new {@link AttrsXmlParser}, set to load things from the given + * XML file. Nothing has been parsed yet. Callers should call {@link #preload()} + * next. + */ + public AttrsXmlParser(String osAttrsXmlPath) { + this(osAttrsXmlPath, null /* inheritableAttributes */); + } + + /** + * Creates a new {@link AttrsXmlParser} set to load things from the given + * XML file. If inheritableAttributes is non-null, it must point to a preloaded + * {@link AttrsXmlParser} which attributes will be used for this one. Since + * already defined attributes are not modifiable, they are thus "inherited". + */ + public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes) { + mOsAttrsXmlPath = osAttrsXmlPath; + + // styles are not inheritable. + mStyleMap = new HashMap(); + + if (inheritableAttributes == null) { + mAttributeMap = new HashMap(); + mEnumFlagValues = new HashMap>(); + } else { + mAttributeMap = new HashMap(inheritableAttributes.mAttributeMap); + mEnumFlagValues = new HashMap>( + inheritableAttributes.mEnumFlagValues); + } + } + + /** + * @return The OS path of the attrs.xml file parsed + */ + public String getOsAttrsXmlPath() { + return mOsAttrsXmlPath; + } + + /** + * Preloads the document, parsing all attributes and declared styles. + * + * @return Self, for command chaining. + */ + public AttrsXmlParser preload() { + Document doc = getDocument(); + + if (doc == null) { + AdtPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$ + mOsAttrsXmlPath); + return this; + } + + Node res = doc.getFirstChild(); + while (res != null && + res.getNodeType() != Node.ELEMENT_NODE && + !res.getNodeName().equals("resources")) { //$NON-NLS-1$ + res = res.getNextSibling(); + } + + if (res == null) { + AdtPlugin.log(IStatus.WARNING, "Failed to find a node in %1$s", //$NON-NLS-1$ + mOsAttrsXmlPath); + return this; + } + + parseResources(res); + return this; + } + + /** + * Loads all attributes & javadoc for the view class info based on the class name. + */ + public void loadViewAttributes(ViewClassInfo info) { + if (getDocument() != null) { + String xmlName = info.getShortClassName(); + DeclareStyleableInfo style = mStyleMap.get(xmlName); + if (style != null) { + info.setAttributes(style.getAttributes()); + info.setJavaDoc(style.getJavaDoc()); + } + } + } + + /** + * Loads all attributes for the layout data info based on the class name. + */ + public void loadLayoutParamsAttributes(LayoutParamsInfo info) { + if (getDocument() != null) { + // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout". + String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$ + info.getViewLayoutClass().getShortClassName(), + info.getShortClassName()); + xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + DeclareStyleableInfo style = mStyleMap.get(xmlName); + if (style != null) { + info.setAttributes(style.getAttributes()); + } + } + } + + /** + * Returns a list of all decleare-styleable found in the xml file. + */ + public Map getDeclareStyleableList() { + return Collections.unmodifiableMap(mStyleMap); + } + + /** + * Returns a map of all enum and flag constants sorted by parent attribute name. + * The map is attribute_name => (constant_name => integer_value). + */ + public Map> getEnumFlagValues() { + return mEnumFlagValues; + } + + //------------------------- + + /** + * Creates an XML document from the attrs.xml OS path. + * May return null if the file doesn't exist or cannot be parsed. + */ + private Document getDocument() { + if (mDocument == null) { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setIgnoringComments(false); + try { + DocumentBuilder builder = factory.newDocumentBuilder(); + mDocument = builder.parse(new File(mOsAttrsXmlPath)); + } catch (ParserConfigurationException e) { + AdtPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$ + mOsAttrsXmlPath); + } catch (SAXException e) { + AdtPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$ + mOsAttrsXmlPath); + } catch (IOException e) { + AdtPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$ + mOsAttrsXmlPath); + } + } + return mDocument; + } + + /** + * Finds all the and nodes in the top node. + */ + private void parseResources(Node res) { + Node lastComment = null; + for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) { + switch (node.getNodeType()) { + case Node.COMMENT_NODE: + lastComment = node; + break; + case Node.ELEMENT_NODE: + if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$ + Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$ + if (nameNode != null) { + String name = nameNode.getNodeValue(); + + Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$ + String parents = parentNode == null ? null : parentNode.getNodeValue(); + + if (name != null && !mStyleMap.containsKey(name)) { + DeclareStyleableInfo style = parseDeclaredStyleable(name, node); + if (parents != null) { + style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$ + } + mStyleMap.put(name, style); + if (lastComment != null) { + style.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); + } + } + } + } else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ + parseAttr(node, lastComment); + } + lastComment = null; + break; + } + } + } + + /** + * Parses an node and convert it into an {@link AttributeInfo} if it is valid. + */ + private AttributeInfo parseAttr(Node attrNode, Node lastComment) { + AttributeInfo info = null; + Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$ + if (nameNode != null) { + String name = nameNode.getNodeValue(); + if (name != null) { + info = mAttributeMap.get(name); + // If the attribute is unknown yet, parse it. + // If the attribute is know but its format is unknown, parse it too. + if (info == null || info.getFormats().length == 0) { + info = parseAttributeTypes(attrNode, name); + if (info != null) { + mAttributeMap.put(name, info); + } + } else if (lastComment != null) { + info = new AttributeInfo(info); + } + if (info != null) { + if (lastComment != null) { + info.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); + } + } + } + } + return info; + } + + /** + * Finds all the attributes for a particular style node, + * e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout". + * + * @param styleName The name of the declare-styleable node + * @param declareStyleableNode The declare-styleable node itself + */ + private DeclareStyleableInfo parseDeclaredStyleable(String styleName, + Node declareStyleableNode) { + ArrayList attrs = new ArrayList(); + Node lastComment = null; + for (Node node = declareStyleableNode.getFirstChild(); + node != null; + node = node.getNextSibling()) { + + switch (node.getNodeType()) { + case Node.COMMENT_NODE: + lastComment = node; + break; + case Node.ELEMENT_NODE: + if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ + AttributeInfo info = parseAttr(node, lastComment); + if (info != null) { + attrs.add(info); + } + } + lastComment = null; + break; + } + + } + + return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()])); + } + + /** + * Returns the {@link AttributeInfo} for a specific XML node. + * This gets the javadoc, the type, the name and the enum/flag values if any. + *

          + * The XML node is expected to have the following attributes: + *

            + *
          • "name", which is mandatory. The node is skipped if this is missing.
          • + *
          • "format".
          • + *
          + * The format may be one type or two types (e.g. "reference|color"). + * An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute, + * they are implicitely stated by the presence of sub-nodes or . + *

          + * By design, nodes of the same name MUST have the same type. + * Attribute nodes are thus cached by name and reused as much as possible. + * When reusing a node, it is duplicated and its javadoc reassigned. + */ + private AttributeInfo parseAttributeTypes(Node attrNode, String name) { + TreeSet formats = new TreeSet(); + String[] enumValues = null; + String[] flagValues = null; + + Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$ + if (attrFormat != null) { + for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$ + try { + Format format = AttributeInfo.Format.valueOf(f.toUpperCase()); + // enum and flags are handled differently right below + if (format != null && + format != AttributeInfo.Format.ENUM && + format != AttributeInfo.Format.FLAG) { + formats.add(format); + } + } catch (IllegalArgumentException e) { + AdtPlugin.log(e, "Unknown format name '%s' in , file '%s'.", //$NON-NLS-1$ + f, name, getOsAttrsXmlPath()); + } + } + } + + // does this have children? + enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$ + if (enumValues != null) { + formats.add(AttributeInfo.Format.ENUM); + } + + // does this have children? + flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$ + if (flagValues != null) { + formats.add(AttributeInfo.Format.FLAG); + } + + AttributeInfo info = new AttributeInfo(name, + formats.toArray(new AttributeInfo.Format[formats.size()])); + info.setEnumValues(enumValues); + info.setFlagValues(flagValues); + return info; + } + + /** + * Given an XML node that represents an node, this method searches + * if the node has any children nodes named "target" (e.g. "enum" or "flag"). + * Such nodes must have a "name" attribute. + *

          + * If "attrNode" is null, look for any that has the given attrNode + * and the requested children nodes. + *

          + * This method collects all the possible names of these children nodes and + * return them. + * + * @param attrNode The XML node + * @param filter The child node to look for, either "enum" or "flag". + * @param attrName The value of the name attribute of + * + * @return Null if there are no such children nodes, otherwise an array of length >= 1 + * of all the names of these children nodes. + */ + private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) { + ArrayList names = null; + for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) { + Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$ + if (nameNode == null) { + AdtPlugin.log(IStatus.WARNING, + "Missing name attribute in <%s>", //$NON-NLS-1$ + attrName, filter); + } else { + if (names == null) { + names = new ArrayList(); + } + String name = nameNode.getNodeValue(); + names.add(name); + + Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$ + if (valueNode == null) { + AdtPlugin.log(IStatus.WARNING, + "Missing value attribute in <%s name=\"%s\">", //$NON-NLS-1$ + attrName, filter, name); + } else { + String value = valueNode.getNodeValue(); + try { + int i = value.startsWith("0x") ? + Integer.parseInt(value.substring(2), 16 /* radix */) : + Integer.parseInt(value); + + Map map = mEnumFlagValues.get(attrName); + if (map == null) { + map = new HashMap(); + mEnumFlagValues.put(attrName, map); + } + map.put(name, Integer.valueOf(i)); + + } catch(NumberFormatException e) { + AdtPlugin.log(e, + "Value in <%s name=\"%s\" value=\"%s\"> is not a valid decimal or hexadecimal", //$NON-NLS-1$ + attrName, filter, name, value); + } + } + } + } + } + return names == null ? null : names.toArray(new String[names.size()]); + } + + /** + * Formats the javadoc. + * Only keeps the first sentence. + * Removes and simplifies links and references. + */ + private String formatJavadoc(String comment) { + if (comment == null) { + return null; + } + // sanitize & collapse whitespace + comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ + // take everything up to the first dot that is followed by a space or the end of the line. + // I love regexps :-). For the curious, the regexp is: + // - start of line + // - ignore whitespace + // - group: + // - everything, not greedy + // - non-capturing group (?: ) + // - end of string + // or + // - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" ) + // ( { + + private final String mName; + + /** + * Constructs a new ResourceItem + * @param name the name of the resource as it appears in the XML and R.java files. + */ + public ResourceItem(String name) { + mName = name; + } + + /** + * Returns the name of the resource item. + */ + public final String getName() { + return mName; + } + + /** + * Compares the {@link ResourceItem} to another. + * @param other the ResourceItem to be compared to. + */ + public int compareTo(ResourceItem other) { + return mName.compareTo(other.mName); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ResourceType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ResourceType.java new file mode 100644 index 0000000..60c471e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ResourceType.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2007 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.common.resources; + +/** + * Enum representing a type of compiled resource. + */ +public enum ResourceType { + ANIM("anim", "Animation"), //$NON-NLS-1$ + ARRAY("array", "Array", "string-array", "integer-array"), //$NON-NLS-1$ //$NON-NLS-3$ //$NON-NLS-4$ + ATTR("attr", "Attr"), //$NON-NLS-1$ + COLOR("color", "Color"), //$NON-NLS-1$ + DIMEN("dimen", "Dimension"), //$NON-NLS-1$ + DRAWABLE("drawable", "Drawable"), //$NON-NLS-1$ + ID("id", "ID"), //$NON-NLS-1$ + LAYOUT("layout", "Layout"), //$NON-NLS-1$ + MENU("menu", "Menu"), //$NON-NLS-1$ + RAW("raw", "Raw"), //$NON-NLS-1$ + STRING("string", "String"), //$NON-NLS-1$ + STYLE("style", "Style"), //$NON-NLS-1$ + STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$ + XML("xml", "XML"); //$NON-NLS-1$ + + private final String mName; + private final String mDisplayName; + private final String[] mAlternateXmlNames; + + ResourceType(String name, String displayName, String... alternateXmlNames) { + mName = name; + mDisplayName = displayName; + mAlternateXmlNames = alternateXmlNames; + } + + /** + * Returns the resource type name, as used by XML files. + */ + public String getName() { + return mName; + } + + /** + * Returns a translated display name for the resource type. + */ + public String getDisplayName() { + return mDisplayName; + } + + /** + * Returns the enum by its name as it appears in the XML or the R class. + * @param name name of the resource + * @return the matching {@link ResourceType} or null if no match was found. + */ + public static ResourceType getEnum(String name) { + for (ResourceType rType : values()) { + if (rType.mName.equals(name)) { + return rType; + } else if (rType.mAlternateXmlNames != null) { + // if there are alternate Xml Names, we test those too + for (String alternate : rType.mAlternateXmlNames) { + if (alternate.equals(name)) { + return rType; + } + } + } + } + return null; + } + + /** + * Returns a formatted string usable in an XML to use the specified {@link ResourceItem}. + * @param resourceItem The resource item. + * @param system Whether this is a system resource or a project resource. + * @return a string in the format @[type]/[name] + */ + public String getXmlString(ResourceItem resourceItem, boolean system) { + if (this == ID && resourceItem instanceof IIdResourceItem) { + IIdResourceItem idResource = (IIdResourceItem)resourceItem; + if (idResource.isDeclaredInline()) { + return (system?"@android:":"@+") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + + return (system?"@android:":"@") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + /** + * Returns an array with all the names defined by this enum. + */ + public static String[] getNames() { + ResourceType[] values = values(); + String[] names = new String[values.length]; + for (int i = values.length - 1; i >= 0; --i) { + names[i] = values[i].getName(); + } + return names; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java new file mode 100644 index 0000000..619e3cc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java @@ -0,0 +1,159 @@ +/* + * 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.common.resources; + +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; + +/** + * Information needed to represent a View or ViewGroup (aka Layout) item + * in the layout hierarchy, as extracted from the main android.jar and the + * associated attrs.xml. + */ +public class ViewClassInfo { + /** Is this a layout class (i.e. ViewGroup) or just a view? */ + private boolean mIsLayout; + /** FQCN e.g. android.view.View, never null. */ + private String mCanonicalClassName; + /** Short class name, e.g. View, never null. */ + private String mShortClassName; + /** Super class. Can be null. */ + private ViewClassInfo mSuperClass; + /** Short javadoc. Can be null. */ + private String mJavaDoc; + /** Attributes for this view or view group. Can be empty but never null. */ + private AttributeInfo[] mAttributes; + + public static class LayoutParamsInfo { + /** Short class name, e.g. LayoutData, never null. */ + private String mShortClassName; + /** ViewLayout class info owning this layout data */ + private ViewClassInfo mViewLayoutClass; + /** Super class. Can be null. */ + private LayoutParamsInfo mSuperClass; + /** Layout Data Attributes for layout classes. Can be empty but not null. */ + private AttributeInfo[] mAttributes; + + public LayoutParamsInfo(ViewClassInfo enclosingViewClassInfo, + String shortClassName, LayoutParamsInfo superClassInfo) { + mShortClassName = shortClassName; + mViewLayoutClass = enclosingViewClassInfo; + mSuperClass = superClassInfo; + mAttributes = new AttributeInfo[0]; + } + + /** Returns short class name, e.g. "LayoutData" */ + public String getShortClassName() { + return mShortClassName; + } + /** Returns the ViewLayout class info enclosing this layout data. Cannot null. */ + public ViewClassInfo getViewLayoutClass() { + return mViewLayoutClass; + } + /** Returns the super class info. Can be null. */ + public LayoutParamsInfo getSuperClass() { + return mSuperClass; + } + /** Returns the LayoutData attributes. Can be empty but not null. */ + public AttributeInfo[] getAttributes() { + return mAttributes; + } + /** Sets the LayoutData attributes. Can be empty but not null. */ + public void setAttributes(AttributeInfo[] attributes) { + mAttributes = attributes; + } + } + + /** Layout data info for a layout class. Null for all non-layout classes and always + * non-null for a layout class. */ + public LayoutParamsInfo mLayoutData; + + // -------- + + public ViewClassInfo(boolean isLayout, String canonicalClassName, String shortClassName) { + mIsLayout = isLayout; + mCanonicalClassName = canonicalClassName; + mShortClassName = shortClassName; + mAttributes = new AttributeInfo[0]; + } + + /** Returns whether this is a layout class (i.e. ViewGroup) or just a View */ + public boolean isLayout() { + return mIsLayout; + } + + /** Returns FQCN e.g. "android.view.View" */ + public String getCanonicalClassName() { + return mCanonicalClassName; + } + + /** Returns short class name, e.g. "View" */ + public String getShortClassName() { + return mShortClassName; + } + + /** Returns the super class. Can be null. */ + public ViewClassInfo getSuperClass() { + return mSuperClass; + } + + /** Returns a short javadoc */ + public String getJavaDoc() { + return mJavaDoc; + } + + /** Returns the attributes for this view or view group. Maybe empty but not null. */ + public AttributeInfo[] getAttributes() { + return mAttributes; + } + + /** Returns the LayoutData info for layout classes. Null for non-layout view classes. */ + public LayoutParamsInfo getLayoutData() { + return mLayoutData; + } + + /** + * Sets a link on the info of the super class of this View or ViewGroup. + *

          + * The super class info must be of the same kind (i.e. group to group or view to view) + * except for the top ViewGroup which links to the View info. + *

          + * The super class cannot be null except for the top View info. + */ + public void setSuperClass(ViewClassInfo superClass) { + mSuperClass = superClass; + } + + /** Sets the javadoc for this View or ViewGroup. */ + public void setJavaDoc(String javaDoc) { + mJavaDoc = javaDoc; + } + + /** Sets the list of attributes for this View or ViewGroup. */ + public void setAttributes(AttributeInfo[] attributes) { + mAttributes = attributes; + } + + /** + * Sets the {@link LayoutParamsInfo} for layout classes. + * Does nothing for non-layout view classes. + */ + public void setLayoutParams(LayoutParamsInfo layoutData) { + if (mIsLayout) { + mLayoutData = layoutData; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java new file mode 100644 index 0000000..d1b4547 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java @@ -0,0 +1,767 @@ +/* + * Copyright (C) 2007 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.editors; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; + +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.TextSelection; +import org.eclipse.jface.text.contentassist.CompletionProposal; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.contentassist.IContextInformationValidator; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +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.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.regex.Pattern; + +/** + * Content Assist Processor for Android XML files + */ +public abstract class AndroidContentAssist implements IContentAssistProcessor { + + /** Regexp to detect a full attribute after an element tag. + *

          Syntax:
          +     *    name = "..." quoted string with all but < and "
          +     * or:
          +     *    name = '...' quoted string with all but < and '
          +     * 
          + */ + private static Pattern sFirstAttribute = Pattern.compile( + "^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$ + + /** Regexp to detect an element tag name */ + private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$ + + /** Regexp to detect whitespace */ + private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ + + protected final static String ROOT_ELEMENT = ""; + + /** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which + * is used to list all the possible roots given by actual implementations. + * DO NOT USE DIRECTLY. Call {@link #getRootDescriptor()} instead. */ + private ElementDescriptor mRootDescriptor; + + private final int mDescriptorId; + + private AndroidEditor mEditor; + + /** + * Constructor for AndroidContentAssist + * @param rootElementDescriptors The valid root elements of the XML hierarchy + */ + public AndroidContentAssist(int descriptorId) { + mDescriptorId = descriptorId; + } + + /** + * Returns a list of completion proposals based on the + * specified location within the document that corresponds + * to the current cursor position within the text viewer. + * + * @param viewer the viewer whose document is used to compute the proposals + * @param offset an offset within the document for which completions should be computed + * @return an array of completion proposals or null if no proposals are possible + * + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) + */ + public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { + + if (mEditor == null) { + mEditor = getAndroidEditor(viewer); + } + + UiElementNode rootUiNode = mEditor.getUiRootNode(); + + Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor + or String or null */ + String parent = ""; //$NON-NLS-1$ + String wordPrefix = extractElementPrefix(viewer, offset); + char needTag = 0; + boolean isElement = false; + boolean isAttribute = false; + + Node currentNode = getNode(viewer, offset); + if (currentNode == null) + return null; + + // check to see if we can find a UiElementNode matching this XML node + UiElementNode currentUiNode = + rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode); + + if (currentNode.getNodeType() == Node.ELEMENT_NODE) { + parent = currentNode.getNodeName(); + + if (wordPrefix.equals(parent)) { + // We are still editing the element's tag name, not the attributes + // (the element's tag name may not even be complete) + isElement = true; + choices = getChoicesForElement(parent, currentNode); + } else { + // We're not editing the current node name, so we might be editing its + // attributes instead... + isAttribute = true; + AttribInfo info = parseAttributeInfo(viewer, offset); + if (info != null) { + // We're editing attributes in an element node (either the attributes' names + // or their values). + choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info); + + if (info.correctedPrefix != null) { + wordPrefix = info.correctedPrefix; + } + needTag = info.needTag; + } + } + } else if (currentNode.getNodeType() == Node.TEXT_NODE) { + isElement = true; + // Examine the parent of the text node. + choices = getChoicesForTextNode(currentNode); + } + + // Abort if we can't recognize the context or there are no completion choices + if (choices == null || choices.length == 0) return null; + + if (isElement) { + // If we found some suggestions, do we need to add an opening "<" bracket + // for the element? We don't if the cursor is right after "<" or " visited = new HashSet(); + + String prefix = null; + for (; prefix == null && + node != null && + node.getNodeType() == Node.ELEMENT_NODE; + node = node.getParentNode()) { + NamedNodeMap attrs = node.getAttributes(); + for (int n = attrs.getLength() - 1; n >= 0; --n) { + Node attr = attrs.item(n); + if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$ + String uri = attr.getNodeValue(); + if (AndroidConstants.NS_RESOURCES.equals(uri)) { + return attr.getLocalName(); + } + visited.add(uri); + } + } + } + + // Use a sensible default prefix if we can't find one. + // We need to make sure the prefix is not one that was declared in the scope + // visited above. + prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$ + String base = prefix; + for (int i = 1; visited.contains(prefix); i++) { + prefix = base + Integer.toString(i); + } + return prefix; + } + + /** + * Gets the choices when the user is editing the name of an XML element. + *

          + * The user is editing the name of an element (the "parent"). + * Find the grand-parent and if one is found, return its children element list. + * The name which is being edited should be one of those. + *

          + * Example: returns the list of all elements that + * can be found under , of which is one of the choices. + * + * @return an ElementDescriptor[] or null if no valid element was found. + */ + private Object[] getChoicesForElement(String parent, Node current_node) { + ElementDescriptor grandparent = null; + if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) { + grandparent = getDescriptor(current_node.getParentNode().getNodeName()); + } else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) { + grandparent = getRootDescriptor(); + } + if (grandparent != null) { + for (ElementDescriptor e : grandparent.getChildren()) { + if (e.getXmlName().startsWith(parent)) { + return grandparent.getChildren(); + } + } + } + + return null; + } + + /** + * Gets the choices when the user is editing an XML attribute. + *

          + * In input, attrInfo contains details on the analyzed context, namely whether the + * user is editing an attribute value (isInValue) or an attribute name. + *

          + * In output, attrInfo also contains two possible new values (this is a hack to circumvent + * the lack of out-parameters in Java): + * - AttribInfo.correctedPrefix if the user has been editing an attribute value and it has + * been detected that what the user typed is different from what extractElementPrefix() + * predicted. This happens because extractElementPrefix() stops when a character that + * cannot be an element name appears whereas parseAttributeInfo() uses a grammar more + * lenient as suitable for attribute values. + * - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal + * must be double-quoted. + * @param currentUiNode + * + * @return an AttributeDescriptor[] if the user is editing an attribute name. + * a String[] if the user is editing an attribute value with some known values, + * or null if nothing is known about the context. + */ + private Object[] getChoicesForAttribute(String parent, + Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo) { + Object[] choices = null; + if (attrInfo.isInValue) { + // Editing an attribute's value... Get the attribute name and then the + // possible choice for the tuple(parent,attribute) + String value = attrInfo.value; + if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$ + value = value.substring(1); + // The prefix that was found at the beginning only scan for characters + // valid of tag name. We now know the real prefix for this attribute's + // value, which is needed to generate the completion choices below. + attrInfo.correctedPrefix = value; + } else { + attrInfo.needTag = '"'; + } + + if (currentUiNode != null) { + // look for an UI attribute matching the current attribute name + String attrName = attrInfo.name; + + UiAttributeNode currAttrNode = null; + for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) { + if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) { + currAttrNode = attrNode; + break; + } + } + + if (currAttrNode != null) { + choices = currAttrNode.getPossibleValues(); + + if (currAttrNode instanceof UiFlagAttributeNode) { + // A "flag" can consist of several values separated by "or" (|). + // If the correct prefix contains such a pipe character, we change + // it so that only the currently edited value is completed. + int pos = value.indexOf('|'); + if (pos >= 0) { + attrInfo.correctedPrefix = value = value.substring(pos + 1); + attrInfo.needTag = 0; + } + } + } + } + + if (choices == null) { + // fallback on the older descriptor-only based lookup. + + // in order to properly handle the special case of the name attribute in + // the action tag, we need the grandparent of the action node, to know + // what type of actions we need. + // e.g. activity -> intent-filter -> action[@name] + String greatGrandParentName = null; + Node grandParent = currentNode.getParentNode(); + if (grandParent != null) { + Node greatGrandParent = grandParent.getParentNode(); + if (greatGrandParent != null) { + greatGrandParentName = greatGrandParent.getLocalName(); + } + } + + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + choices = data.getAttributeValues(parent, attrInfo.name, greatGrandParentName); + } + } + } else { + // Editing an attribute's name... Get attributes valid for the parent node. + if (currentUiNode != null) { + choices = currentUiNode.getAttributeDescriptors(); + } else { + ElementDescriptor parent_desc = getDescriptor(parent); + choices = parent_desc.getAttributes(); + } + } + return choices; + } + + /** + * Gets the choices when the user is editing an XML text node. + *

          + * This means the user is editing outside of any XML element or attribute. + * Simply return the list of XML elements that can be present there, based on the + * parent of the current node. + * + * @return An ElementDescriptor[] or null. + */ + private Object[] getChoicesForTextNode(Node currentNode) { + Object[] choices = null; + String parent; + Node parent_node = currentNode.getParentNode(); + if (parent_node.getNodeType() == Node.ELEMENT_NODE) { + // We're editing a text node which parent is an element node. Limit + // content assist to elements valid for the parent. + parent = parent_node.getNodeName(); + ElementDescriptor desc = getDescriptor(parent); + if (desc != null) { + choices = desc.getChildren(); + } + } else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) { + // We're editing a text node at the first level (i.e. root node). + // Limit content assist to the only valid root elements. + choices = getRootDescriptor().getChildren(); + } + return choices; + } + + /** + * Given a list of choices found, generates the proposals to be displayed to the user. + *

          + * Choices is an object array. Items of the array can be: + * - ElementDescriptor: a possible element descriptor which XML name should be completed. + * - AttributeDescriptor: a possible attribute descriptor which XML name should be completed. + * - String: string values to display as-is to the user. Typically those are possible + * values for a given attribute. + * + * @return The ICompletionProposal[] to display to the user. + */ + private ICompletionProposal[] computeProposals(int offset, Node currentNode, + Object[] choices, String wordPrefix, char need_tag, + boolean is_attribute, int selectionLength) { + ArrayList proposals = new ArrayList(); + HashMap nsUriMap = new HashMap(); + + for (Object choice : choices) { + String keyword = null; + String nsPrefix = null; + Image icon = null; + String tooltip = null; + if (choice instanceof ElementDescriptor) { + keyword = ((ElementDescriptor)choice).getXmlName(); + icon = ((ElementDescriptor)choice).getIcon(); + tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip()); + } else if (choice instanceof TextValueDescriptor) { + continue; // Value nodes are not part of the completion choices + } else if (choice instanceof SeparatorAttributeDescriptor) { + continue; // not real attribute descriptors + } else if (choice instanceof AttributeDescriptor) { + keyword = ((AttributeDescriptor)choice).getXmlLocalName(); + icon = ((AttributeDescriptor)choice).getIcon(); + if (choice instanceof TextAttributeDescriptor) { + tooltip = ((TextAttributeDescriptor) choice).getTooltip(); + } + + String nsUri = ((AttributeDescriptor)choice).getNamespaceUri(); + nsPrefix = nsUriMap.get(nsUri); + if (nsPrefix == null) { + nsPrefix = lookupNamespacePrefix(currentNode, nsUri); + nsUriMap.put(nsUri, nsPrefix); + } + if (nsPrefix != null) { + nsPrefix += ":"; //$NON-NLS-1$ + } + + } else if (choice instanceof String) { + keyword = (String) choice; + } else { + continue; // discard unknown choice + } + + if (keyword.startsWith(wordPrefix) || + (nsPrefix != null && keyword.startsWith(nsPrefix))) { + if (nsPrefix != null) { + keyword = nsPrefix + keyword; + } + String end_tag = ""; //$NON-NLS-1$ + if (need_tag != 0) { + if (need_tag == '"') { + keyword = need_tag + keyword; + end_tag = String.valueOf(need_tag); + } else if (need_tag == '<') { + if (elementCanHaveChildren(choice)) { + end_tag = String.format(">", keyword); //$NON-NLS-1$ + keyword = need_tag + keyword; + } else { + keyword = need_tag + keyword; + end_tag = "/>"; //$NON-NLS-1$ + } + } + } + CompletionProposal proposal = new CompletionProposal( + keyword + end_tag, // String replacementString + offset - wordPrefix.length(), // int replacementOffset + wordPrefix.length() + selectionLength, // int replacementLength + keyword.length(), // int cursorPosition (rel. to rplcmntOffset) + icon, // Image image + null, // String displayString + null, // IContextInformation contextInformation + tooltip // String additionalProposalInfo + ); + + proposals.add(proposal); + } + } + + return proposals.toArray(new ICompletionProposal[proposals.size()]); + } + + /** + * Indicates whether this descriptor describes an element that can potentially + * have children (either sub-elements or text value). If an element can have children, + * we want to explicitly write an opening and a separate closing tag. + *

          + * Elements can have children if the descriptor has children element descriptors + * or if one of the attributes is a TextValueDescriptor. + * + * @param descriptor An ElementDescriptor or an AttributeDescriptor + * @return True if the descriptor is an ElementDescriptor that can have children or a text value + */ + private boolean elementCanHaveChildren(Object descriptor) { + if (descriptor instanceof ElementDescriptor) { + ElementDescriptor desc = (ElementDescriptor) descriptor; + if (desc.hasChildren()) { + return true; + } + for (AttributeDescriptor attr_desc : desc.getAttributes()) { + if (attr_desc instanceof TextValueDescriptor) { + return true; + } + } + } + return false; + } + + /** + * Returns the element descriptor matching a given XML node name or null if it can't be + * found. + *

          + * This is simplistic; ideally we should consider the parent's chain to make sure we + * can differentiate between different hierarchy trees. Right now the first match found + * is returned. + */ + private ElementDescriptor getDescriptor(String nodeName) { + return getRootDescriptor().findChildrenDescriptor(nodeName, true /* recursive */); + } + + public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { + return null; + } + + /** + * Returns the characters which when entered by the user should + * automatically trigger the presentation of possible completions. + * + * In our case, we auto-activate on opening tags and attributes namespace. + * + * @return the auto activation characters for completion proposal or null + * if no auto activation is desired + */ + public char[] getCompletionProposalAutoActivationCharacters() { + return new char[]{ '<', ':', '=' }; + } + + public char[] getContextInformationAutoActivationCharacters() { + return null; + } + + public IContextInformationValidator getContextInformationValidator() { + return null; + } + + public String getErrorMessage() { + return null; + } + + /** + * Heuristically extracts the prefix used for determining template relevance + * from the viewer's document. The default implementation returns the String from + * offset backwards that forms a potential XML element name. + * + * Code extracted from org.eclipse.jface.text.templatesTemplateCompletionProcessor + * and adapted to our needs. + * + * @param viewer the viewer + * @param offset offset into document + * @return the prefix to consider + */ + protected String extractElementPrefix(ITextViewer viewer, int offset) { + int i = offset; + IDocument document = viewer.getDocument(); + if (i > document.getLength()) return ""; //$NON-NLS-1$ + + try { + for (; i > 0; --i) { + char ch = document.getChar(i - 1); + // accepted characters are a-z and : (for attributes' namespace) + if (ch != ':' && (ch < 'a' || ch > 'z')) break; + } + + return document.get(i, offset - i); + } catch (BadLocationException e) { + return ""; //$NON-NLS-1$ + } + } + + /** + * Extracts the character at the given offset. + * Returns 0 if the offset is invalid. + */ + protected char extractChar(ITextViewer viewer, int offset) { + IDocument document = viewer.getDocument(); + if (offset > document.getLength()) return 0; + + try { + return document.getChar(offset); + } catch (BadLocationException e) { + return 0; + } + } + + /** + * Information about the current edit of an attribute as reported by parseAttributeInfo. + */ + private class AttribInfo { + /** True if the cursor is located in an attribute's value, false if in an attribute name */ + public boolean isInValue = false; + /** The attribute name. Null when not set. */ + public String name = null; + /** The attribute value. Null when not set. The value *may* start with a quote + * (' or "), in which case we know we don't need to quote the string for the user */ + public String value = null; + /** String typed by the user so far (i.e. right before requesting code completion), + * which will be corrected if we find a possible completion for an attribute value. + * See the long comment in getChoicesForAttribute(). */ + public String correctedPrefix = null; + /** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */ + public char needTag = 0; + } + + + /** + * Try to guess if the cursor is editing an element's name or an attribute following an + * element. If it's an attribute, try to find if an attribute name is being defined or + * its value. + *
          + * This is currently *only* called when we know the cursor is after a complete element + * tag name, so it should never return null. + *
          + * Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags + *
          + * @return An AttribInfo describing which attribute is being edited or null if the cursor is + * not editing an attribute (in which case it must be an element's name). + */ + private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset) { + AttribInfo info = new AttribInfo(); + + IDocument document = viewer.getDocument(); + int n = document.getLength(); + if (offset <= n) { + try { + n = offset; + for (;offset > 0; --offset) { + char ch = document.getChar(offset - 1); + if (ch == '<') break; + } + + // text will contain the full string of the current element, + // i.e. whatever is after the "<" to the current cursor + String text = document.get(offset, n - offset); + + // Normalize whitespace to single spaces + text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$ + + // Remove the leading element name. By spec, it must be after the < without + // any whitespace. If there's nothing left, no attribute has been defined yet. + // Be sure to keep any whitespace after the initial word if any, as it matters. + text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$ + + // There MUST be space after the element name. If not, the cursor is still + // defining the element name. + if (!text.startsWith(" ")) { //$NON-NLS-1$ + return null; + } + + // Remove full attributes: + // Syntax: + // name = "..." quoted string with all but < and " + // or: + // name = '...' quoted string with all but < and ' + String temp; + do { + temp = text; + text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$ + } while(!temp.equals(text)); + + // Now we're left with 3 cases: + // - nothing: either there is no attribute definition or the cursor located after + // a completed attribute definition. + // - a string with no =: the user is writing an attribute name. This case can be + // merged with the previous one. + // - string with an = sign, optionally followed by a quote (' or "): the user is + // writing the value of the attribute. + int pos_equal = text.indexOf('='); + if (pos_equal == -1) { + info.isInValue = false; + info.name = text.trim(); + } else { + info.isInValue = true; + info.name = text.substring(0, pos_equal).trim(); + info.value = text.substring(pos_equal + 1).trim(); + } + return info; + } catch (BadLocationException e) { + // pass + } + } + + return null; + } + + + /** + * Returns the XML DOM node corresponding to the given offset of the given document. + */ + protected Node getNode(ITextViewer viewer, int offset) { + Node node = null; + try { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + IStructuredModel model = mm.getExistingModelForRead(viewer.getDocument()); + if (model != null) { + for(; offset >= 0 && node == null; --offset) { + node = (Node) model.getIndexedRegion(offset); + } + } + } + } catch (Exception e) { + // Ignore exceptions. + } + + return node; + } + + /** + * Computes (if needed) and returns the root descriptor. + * @return + */ + private ElementDescriptor getRootDescriptor() { + if (mRootDescriptor == null) { + AndroidTargetData data = mEditor.getTargetData(); + if (data != null) { + IDescriptorProvider descriptorProvider = data.getDescriptorProvider(mDescriptorId); + + if (descriptorProvider != null) { + mRootDescriptor = new ElementDescriptor("", + descriptorProvider.getRootElementDescriptors()); + } + } + } + + return mRootDescriptor; + } + + /** + * Returns the active {@link AndroidEditor} matching this source viewer. + */ + private AndroidEditor getAndroidEditor(ITextViewer viewer) { + IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (wwin != null) { + IWorkbenchPage page = wwin.getActivePage(); + if (page != null) { + IEditorPart editor = page.getActiveEditor(); + if (editor instanceof AndroidEditor) { + ISourceViewer ssviewer = ((AndroidEditor) editor).getStructuredSourceViewer(); + if (ssviewer == viewer) { + return (AndroidEditor) editor; + } + } + } + } + + return null; + } + + + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java new file mode 100644 index 0000000..78e0401 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidEditor.java @@ -0,0 +1,820 @@ +/* + * Copyright (C) 2007 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.editors; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.QualifiedName; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorSite; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.browser.IWorkbenchBrowserSupport; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormEditor; +import org.eclipse.ui.forms.editor.IFormPage; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.events.IHyperlinkListener; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.part.MultiPageEditorPart; +import org.eclipse.ui.part.WorkbenchPart; +import org.eclipse.wst.sse.core.StructuredModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelManager; +import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; +import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; +import org.eclipse.wst.sse.ui.StructuredTextEditor; +import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; +import org.w3c.dom.Document; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Multi-page form editor for Android XML files. + *

          + * It is designed to work with a {@link StructuredTextEditor} that will display an XML file. + *
          + * Derived classes must implement createFormPages to create the forms before the + * source editor. This can be a no-op if desired. + */ +public abstract class AndroidEditor extends FormEditor implements IResourceChangeListener { + + /** Preference name for the current page of this file */ + private static final String PREF_CURRENT_PAGE = "_current_page"; + + /** Id string used to create the Android SDK browser */ + private static String BROWSER_ID = "android"; // $NON-NLS-1$ + + /** Page id of the XML source editor, used for switching tabs programmatically */ + public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$ + + /** Width hint for text fields. Helps the grid layout resize properly on smaller screens */ + public static final int TEXT_WIDTH_HINT = 50; + + /** Page index of the text editor (always the last page) */ + private int mTextPageIndex; + /** The text editor */ + private StructuredTextEditor mTextEditor; + /** Listener for the XML model from the StructuredEditor */ + private XmlModelStateListener mXmlModelStateListener; + /** Listener to update the root node if the resource framework changes */ + private Runnable mResourceRefreshListener; + + /** + * Creates a form editor. + */ + public AndroidEditor() { + super(); + ResourcesPlugin.getWorkspace().addResourceChangeListener(this); + + mResourceRefreshListener = new Runnable() { + public void run() { + commitPages(false /* onSave */); + + // recreate the ui root node always + initUiRootNode(true /*force*/); + } + }; + AdtPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); + } + + // ---- Abstract Methods ---- + + /** + * Returns the root node of the UI element hierarchy manipulated by the current + * UI node editor. + */ + abstract public UiElementNode getUiRootNode(); + + /** + * Creates the various form pages. + *

          + * Derived classes must implement this to add their own specific tabs. + */ + abstract protected void createFormPages(); + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiManifestNode is recreated even if it already exists. + */ + abstract protected void initUiRootNode(boolean force); + + /** + * Subclasses should override this method to process the new XML Model, which XML + * root node is given. + * + * The base implementation is empty. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + protected void xmlModelChanged(Document xml_doc) { + // pass + } + + // ---- Base Class Overrides, Interfaces Implemented ---- + + /** + * Creates the pages of the multi-page editor. + */ + @Override + protected void addPages() { + createAndroidPages(); + selectDefaultPage(null /* defaultPageId */); + } + + /** + * Creates the page for the Android Editors + */ + protected void createAndroidPages() { + createFormPages(); + createTextEditor(); + + createUndoRedoActions(); + } + + /** + * Creates undo redo actions for the editor site (so that it works for any page of this + * multi-page editor) by re-using the actions defined by the {@link StructuredTextEditor} + * (aka the XML text editor.) + */ + private void createUndoRedoActions() { + IActionBars bars = getEditorSite().getActionBars(); + if (bars != null) { + IAction action = mTextEditor.getAction(ActionFactory.UNDO.getId()); + bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), action); + + action = mTextEditor.getAction(ActionFactory.REDO.getId()); + bars.setGlobalActionHandler(ActionFactory.REDO.getId(), action); + + bars.updateActionBars(); + } + } + + /** + * Selects the default active page. + * @param defaultPageId the id of the page to show. If null the editor attempts to + * find the default page in the properties of the {@link IResource} object being edited. + */ + protected void selectDefaultPage(String defaultPageId) { + if (defaultPageId == null) { + if (getEditorInput() instanceof IFileEditorInput) { + IFile file = ((IFileEditorInput) getEditorInput()).getFile(); + + QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, + getClass().getSimpleName() + PREF_CURRENT_PAGE); + String pageId; + try { + pageId = file.getPersistentProperty(qname); + if (pageId != null) { + defaultPageId = pageId; + } + } catch (CoreException e) { + // ignored + } + } + } + + if (defaultPageId != null) { + try { + setActivePage(Integer.parseInt(defaultPageId)); + } catch (Exception e) { + // We can get NumberFormatException from parseInt but also + // AssertionError from setActivePage when the index is out of bounds. + // Generally speaking we just want to ignore any exception and fall back on the + // first page rather than crash the editor load. Logging the error is enough. + AdtPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId); + } + } + } + + /** + * Removes all the pages from the editor. + */ + protected void removePages() { + int count = getPageCount(); + for (int i = count - 1 ; i >= 0 ; i--) { + removePage(i); + } + } + + /** + * Overrides the parent's setActivePage to be able to switch to the xml editor. + * + * If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page. + * This is needed because the editor doesn't actually derive from IFormPage and thus + * doesn't have the get-by-page-id method. In this case, the method returns null since + * IEditorPart does not implement IFormPage. + */ + @Override + public IFormPage setActivePage(String pageId) { + if (pageId.equals(TEXT_EDITOR_ID)) { + super.setActivePage(mTextPageIndex); + return null; + } else { + return super.setActivePage(pageId); + } + } + + + /** + * Notifies this multi-page editor that the page with the given id has been + * activated. This method is called when the user selects a different tab. + * + * @see MultiPageEditorPart#pageChange(int) + */ + @Override + protected void pageChange(int newPageIndex) { + super.pageChange(newPageIndex); + + if (getEditorInput() instanceof IFileEditorInput) { + IFile file = ((IFileEditorInput) getEditorInput()).getFile(); + + QualifiedName qname = new QualifiedName(AdtPlugin.PLUGIN_ID, + getClass().getSimpleName() + PREF_CURRENT_PAGE); + try { + file.setPersistentProperty(qname, Integer.toString(newPageIndex)); + } catch (CoreException e) { + // ignore + } + } + } + + /** + * Notifies this listener that some resource changes + * are happening, or have already happened. + * + * Closes all project files on project close. + * @see IResourceChangeListener + */ + public void resourceChanged(final IResourceChangeEvent event) { + if (event.getType() == IResourceChangeEvent.PRE_CLOSE) { + Display.getDefault().asyncExec(new Runnable() { + public void run() { + IWorkbenchPage[] pages = getSite().getWorkbenchWindow() + .getPages(); + for (int i = 0; i < pages.length; i++) { + if (((FileEditorInput)mTextEditor.getEditorInput()) + .getFile().getProject().equals( + event.getResource())) { + IEditorPart editorPart = pages[i].findEditor(mTextEditor + .getEditorInput()); + pages[i].closeEditor(editorPart, true); + } + } + } + }); + } + } + + /** + * Initializes the editor part with a site and input. + *

          + * Checks that the input is an instance of {@link IFileEditorInput}. + * + * @see FormEditor + */ + @Override + public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException { + if (!(editorInput instanceof IFileEditorInput)) + throw new PartInitException("Invalid Input: Must be IFileEditorInput"); + super.init(site, editorInput); + } + + /** + * Removes attached listeners. + * + * @see WorkbenchPart + */ + @Override + public void dispose() { + IStructuredModel xml_model = getModelForRead(); + if (xml_model != null) { + try { + if (mXmlModelStateListener != null) { + xml_model.removeModelStateListener(mXmlModelStateListener); + } + + } finally { + xml_model.releaseFromRead(); + } + } + ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); + + if (mResourceRefreshListener != null) { + AdtPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); + mResourceRefreshListener = null; + } + + super.dispose(); + } + + /** + * Commit all dirty pages then saves the contents of the text editor. + *

          + * This works by committing all data to the XML model and then + * asking the Structured XML Editor to save the XML. + * + * @see IEditorPart + */ + @Override + public void doSave(IProgressMonitor monitor) { + commitPages(true /* onSave */); + + // The actual "save" operation is done by the Structured XML Editor + getEditor(mTextPageIndex).doSave(monitor); + } + + /* (non-Javadoc) + * Saves the contents of this editor to another object. + *

          + * Subclasses must override this method to implement the open-save-close lifecycle + * for an editor. For greater details, see IEditorPart + *

          + * + * @see IEditorPart + */ + @Override + public void doSaveAs() { + commitPages(true /* onSave */); + + IEditorPart editor = getEditor(mTextPageIndex); + editor.doSaveAs(); + setPageText(mTextPageIndex, editor.getTitle()); + setInput(editor.getEditorInput()); + } + + /** + * Commits all dirty pages in the editor. This method should + * be called as a first step of a 'save' operation. + *

          + * This is the same implementation as in {@link FormEditor} + * except it fixes two bugs: a cast to IFormPage is done + * from page.get(i) before being tested with instanceof. + * Another bug is that the last page might be a null pointer. + *

          + * The incorrect casting makes the original implementation crash due + * to our {@link StructuredTextEditor} not being an {@link IFormPage} + * so we have to override and duplicate to fix it. + * + * @param onSave true if commit is performed as part + * of the 'save' operation, false otherwise. + * @since 3.3 + */ + @Override + public void commitPages(boolean onSave) { + if (pages != null) { + for (int i = 0; i < pages.size(); i++) { + Object page = pages.get(i); + if (page != null && page instanceof IFormPage) { + IFormPage form_page = (IFormPage) page; + IManagedForm managed_form = form_page.getManagedForm(); + if (managed_form != null && managed_form.isDirty()) { + managed_form.commit(onSave); + } + } + } + } + } + + /* (non-Javadoc) + * Returns whether the "save as" operation is supported by this editor. + *

          + * Subclasses must override this method to implement the open-save-close lifecycle + * for an editor. For greater details, see IEditorPart + *

          + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return false; + } + + // ---- Local methods ---- + + + /** + * Helper method that creates a new hyper-link Listener. + * Used by derived classes which need active links in {@link FormText}. + *

          + * This link listener handles two kinds of URLs: + *

            + *
          • Links starting with "http" are simply sent to a local browser. + *
          • Links starting with "file:/" are simply sent to a local browser. + *
          • Links starting with "page:" are expected to be an editor page id to switch to. + *
          • Other links are ignored. + *
          + * + * @return A new hyper-link listener for FormText to use. + */ + public final IHyperlinkListener createHyperlinkListener() { + return new HyperlinkAdapter() { + /** + * Switch to the page corresponding to the link that has just been clicked. + * For this purpose, the HREF of the <a> tags above is the page ID to switch to. + */ + @Override + public void linkActivated(HyperlinkEvent e) { + super.linkActivated(e); + String link = e.data.toString(); + if (link.startsWith("http") || //$NON-NLS-1$ + link.startsWith("file:/")) { //$NON-NLS-1$ + openLinkInBrowser(link); + } else if (link.startsWith("page:")) { //$NON-NLS-1$ + // Switch to an internal page + setActivePage(link.substring(5 /* strlen("page:") */)); + } + } + }; + } + + /** + * Open the http link into a browser + * + * @param link The URL to open in a browser + */ + private void openLinkInBrowser(String link) { + try { + IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance(); + wbs.createBrowser(BROWSER_ID).openURL(new URL(link)); + } catch (PartInitException e1) { + // pass + } catch (MalformedURLException e1) { + // pass + } + } + + /** + * Creates the XML source editor. + *

          + * Memorizes the index page of the source editor (it's always the last page, but the number + * of pages before can change.) + *
          + * Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it. + * Finally triggers modelChanged() on the model listener -- derived classes can use this + * to initialize the model the first time. + *

          + * Called only once after createFormPages. + */ + private void createTextEditor() { + try { + mTextEditor = new StructuredTextEditor(); + int index = addPage(mTextEditor, getEditorInput()); + mTextPageIndex = index; + setPageText(index, mTextEditor.getTitle()); + + if (!(mTextEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) { + Status status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + "Error opening the Android XML editor. Is the document an XML file?"); + throw new RuntimeException("Android XML Editor Error", new CoreException(status)); + } + + IStructuredModel xml_model = getModelForRead(); + if (xml_model != null) { + try { + mXmlModelStateListener = new XmlModelStateListener(); + xml_model.addModelStateListener(mXmlModelStateListener); + mXmlModelStateListener.modelChanged(xml_model); + } catch (Exception e) { + AdtPlugin.log(e, "Error while loading editor"); //$NON-NLS-1$ + } finally { + xml_model.releaseFromRead(); + } + } + } catch (PartInitException e) { + ErrorDialog.openError(getSite().getShell(), + "Android XML Editor Error", null, e.getStatus()); + } + } + + /** + * Returns the ISourceViewer associated with the Structured Text editor. + */ + public final ISourceViewer getStructuredSourceViewer() { + if (mTextEditor != null) { + // We can't access mEditor.getSourceViewer() because it is protected, + // however getTextViewer simply returns the SourceViewer casted, so we + // can use it instead. + return mTextEditor.getTextViewer(); + } + return null; + } + + /** + * Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source + * Editor) or null if not available. + */ + public final IStructuredDocument getStructuredDocument() { + if (mTextEditor != null && mTextEditor.getTextViewer() != null) { + return (IStructuredDocument) mTextEditor.getTextViewer().getDocument(); + } + return null; + } + + /** + * Returns a version of the model that has been shared for read. + *

          + * Callers must call model.releaseFromRead() when done, typically + * in a try..finally clause. + * + * @return The model for the XML document or null if cannot be obtained from the editor + */ + public final IStructuredModel getModelForRead() { + IStructuredDocument document = getStructuredDocument(); + if (document != null) { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + return mm.getModelForRead(document); + } + } + return null; + } + + /** + * Returns a version of the model that has been shared for edit. + *

          + * Callers must call model.releaseFromEdit() when done, typically + * in a try..finally clause. + * + * @return The model for the XML document or null if cannot be obtained from the editor + */ + public final IStructuredModel getModelForEdit() { + IStructuredDocument document = getStructuredDocument(); + if (document != null) { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + return mm.getModelForEdit(document); + } + } + return null; + } + + /** + * Helper class to perform edits on the XML model whilst making sure the + * model has been prepared to be changed. + *

          + * It first gets a model for edition using {@link #getModelForEdit()}, + * then calls {@link IStructuredModel#aboutToChangeModel()}, + * then performs the requested action + * and finally calls {@link IStructuredModel#changedModel()} + * and {@link IStructuredModel#releaseFromEdit()}. + *

          + * The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method + * is called, XML model listeners will be triggered. + * + * @param edit_action Something that will change the XML. + */ + public final void editXmlModel(Runnable edit_action) { + IStructuredModel model = getModelForEdit(); + try { + model.aboutToChangeModel(); + edit_action.run(); + } finally { + // Notify the model we're done modifying it. This must *always* be executed. + model.changedModel(); + model.releaseFromEdit(); + } + } + + /** + * Starts an "undo recording" session. This is managed by the underlying undo manager + * associated to the structured XML model. + *

          + * There must be a corresponding call to {@link #endUndoRecording()}. + *

          + * beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one + * undo operation is recorded.) + * + * @param label The label for the undo operation. Can be null but we should really try to put + * something meaningful if possible. + * @return True if the undo recording actually started, false if any kind of error occured. + * {@link #endUndoRecording()} should only be called if True is returned. + */ + private final boolean beginUndoRecording(String label) { + IStructuredDocument document = getStructuredDocument(); + if (document != null) { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + IStructuredModel model = mm.getModelForEdit(document); + if (model != null) { + model.beginRecording(this, label); + return true; + } + } + } + return false; + } + + /** + * Ends an "undo recording" session. + *

          + * This is the counterpart call to {@link #beginUndoRecording(String)} and should only be + * used if the initial call returned true. + */ + private final void endUndoRecording() { + IStructuredDocument document = getStructuredDocument(); + if (document != null) { + IModelManager mm = StructuredModelManager.getModelManager(); + if (mm != null) { + IStructuredModel model = mm.getModelForEdit(document); + if (model != null) { + model.endRecording(this); + } + } + } + } + + /** + * Creates an "undo recording" session by calling the undoableAction runnable + * using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}. + *

          + * You can nest several calls to {@link #wrapUndoRecording(String, Runnable)}, only one + * recording session will be created. + * + * @param label The label for the undo operation. Can be null. Ideally we should really try + * to put something meaningful if possible. + */ + public void wrapUndoRecording(String label, Runnable undoableAction) { + boolean recording = false; + try { + recording = beginUndoRecording(label); + undoableAction.run(); + } finally { + if (recording) { + endUndoRecording(); + } + } + } + + /** + * Returns the XML {@link Document} or null if we can't get it + */ + protected final Document getXmlDocument(IStructuredModel model) { + if (model == null) { + AdtPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$ + return null; + } + + if (model instanceof IDOMModel) { + IDOMModel dom_model = (IDOMModel) model; + return dom_model.getDocument(); + } + return null; + } + + /** + * Returns the {@link IProject} for the edited file. + */ + public IProject getProject() { + if (mTextEditor != null) { + IEditorInput input = mTextEditor.getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + IFile inputFile = fileInput.getFile(); + + if (inputFile != null) { + return inputFile.getProject(); + } + } + } + + return null; + } + + /** + * Returns the {@link PlatformData} for the edited file. + */ + public AndroidTargetData getTargetData() { + IProject project = getProject(); + if (project != null) { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(project); + + if (target != null) { + return currentSdk.getTargetData(target); + } + } + } + + return null; + } + + + /** + * Listen to changes in the underlying XML model in the structured editor. + */ + private class XmlModelStateListener implements IModelStateListener { + + /** + * A model is about to be changed. This typically is initiated by one + * client of the model, to signal a large change and/or a change to the + * model's ID or base Location. A typical use might be if a client might + * want to suspend processing until all changes have been made. + *

          + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelAboutToBeChanged(IStructuredModel model) { + // pass + } + + /** + * Signals that the changes foretold by modelAboutToBeChanged have been + * made. A typical use might be to refresh, or to resume processing that + * was suspended as a result of modelAboutToBeChanged. + *

          + * This AndroidEditor implementation calls the xmlModelChanged callback. + */ + public void modelChanged(IStructuredModel model) { + xmlModelChanged(getXmlDocument(model)); + } + + /** + * Notifies that a model's dirty state has changed, and passes that state + * in isDirty. A model becomes dirty when any change is made, and becomes + * not-dirty when the model is saved. + *

          + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) { + // pass + } + + /** + * A modelDeleted means the underlying resource has been deleted. The + * model itself is not removed from model management until all have + * released it. Note: baseLocation is not (necessarily) changed in this + * event, but may not be accurate. + *

          + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelResourceDeleted(IStructuredModel model) { + // pass + } + + /** + * A model has been renamed or copied (as in saveAs..). In the renamed + * case, the two paramenters are the same instance, and only contain the + * new info for id and base location. + *

          + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) { + // pass + } + + /** + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelAboutToBeReinitialized(IStructuredModel structuredModel) { + // pass + } + + /** + * This AndroidEditor implementation of IModelChangedListener is empty. + */ + public void modelReinitialized(IStructuredModel structuredModel) { + // pass + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java new file mode 100644 index 0000000..ab17bef --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2007 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.editors; + + +import org.eclipse.jface.text.IAutoEditStrategy; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextHover; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; +import org.eclipse.jface.text.contentassist.IContentAssistant; +import org.eclipse.jface.text.formatter.IContentFormatter; +import org.eclipse.jface.text.source.ISourceViewer; +import org.eclipse.jface.viewers.IInputProvider; +import org.eclipse.wst.sse.core.text.IStructuredPartitions; +import org.eclipse.wst.xml.core.text.IXMLPartitions; +import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML; + +import java.util.ArrayList; + +/** + * Base Source Viewer Configuration for Android resources. + */ +public class AndroidSourceViewerConfig extends StructuredTextViewerConfigurationXML { + + /** Content Assist Processor to use for all handled partitions. */ + private IContentAssistProcessor mProcessor; + + public AndroidSourceViewerConfig(IContentAssistProcessor processor) { + super(); + mProcessor = processor; + } + + @Override + public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { + return super.getContentAssistant(sourceViewer); + } + + /** + * Returns the content assist processors that will be used for content + * assist in the given source viewer and for the given partition type. + * + * @param sourceViewer the source viewer to be configured by this + * configuration + * @param partitionType the partition type for which the content assist + * processors are applicable + * @return IContentAssistProcessors or null if should not be supported + */ + @Override + protected IContentAssistProcessor[] getContentAssistProcessors( + ISourceViewer sourceViewer, String partitionType) { + ArrayList processors = new ArrayList(); + if (partitionType == IStructuredPartitions.UNKNOWN_PARTITION || + partitionType == IStructuredPartitions.DEFAULT_PARTITION || + partitionType == IXMLPartitions.XML_DEFAULT) { + if (sourceViewer instanceof IInputProvider) { + IInputProvider input = (IInputProvider) sourceViewer; + Object a = input.getInput(); + if (a != null) + a.toString(); + } + + IDocument doc = sourceViewer.getDocument(); + if (doc != null) + doc.toString(); + + processors.add(mProcessor); + } + + IContentAssistProcessor[] others = super.getContentAssistProcessors(sourceViewer, + partitionType); + if (others != null && others.length > 0) { + for (IContentAssistProcessor p : others) { + processors.add(p); + } + } + + if (processors.size() > 0) { + return processors.toArray(new IContentAssistProcessor[processors.size()]); + } else { + return null; + } + } + + @Override + public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { + // TODO text hover for android xml + return super.getTextHover(sourceViewer, contentType); + } + + @Override + public IAutoEditStrategy[] getAutoEditStrategies( + ISourceViewer sourceViewer, String contentType) { + // TODO auto edit strategies for android xml + return super.getAutoEditStrategies(sourceViewer, contentType); + } + + @Override + public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { + // TODO content formatter for android xml + return super.getContentFormatter(sourceViewer); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/FirstElementParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/FirstElementParser.java new file mode 100644 index 0000000..bb0996b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/FirstElementParser.java @@ -0,0 +1,164 @@ +/* + * 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.editors; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Quickly parses a (potential) XML file to extract its first element (i.e. the root element) + * and namespace, if any. + *

          + * This is used to determine if a file is an XML document that the XmlEditor can process. + *

          + * TODO use this to remove the hardcoded "android" namespace prefix limitation. + */ +public final class FirstElementParser { + + private static SAXParserFactory sSaxfactory; + + /** + * Result from the XML parsing.
          + * Contains the name of the root XML element.
          + * If an XMLNS URI was specified and found, the XMLNS prefix is recorded. Otherwise it is null. + */ + public static final class Result { + private String mElement; + private String mXmlnsPrefix; + private String mXmlnsUri; + + public String getElement() { + return mElement; + } + + public String getXmlnsPrefix() { + return mXmlnsPrefix; + } + + public String getXmlnsUri() { + return mXmlnsUri; + } + + void setElement(String element) { + mElement = element; + } + + void setXmlnsPrefix(String xmlnsPrefix) { + mXmlnsPrefix = xmlnsPrefix; + } + + void setXmlnsUri(String xmlnsUri) { + mXmlnsUri = xmlnsUri; + } + } + + private static class ResultFoundException extends SAXException { } + + /** + * Parses the given filename. + * + * @param osFilename The file to parse. + * @param xmlnsUri An optional URL of which we want to know the prefix. + * @return The element details found or null if not found. + */ + public static Result parse(String osFilename, String xmlnsUri) { + if (sSaxfactory == null) { + // TODO just create a single factory in CommonPlugin and reuse it + sSaxfactory = SAXParserFactory.newInstance(); + sSaxfactory.setNamespaceAware(true); + } + + Result result = new Result(); + if (xmlnsUri != null && xmlnsUri.length() > 0) { + result.setXmlnsUri(xmlnsUri); + } + + try { + SAXParser parser = sSaxfactory.newSAXParser(); + XmlHandler handler = new XmlHandler(result); + parser.parse(new InputSource(new FileReader(osFilename)), handler); + + } catch(ResultFoundException e) { + // XML handling was aborted because the required element was found. + // Simply return the result. + return result; + } catch (ParserConfigurationException e) { + } catch (SAXException e) { + } catch (FileNotFoundException e) { + } catch (IOException e) { + } + + return null; + } + + /** + * Private constructor. Use the static parse() method instead. + */ + private FirstElementParser() { + // pass + } + + /** + * A specialized SAX handler that captures the arguments of the very first element + * (i.e. the root element) + */ + private static class XmlHandler extends DefaultHandler { + private final Result mResult; + + public XmlHandler(Result result) { + mResult = result; + } + + /** + * Processes a namespace prefix mapping. + * I.e. for xmlns:android="some-uri", this received prefix="android" and uri="some-uri". + *

          + * The prefix is recorded in the result structure if the URI is the one searched for. + *

          + * This event happens before the corresponding startElement event. + */ + @Override + public void startPrefixMapping(String prefix, String uri) { + if (uri.equals(mResult.getXmlnsUri())) { + mResult.setXmlnsPrefix(prefix); + } + } + + /** + * Processes a new element start. + *

          + * This simply records the element name and abort processing by throwing an exception. + */ + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) + throws SAXException { + mResult.setElement(localName); + throw new ResultFoundException(); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/IconFactory.java new file mode 100644 index 0000000..e3de3af --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/IconFactory.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.editors; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +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.widgets.Display; + +import java.util.HashMap; + +/** + * Factory to generate icons for Android Editors. + *

          + * Icons are kept here and reused. + */ +public class IconFactory { + + public static final int COLOR_RED = SWT.COLOR_DARK_RED; + public static final int COLOR_GREEN = SWT.COLOR_DARK_GREEN; + public static final int COLOR_BLUE = SWT.COLOR_DARK_BLUE; + public static final int COLOR_DEFAULT = SWT.COLOR_BLACK; + + public static final int SHAPE_CIRCLE = 'C'; + public static final int SHAPE_RECT = 'R'; + public static final int SHAPE_DEFAULT = SHAPE_CIRCLE; + + private static IconFactory sInstance; + + private HashMap mIconMap = new HashMap(); + private HashMap mImageDescMap = new HashMap(); + + private IconFactory() { + } + + public static synchronized IconFactory getInstance() { + if (sInstance == null) { + sInstance = new IconFactory(); + } + return sInstance; + } + + public void Dispose() { + // Dispose icons + for (Image icon : mIconMap.values()) { + // The map can contain null values + if (icon != null) { + icon.dispose(); + } + } + mIconMap.clear(); + } + + /** + * Returns an Image for a given icon name. + *

          + * Callers should not dispose it. + * + * @param osName The leaf name, without the extension, of an existing icon in the + * editor's "icons" directory. If it doesn't exists, a default icon will be + * generated automatically based on the name. + */ + public Image getIcon(String osName) { + return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT); + } + + /** + * Returns an Image for a given icon name. + *

          + * Callers should not dispose it. + * + * @param osName The leaf name, without the extension, of an existing icon in the + * editor's "icons" directory. If it doesn't exists, a default icon will be + * generated automatically based on the name. + * @param color The color of the text in the automatically generated icons, + * one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED. + * @param shape The shape of the icon in the automatically generated icons, + * one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT. + */ + public Image getIcon(String osName, int color, int shape) { + String key = Character.toString((char) shape) + Integer.toString(color) + osName; + Image icon = mIconMap.get(key); + if (icon == null && !mIconMap.containsKey(key)) { + ImageDescriptor id = getImageDescriptor(osName, color, shape); + if (id != null) { + icon = id.createImage(); + } + // Note that we store null references in the icon map, to avoid looking them + // up every time. If it didn't exist once, it will not exist later. + mIconMap.put(key, icon); + } + return icon; + } + + /** + * Returns an ImageDescriptor for a given icon name. + *

          + * Callers should not dispose it. + * + * @param osName The leaf name, without the extension, of an existing icon in the + * editor's "icons" directory. If it doesn't exists, a default icon will be + * generated automatically based on the name. + */ + public ImageDescriptor getImageDescriptor(String osName) { + return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT); + } + + /** + * Returns an ImageDescriptor for a given icon name. + *

          + * Callers should not dispose it. + * + * @param osName The leaf name, without the extension, of an existing icon in the + * editor's "icons" directory. If it doesn't exists, a default icon will be + * generated automatically based on the name. + * @param color The color of the text in the automatically generated icons. + * one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED. + * @param shape The shape of the icon in the automatically generated icons, + * one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT. + */ + public ImageDescriptor getImageDescriptor(String osName, int color, int shape) { + String key = Character.toString((char) shape) + Integer.toString(color) + osName; + ImageDescriptor id = mImageDescMap.get(key); + if (id == null && !mImageDescMap.containsKey(key)) { + id = AdtPlugin.imageDescriptorFromPlugin( + AdtPlugin.PLUGIN_ID, + String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$ + + if (id == null) { + id = new LetterImageDescriptor(osName.charAt(0), color, shape); + } + + // Note that we store null references in the icon map, to avoid looking them + // up every time. If it didn't exist once, it will not exist later. + mImageDescMap.put(key, id); + } + return id; + } + + /** + * A simple image description that generates a 16x16 image which consists + * of a colored letter inside a black & white circle. + */ + private static class LetterImageDescriptor extends ImageDescriptor { + + private final char mLetter; + private final int mColor; + private final int mShape; + + public LetterImageDescriptor(char letter, int color, int shape) { + mLetter = letter; + mColor = color; + mShape = shape; + } + + @Override + public ImageData getImageData() { + + final int SX = 15; + final int SY = 15; + final int RX = 4; + final int RY = 4; + + Display display = Display.getCurrent(); + if (display == null) { + return null; + } + + Image image = new Image(display, SX, SY); + + image.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); + + GC gc = new GC(image); + gc.setAdvanced(true); + gc.setAntialias(SWT.ON); + gc.setTextAntialias(SWT.ON); + + gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); + if (mShape == SHAPE_CIRCLE) { + gc.fillOval(0, 0, SX - 1, SY - 1); + } else if (mShape == SHAPE_RECT) { + gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY); + } + + gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.setLineWidth(1); + if (mShape == SHAPE_CIRCLE) { + gc.drawOval(0, 0, SX - 1, SY - 1); + } else if (mShape == SHAPE_RECT) { + gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY); + } + + // Get a bold version of the default system font, if possible. + Font font = display.getSystemFont(); + FontData[] fds = font.getFontData(); + fds[0].setStyle(SWT.BOLD); + // use 3/4th of the circle diameter for the font size (in pixels) + // and convert it to "font points" (font points in SWT are hardcoded in an + // arbitrary 72 dpi and then converted in real pixels using whatever is + // indicated by getDPI -- at least that's how it works under Win32). + fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y)); + // Note: win32 implementation always uses fds[0] so we change just that one. + // getFontData indicates that the array of fd is really an unusual thing for X11. + font = new Font(display, fds); + gc.setFont(font); + gc.setForeground(display.getSystemColor(mColor)); + + // Text measurement varies so slightly depending on the platform + int ofx = 0; + int ofy = 0; + if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_WINDOWS) { + ofx = +1; + ofy = -1; + } + + String s = Character.toString(mLetter).toUpperCase(); + Point p = gc.textExtent(s); + int tx = (SX + ofx - p.x) / 2; + int ty = (SY + ofy - p.y) / 2; + gc.drawText(s, tx, ty, true /* isTransparent */); + + font.dispose(); + gc.dispose(); + + ImageData data = image.getImageData(); + image.dispose(); + return data; + } + + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java new file mode 100644 index 0000000..2c779b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.swt.graphics.Image; + +/** + * {@link AttributeDescriptor} describes an XML attribute with its XML attribute name. + *

          + * An attribute descriptor also knows which UI node should be instantiated to represent + * this particular attribute (e.g. text field, icon chooser, class selector, etc.) + * Some attributes may be hidden and have no user interface at all. + *

          + * This is an abstract class. Derived classes must implement data description and return + * the correct UiAttributeNode-derived class. + */ +public abstract class AttributeDescriptor { + private String mXmlLocalName; + private ElementDescriptor mParent; + private final String mNsUri; + + /** + * Creates a new {@link AttributeDescriptor} + * + * @param xmlLocalName The XML name of the attribute (case sensitive) + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + */ + public AttributeDescriptor(String xmlLocalName, String nsUri) { + mXmlLocalName = xmlLocalName; + mNsUri = nsUri; + } + + /** + * Returns the XML local name of the attribute (case sensitive) + */ + public final String getXmlLocalName() { + return mXmlLocalName; + } + + public final String getNamespaceUri() { + return mNsUri; + } + + final void setParent(ElementDescriptor parent) { + mParent = parent; + } + + public final ElementDescriptor getParent() { + return mParent; + } + + /** + * Returns an optional icon for the attribute. + *

          + * By default this tries to return an icon based on the XML name of the attribute. + * If this fails, it tries to return the default Android logo as defined in the + * plugin. If all fails, it returns null. + * + * @return An icon for this element or null. + */ + public Image getIcon() { + IconFactory factory = IconFactory.getInstance(); + Image icon; + icon = factory.getIcon(getXmlLocalName(), IconFactory.COLOR_RED, IconFactory.SHAPE_CIRCLE); + return icon != null ? icon : AdtPlugin.getAndroidLogo(); + } + + /** + * @param uiParent The {@link UiElementNode} parent of this UI attribute. + * @return A new {@link UiAttributeNode} linked to this descriptor or null if this + * attribute has no user interface. + */ + public abstract UiAttributeNode createUiNode(UiElementNode uiParent); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java new file mode 100644 index 0000000..05ae922 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java @@ -0,0 +1,69 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiAbstractTextAttributeNode; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.swt.graphics.Image; + +/** + * Label provider for {@link UiAbstractTextAttributeNode}. + */ +public class AttributeDescriptorLabelProvider implements ILabelProvider { + + private final static AttributeDescriptorLabelProvider sThis = + new AttributeDescriptorLabelProvider(); + + public static ILabelProvider getProvider() { + return sThis; + } + + public Image getImage(Object element) { + return null; + } + + public String getText(Object element) { + if (element instanceof UiAbstractTextAttributeNode) { + return ((UiAbstractTextAttributeNode)element).getCurrentValue(); + } + + return null; + } + + public void addListener(ILabelProviderListener listener) { + // TODO Auto-generated method stub + + } + + public void dispose() { + // TODO Auto-generated method stub + + } + + public boolean isLabelProperty(Object element, String property) { + // TODO Auto-generated method stub + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // TODO Auto-generated method stub + + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java new file mode 100644 index 0000000..9ebf5f1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; + +/** + * Describes a text attribute that can only contain boolean values. + * It is displayed by a {@link UiListAttributeNode}. + */ +public class BooleanAttributeDescriptor extends ListAttributeDescriptor { + + public BooleanAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip, + new String[] { "true", "false" } ); + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java new file mode 100644 index 0000000..c84bf57 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java @@ -0,0 +1,809 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format; +import com.android.ide.eclipse.editors.layout.LayoutConstants; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.swt.graphics.Image; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * Utility methods related to descriptors handling. + */ +public final class DescriptorsUtils { + + private static final String DEFAULT_WIDGET_PREFIX = "widget"; + + private static final int JAVADOC_BREAK_LENGTH = 60; + + /** + * The path in the online documentation for the manifest description. + *

          + * This is NOT a complete URL. To be used, it needs to be appended + * to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK + * documentation. + */ + public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$ + + public static final String IMAGE_KEY = "image"; //$NON-NLS-1$ + + private static final String CODE = "$code"; //$NON-NLS-1$ + private static final String LINK = "$link"; //$NON-NLS-1$ + private static final String ELEM = "$elem"; //$NON-NLS-1$ + private static final String BREAK = "$break"; //$NON-NLS-1$ + + /** + * The {@link ITextAttributeCreator} interface is used by the appendAttribute() method + * to provide a way for caller to override the kind of {@link TextAttributeDescriptor} + * created for a give XML attribute name. + */ + public interface ITextAttributeCreator { + /** + * Creates a new {@link TextAttributeDescriptor} instance for the given XML name, + * UI name and tooltip. + * + * @param xmlName The XML attribute name. + * @param uiName The UI attribute name. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip An optional tooltip. + * @return A new {@link TextAttributeDescriptor} (or derived) instance. + */ + public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, + String tooltip); + } + + /** + * Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}. + * + * @param attributes The list of {@link AttributeDescriptor} to append to + * @param elementXmlName Optional XML local name of the element to which attributes are + * being added. When not null, this is used to filter overrides. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param infos The array of {@link AttributeInfo} to read and append to attributes + * @param requiredAttributes An optional list of attributes to mark as "required" (i.e. append + * a "*" to their UI name as a hint for the user.) + * @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator + * can either by a Class or an instance of + * {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor. + */ + public static void appendAttributes(ArrayList attributes, + String elementXmlName, + String nsUri, AttributeInfo[] infos, + String[] requiredAttributes, + Map overrides) { + for (AttributeInfo info : infos) { + boolean required = false; + if (requiredAttributes != null) { + for(String attr_name : requiredAttributes) { + if (attr_name.equals(info.getName())) { + required = true; + break; + } + } + } + appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides); + } + } + + /** + * Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}. + * + * @param attributes The list of {@link AttributeDescriptor} to append to + * @param elementXmlName Optional XML local name of the element to which attributes are + * being added. When not null, this is used to filter overrides. + * @param info The {@link AttributeInfo} to append to attributes + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param required True if the attribute is to be marked as "required" (i.e. append + * a "*" to its UI name as a hint for the user.) + * @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator + * can either by a Class or an instance of + * {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor. + */ + public static void appendAttribute(ArrayList attributes, + String elementXmlName, + String nsUri, + AttributeInfo info, boolean required, + Map overrides) { + AttributeDescriptor attr = null; + + String xmlLocalName = info.getName(); + String uiName = prettyAttributeUiName(info.getName()); // ui_name + if (required) { + uiName += "*"; //$NON-NLS-1$ + } + String tooltip = formatTooltip(info.getJavaDoc()); // tooltip + + // Add the known types to the tooltip + Format[] formats_list = info.getFormats(); + int flen = formats_list.length; + if (flen > 0) { + // Fill the formats in a set for faster access + HashSet formats_set = new HashSet(); + + StringBuilder sb = new StringBuilder(); + if (tooltip != null) { + sb.append(tooltip); + sb.append(" "); //$NON-NLS-1$ + } + sb.append("["); //$NON-NLS-1$ + for (int i = 0; i < flen; i++) { + Format f = formats_list[i]; + formats_set.add(f); + + sb.append(f.toString().toLowerCase()); + if (i < flen - 1) { + sb.append(", "); //$NON-NLS-1$ + } + } + // The extra space at the end makes the tooltip more readable on Windows. + sb.append("]"); //$NON-NLS-1$ + + if (required) { + sb.append(". Required."); + } + + // The extra space at the end makes the tooltip more readable on Windows. + sb.append(" "); //$NON-NLS-1$ + + tooltip = sb.toString(); + + // Create a specialized attribute if we can + if (overrides != null) { + for (Entry entry: overrides.entrySet()) { + String key = entry.getKey(); + String elements[] = key.split("/"); + String overrideAttrLocalName = null; + if (elements.length < 1) { + continue; + } else if (elements.length == 1) { + overrideAttrLocalName = elements[0]; + elements = null; + } else { + overrideAttrLocalName = elements[elements.length - 1]; + elements = elements[0].split(","); + } + + if (overrideAttrLocalName == null || + !overrideAttrLocalName.equals(xmlLocalName)) { + continue; + } + + boolean ok_element = elements.length < 1; + if (!ok_element) { + for (String element : elements) { + if (element.equals("*") || element.equals(elementXmlName)) { + ok_element = true; + break; + } + } + } + + if (!ok_element) { + continue; + } + + Object override = entry.getValue(); + if (override instanceof Class) { + try { + // The override is instance of the class to create, which must + // have a constructor compatible with TextAttributeDescriptor. + @SuppressWarnings("unchecked") //$NON-NLS-1$ + Class clazz = + (Class) override; + Constructor cons; + cons = clazz.getConstructor(new Class[] { + String.class, String.class, String.class, String.class } ); + attr = cons.newInstance( + new Object[] { xmlLocalName, uiName, nsUri, tooltip }); + } catch (SecurityException e) { + // ignore + } catch (NoSuchMethodException e) { + // ignore + } catch (IllegalArgumentException e) { + // ignore + } catch (InstantiationException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } catch (InvocationTargetException e) { + // ignore + } + } else if (override instanceof ITextAttributeCreator) { + attr = ((ITextAttributeCreator) override).create( + xmlLocalName, uiName, nsUri, tooltip); + } + } + } // if overrides + + // Create a specialized descriptor if we can, based on type + if (attr == null) { + if (formats_set.contains(Format.REFERENCE)) { + // This is either a multi-type reference or a generic reference. + attr = new ReferenceAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); + } else if (formats_set.contains(Format.ENUM)) { + attr = new ListAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip, + info.getEnumValues()); + } else if (formats_set.contains(Format.FLAG)) { + attr = new FlagAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip, + info.getFlagValues()); + } else if (formats_set.contains(Format.BOOLEAN)) { + attr = new BooleanAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); + } else if (formats_set.contains(Format.STRING)) { + attr = new ReferenceAttributeDescriptor(ResourceType.STRING, + xmlLocalName, uiName, nsUri, + tooltip); + } + } + } + + // By default a simple text field is used + if (attr == null) { + attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); + } + attributes.add(attr); + } + + /** + * Indicates the the given {@link AttributeInfo} already exists in the ArrayList of + * {@link AttributeDescriptor}. This test for the presence of a descriptor with the same + * XML name. + * + * @param attributes The list of {@link AttributeDescriptor} to compare to. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param info The {@link AttributeInfo} to know whether it is included in the above list. + * @return True if this {@link AttributeInfo} is already present in + * the {@link AttributeDescriptor} list. + */ + public static boolean containsAttribute(ArrayList attributes, + String nsUri, + AttributeInfo info) { + String xmlLocalName = info.getName(); + for (AttributeDescriptor desc : attributes) { + if (desc.getXmlLocalName().equals(xmlLocalName)) { + if (nsUri == desc.getNamespaceUri() || + (nsUri != null && nsUri.equals(desc.getNamespaceUri()))) { + return true; + } + } + } + return false; + } + + /** + * Create a pretty attribute UI name from an XML name. + *

          + * The original xml name starts with a lower case and is camel-case, + * e.g. "maxWidthForView". The pretty name starts with an upper case + * and has space separators, e.g. "Max width for view". + */ + public static String prettyAttributeUiName(String name) { + if (name.length() < 1) { + return name; + } + StringBuffer buf = new StringBuffer(); + + char c = name.charAt(0); + // Use upper case initial letter + buf.append((char)(c >= 'a' && c <= 'z' ? c + 'A' - 'a' : c)); + int len = name.length(); + for (int i = 1; i < len; i++) { + c = name.charAt(i); + if (c >= 'A' && c <= 'Z') { + // Break camel case into separate words + buf.append(' '); + // Use a lower case initial letter for the next word, except if the + // word is solely X, Y or Z. + if (c >= 'X' && c <= 'Z' && + (i == len-1 || + (i < len-1 && name.charAt(i+1) >= 'A' && name.charAt(i+1) <= 'Z'))) { + buf.append(c); + } else { + buf.append((char)(c - 'A' + 'a')); + } + } else if (c == '_') { + buf.append(' '); + } else { + buf.append(c); + } + } + + name = buf.toString(); + + // Replace these acronyms by upper-case versions + // - (?<=^| ) means "if preceded by a space or beginning of string" + // - (?=$| ) means "if followed by a space or end of string" + name = name.replaceAll("(?<=^| )sdk(?=$| )", "SDK"); + name = name.replaceAll("(?<=^| )uri(?=$| )", "URI"); + + return name; + } + + /** + * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z]. + * Returns the string unmodified if the first character is not [a-z]. + * + * @param str The string to capitalize. + * @return The capitalized string + */ + public static String capitalize(String str) { + if (str == null || str.length() < 1 || str.charAt(0) < 'a' || str.charAt(0) > 'z') { + return str; + } + + StringBuilder sb = new StringBuilder(); + sb.append((char)(str.charAt(0) + 'A' - 'a')); + sb.append(str.substring(1)); + return sb.toString(); + } + + /** + * Formats the javadoc tooltip to be usable in a tooltip. + */ + public static String formatTooltip(String javadoc) { + ArrayList spans = scanJavadoc(javadoc); + + StringBuilder sb = new StringBuilder(); + boolean needBreak = false; + + for (int n = spans.size(), i = 0; i < n; ++i) { + String s = spans.get(i); + if (CODE.equals(s)) { + s = spans.get(++i); + if (s != null) { + sb.append('"').append(s).append('"'); + } + } else if (LINK.equals(s)) { + String base = spans.get(++i); + String anchor = spans.get(++i); + String text = spans.get(++i); + + if (base != null) { + base = base.trim(); + } + if (anchor != null) { + anchor = anchor.trim(); + } + if (text != null) { + text = text.trim(); + } + + // If there's no text, use the anchor if there's one + if (text == null || text.length() == 0) { + text = anchor; + } + + if (base != null && base.length() > 0) { + if (text == null || text.length() == 0) { + // If we still have no text, use the base as text + text = base; + } + } + + if (text != null) { + sb.append(text); + } + + } else if (ELEM.equals(s)) { + s = spans.get(++i); + if (s != null) { + sb.append(s); + } + } else if (BREAK.equals(s)) { + needBreak = true; + } else if (s != null) { + if (needBreak && s.trim().length() > 0) { + sb.append('\r'); + } + sb.append(s); + needBreak = false; + } + } + + return sb.toString(); + } + + /** + * Formats the javadoc tooltip to be usable in a FormText. + *

          + * If the descriptor can provide an icon, the caller should provide + * elementsDescriptor.getIcon() as "image" to FormText, e.g.: + * formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon()); + * + * @param javadoc The javadoc to format. Cannot be null. + * @param elementDescriptor The element descriptor parent of the javadoc. Cannot be null. + * @param androidDocBaseUrl The base URL for the documentation. Cannot be null. Should be + * FrameworkResourceManager.getInstance().getDocumentationBaseUrl() + */ + public static String formatFormText(String javadoc, + ElementDescriptor elementDescriptor, + String androidDocBaseUrl) { + ArrayList spans = scanJavadoc(javadoc); + + String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL; + String sdkUrl = elementDescriptor.getSdkUrl(); + if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) { + fullSdkUrl = androidDocBaseUrl + sdkUrl; + } + + StringBuilder sb = new StringBuilder(); + + Image icon = elementDescriptor.getIcon(); + if (icon != null) { + sb.append("

        • "); //$NON-NLS-1$ + } else { + sb.append("

          "); //$NON-NLS-1$ + } + + for (int n = spans.size(), i = 0; i < n; ++i) { + String s = spans.get(i); + if (CODE.equals(s)) { + s = spans.get(++i); + if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) { + sb.append(""); //$NON-NLS-1$ + sb.append(s); + sb.append(""); //$NON-NLS-1$ + } else if (s != null) { + sb.append('"').append(s).append('"'); + } + } else if (LINK.equals(s)) { + String base = spans.get(++i); + String anchor = spans.get(++i); + String text = spans.get(++i); + + if (base != null) { + base = base.trim(); + } + if (anchor != null) { + anchor = anchor.trim(); + } + if (text != null) { + text = text.trim(); + } + + // If there's no text, use the anchor if there's one + if (text == null || text.length() == 0) { + text = anchor; + } + + // TODO specialize with a base URL for views, menus & other resources + // Base is empty for a local page anchor, in which case we'll replace it + // by the element SDK URL if it exists. + if ((base == null || base.length() == 0) && fullSdkUrl != null) { + base = fullSdkUrl; + } + + String url = null; + if (base != null && base.length() > 0) { + if (base.startsWith("http")) { //$NON-NLS-1$ + // If base looks an URL, use it, with the optional anchor + url = base; + if (anchor != null && anchor.length() > 0) { + // If the base URL already has an anchor, it needs to be + // removed first. If there's no anchor, we need to add "#" + int pos = url.lastIndexOf('#'); + if (pos < 0) { + url += "#"; //$NON-NLS-1$ + } else if (pos < url.length() - 1) { + url = url.substring(0, pos + 1); + } + + url += anchor; + } + } else if (text == null || text.length() == 0) { + // If we still have no text, use the base as text + text = base; + } + } + + if (url != null && text != null) { + sb.append(""); //$NON-NLS-1$ + sb.append(text); + sb.append(""); //$NON-NLS-1$ + } else if (text != null) { + sb.append("").append(text).append(""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + } else if (ELEM.equals(s)) { + s = spans.get(++i); + if (sdkUrl != null && s != null) { + sb.append(""); //$NON-NLS-1$ + sb.append(s); + sb.append(""); //$NON-NLS-1$ + } else if (s != null) { + sb.append("").append(s).append(""); //$NON-NLS-1$ //$NON-NLS-2$ + } + } else if (BREAK.equals(s)) { + // ignore line breaks in pseudo-HTML rendering + } else if (s != null) { + sb.append(s); + } + } + + if (icon != null) { + sb.append("

        • "); //$NON-NLS-1$ + } else { + sb.append("

          "); //$NON-NLS-1$ + } + return sb.toString(); + } + + private static ArrayList scanJavadoc(String javadoc) { + ArrayList spans = new ArrayList(); + + // Standardize all whitespace in the javadoc to single spaces. + if (javadoc != null) { + javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + + // Detects {@link # } where all 3 are optional + Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$ + // Detects blah + Pattern p_code = Pattern.compile("(.+?)(.*)"); //$NON-NLS-1$ + // Detects @blah@, used in hard-coded tooltip descriptors + Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$ + // Detects a buffer that starts by @ < or { (one that was not matched above) + Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$ + // Detects everything till the next potential separator, i.e. @ < or { + Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$ + + int currentLength = 0; + String text = null; + + while(javadoc != null && javadoc.length() > 0) { + Matcher m; + String s = null; + if ((m = p_code.matcher(javadoc)).matches()) { + spans.add(CODE); + spans.add(text = cleanupJavadocHtml(m.group(1))); // text + javadoc = m.group(2); + if (text != null) { + currentLength += text.length(); + } + } else if ((m = p_link.matcher(javadoc)).matches()) { + spans.add(LINK); + spans.add(m.group(1)); // @link base + spans.add(m.group(2)); // @link anchor + spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text + javadoc = m.group(4); + if (text != null) { + currentLength += text.length(); + } + } else if ((m = p_elem.matcher(javadoc)).matches()) { + spans.add(ELEM); + spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@ + javadoc = m.group(2); + if (text != null) { + currentLength += text.length() - 2; + } + } else if ((m = p_open.matcher(javadoc)).matches()) { + s = m.group(1); + javadoc = m.group(2); + } else if ((m = p_text.matcher(javadoc)).matches()) { + s = m.group(1); + javadoc = m.group(2); + } else { + // This is not supposed to happen. In case of, just use everything. + s = javadoc; + javadoc = null; + } + if (s != null && s.length() > 0) { + s = cleanupJavadocHtml(s); + + if (currentLength >= JAVADOC_BREAK_LENGTH) { + spans.add(BREAK); + currentLength = 0; + } + while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) { + int pos = s.indexOf(' ', JAVADOC_BREAK_LENGTH - currentLength); + if (pos <= 0) { + break; + } + spans.add(s.substring(0, pos + 1)); + spans.add(BREAK); + currentLength = 0; + s = s.substring(pos + 1); + } + + spans.add(s); + currentLength += s.length(); + } + } + + return spans; + } + + /** + * Remove anything that looks like HTML from a javadoc snippet, as it is supported + * neither by FormText nor a standard text tooltip. + */ + private static String cleanupJavadocHtml(String s) { + if (s != null) { + s = s.replaceAll("<", "\""); //$NON-NLS-1$ $NON-NLS-2$ + s = s.replaceAll(">", "\""); //$NON-NLS-1$ $NON-NLS-2$ + s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$ + } + return s; + } + + /** + * Sets the default layout attributes for the a new UiElementNode. + *

          + * Note that ideally the node should already be part of a hierarchy so that its + * parent layout and previous sibling can be determined, if any. + *

          + * This does not override attributes which are not empty. + */ + public static void setDefaultLayoutAttributes(UiElementNode ui_node, boolean updateLayout) { + // if this ui_node is a layout and we're adding it to a document, use fill_parent for + // both W/H. Otherwise default to wrap_layout. + boolean fill = ui_node.getDescriptor().hasChildren() && + ui_node.getUiParent() instanceof UiDocumentNode; + ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_WIDTH, + fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT, + false /* override */); + ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_HEIGHT, + fill ? LayoutConstants.VALUE_FILL_PARENT : LayoutConstants.VALUE_WRAP_CONTENT, + false /* override */); + + String widget_id = getFreeWidgetId(ui_node.getUiRoot(), + new Object[] { ui_node.getDescriptor().getXmlLocalName(), null, null, null }); + if (widget_id != null) { + ui_node.setAttributeValue(LayoutConstants.ATTR_ID, "@+id/" + widget_id, //$NON-NLS-1$ + false /* override */); + } + + ui_node.setAttributeValue(LayoutConstants.ATTR_TEXT, widget_id, false /*override*/); + + if (updateLayout) { + UiElementNode ui_parent = ui_node.getUiParent(); + if (ui_parent != null && + ui_parent.getDescriptor().getXmlLocalName().equals( + LayoutConstants.RELATIVE_LAYOUT)) { + UiElementNode ui_previous = ui_node.getUiPreviousSibling(); + if (ui_previous != null) { + String id = ui_previous.getAttributeValue(LayoutConstants.ATTR_ID); + if (id != null && id.length() > 0) { + id = id.replace("@+", "@"); //$NON-NLS-1$ //$NON-NLS-2$ + ui_node.setAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW, id, + false /* override */); + } + } + } + } + } + + /** + * Given a UI root node, returns the first available id that matches the + * pattern "prefix%02d". + * + * @param uiNode The UI node that gives the prefix to match. + * @return A suitable generated id + */ + public static String getFreeWidgetId(UiElementNode uiNode) { + return getFreeWidgetId(uiNode.getUiRoot(), + new Object[] { uiNode.getDescriptor().getXmlLocalName(), null, null, null }); + } + + /** + * Given a UI root node, returns the first available id that matches the + * pattern "prefix%02d". + * + * For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters + * in methods and we're not going to do a dedicated type, we just use an object array which + * must contain one initial item and several are built on the fly just for internal storage: + *

            + *
          • prefix(String): The prefix of the generated id, i.e. "widget". Cannot be null. + *
          • index(Integer): The minimum index of the generated id. Must start with null. + *
          • generated(String): The generated widget currently being searched. Must start with null. + *
          • map(Set): A set of the ids collected so far when walking through the widget + * hierarchy. Must start with null. + *
          + * + * @param uiRoot The Ui root node where to start searching recursively. For the initial call + * you want to pass the document root. + * @param params An in-out context of parameters used during recursion, as explained above. + * @return A suitable generated id + */ + @SuppressWarnings("unchecked") + private static String getFreeWidgetId(UiElementNode uiRoot, + Object[] params) { + + Set map = (Set)params[3]; + if (map == null) { + params[3] = map = new HashSet(); + } + + int num = params[1] == null ? 0 : ((Integer)params[1]).intValue(); + + String generated = (String) params[2]; + String prefix = (String) params[0]; + if (generated == null) { + int pos = prefix.indexOf('.'); + if (pos >= 0) { + prefix = prefix.substring(pos + 1); + } + pos = prefix.indexOf('$'); + if (pos >= 0) { + prefix = prefix.substring(pos + 1); + } + prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$ + if (prefix.length() == 0) { + prefix = DEFAULT_WIDGET_PREFIX; + } + + do { + num++; + generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$ + } while (map.contains(generated)); + + params[0] = prefix; + params[1] = num; + params[2] = generated; + } + + String id = uiRoot.getAttributeValue(LayoutConstants.ATTR_ID); + if (id != null) { + id = id.replace("@+id/", ""); //$NON-NLS-1$ $NON-NLS-2$ + id = id.replace("@id/", ""); //$NON-NLS-1$ $NON-NLS-2$ + if (map.add(id) && map.contains(generated)) { + + do { + num++; + generated = String.format("%1$s%2$02d", prefix, num); //$NON-NLS-1$ + } while (map.contains(generated)); + + params[1] = num; + params[2] = generated; + } + } + + for (UiElementNode uiChild : uiRoot.getUiChildren()) { + getFreeWidgetId(uiChild, params); + } + + // Note: return params[2] (not "generated") since it could have changed during recursion. + return (String) params[2]; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java new file mode 100644 index 0000000..7d296f7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java @@ -0,0 +1,57 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * {@link DocumentDescriptor} describes the properties expected for an XML document node. + * + * Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name, + * tooltip, SDK url and attributes list. + *

          + * It has a children list which represent all the possible roots of the document. + *

          + * The document nodes are "mandatory", meaning the UI node is never deleted and it may lack + * an actual XML node attached. + */ +public class DocumentDescriptor extends ElementDescriptor { + + /** + * Constructs a new {@link DocumentDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + *

          + * The XML name is never shown in the UI directly. It is however used when an icon + * needs to be found for the node. + * + * @param xml_name The XML element node name. Case sensitive. + * @param children The list of allowed children. Can be null or empty. + */ + public DocumentDescriptor(String xml_name, ElementDescriptor[] children) { + super(xml_name, children, true /* mandatory */); + } + + /** + * @return A new {@link UiElementNode} linked to this descriptor. + */ + @Override + public UiElementNode createUiNode() { + return new UiDocumentNode(this); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java new file mode 100644 index 0000000..7d7b1c9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; + +import java.util.HashSet; +import java.util.Set; + +/** + * {@link ElementDescriptor} describes the properties expected for a given XML element node. + * + * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url, + * an attributes list and a children list. + * + * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack + * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached + * and it will cease to exist when the XML node ceases to exist. + */ +public class ElementDescriptor { + /** The XML element node name. Case sensitive. */ + private String mXmlName; + /** The XML element name for the user interface, typically capitalized. */ + private String mUiName; + /** The list of allowed attributes. */ + private AttributeDescriptor[] mAttributes; + /** The list of allowed children */ + private ElementDescriptor[] mChildren; + /* An optional tooltip. Can be empty. */ + private String mTooltip; + /** An optional SKD URL. Can be empty. */ + private String mSdkUrl; + /** Whether this UI node must always exist (even for empty models). */ + private boolean mMandatory; + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, + * tooltip, SDK url, attributes list, children list and mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + boolean mandatory) { + mMandatory = mandatory; + mXmlName = xml_name; + mUiName = ui_name; + mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; + mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; + setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); + mChildren = children != null ? children : new ElementDescriptor[]{}; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) { + this(xml_name, prettyName(xml_name), null, null, null, children, mandatory); + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param children The list of allowed children. Can be null or empty. + */ + public ElementDescriptor(String xml_name, ElementDescriptor[] children) { + this(xml_name, prettyName(xml_name), null, null, null, children, false); + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + */ + public ElementDescriptor(String xml_name) { + this(xml_name, prettyName(xml_name), null, null, null, null, false); + } + + /** Returns whether this node must always exist (even for empty models) */ + public boolean isMandatory() { + return mMandatory; + } + + /** + * Returns the XML element node local name (case sensitive) + */ + public final String getXmlLocalName() { + int pos = mXmlName.indexOf(':'); + if (pos != -1) { + return mXmlName.substring(pos+1); + } + return mXmlName; + } + + /** Returns the XML element node name. Case sensitive. */ + public String getXmlName() { + return mXmlName; + } + + /** + * Returns the namespace of the attribute. + */ + public final String getNamespace() { + // For now we hard-code the prefix as being "android" + if (mXmlName.startsWith("android:")) { //$NON-NLs-1$ + return AndroidConstants.NS_RESOURCES; + } + + return ""; //$NON-NLs-1$ + } + + + /** Returns the XML element name for the user interface, typically capitalized. */ + public String getUiName() { + return mUiName; + } + + /** + * Returns an optional icon for the element. + *

          + * By default this tries to return an icon based on the XML name of the element. + * If this fails, it tries to return the default Android logo as defined in the + * plugin. If all fails, it returns null. + * + * @return An icon for this element or null. + */ + public Image getIcon() { + IconFactory factory = IconFactory.getInstance(); + int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; + int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; + Image icon = factory.getIcon(mXmlName, color, shape); + return icon != null ? icon : AdtPlugin.getAndroidLogo(); + } + + /** + * Returns an optional ImageDescriptor for the element. + *

          + * By default this tries to return an image based on the XML name of the element. + * If this fails, it tries to return the default Android logo as defined in the + * plugin. If all fails, it returns null. + * + * @return An ImageDescriptor for this element or null. + */ + public ImageDescriptor getImageDescriptor() { + IconFactory factory = IconFactory.getInstance(); + int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; + int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; + ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape); + return id != null ? id : AdtPlugin.getAndroidLogoDesc(); + } + + /* Returns the list of allowed attributes. */ + public AttributeDescriptor[] getAttributes() { + return mAttributes; + } + + /* Sets the list of allowed attributes. */ + public void setAttributes(AttributeDescriptor[] attributes) { + mAttributes = attributes; + for (AttributeDescriptor attribute : attributes) { + attribute.setParent(this); + } + } + + /** Returns the list of allowed children */ + public ElementDescriptor[] getChildren() { + return mChildren; + } + + /** @return True if this descriptor has children available */ + public boolean hasChildren() { + return mChildren.length > 0; + } + + /** Sets the list of allowed children. */ + public void setChildren(ElementDescriptor[] newChildren) { + mChildren = newChildren; + } + + /** + * Returns an optional tooltip. Will be null if not present. + *

          + * The tooltip is based on the Javadoc of the element and already processed via + * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as + * a UI tooltip. + */ + public String getTooltip() { + return mTooltip; + } + + /** Returns an optional SKD URL. Will be null if not present. */ + public String getSdkUrl() { + return mSdkUrl; + } + + /** Sets the optional tooltip. Can be null or empty. */ + public void setTooltip(String tooltip) { + mTooltip = tooltip; + } + + /** Sets the optional SDK URL. Can be null or empty. */ + public void setSdkUrl(String sdkUrl) { + mSdkUrl = sdkUrl; + } + + /** + * @return A new {@link UiElementNode} linked to this descriptor. + */ + public UiElementNode createUiNode() { + return new UiElementNode(this); + } + + /** + * Returns the first children of this descriptor that describes the given XML element name. + *

          + * In recursive mode, searches the direct children first before descending in the hierarchy. + * + * @return The ElementDescriptor matching the requested XML node element name or null. + */ + public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) { + return findChildrenDescriptorInternal(element_name, recursive, null); + } + + private ElementDescriptor findChildrenDescriptorInternal(String element_name, + boolean recursive, + Set visited) { + if (recursive && visited == null) { + visited = new HashSet(); + } + + for (ElementDescriptor e : getChildren()) { + if (e.getXmlName().equals(element_name)) { + return e; + } + } + + if (visited != null) { + visited.add(this); + } + + if (recursive) { + for (ElementDescriptor e : getChildren()) { + if (visited != null) { + if (!visited.add(e)) { // Set.add() returns false if element is already present + continue; + } + } + ElementDescriptor f = e.findChildrenDescriptorInternal(element_name, + recursive, visited); + if (f != null) { + return f; + } + } + } + + return null; + } + + /** + * Utility helper than pretty-formats an XML Name for the UI. + * This is used by the simplified constructor that takes only an XML element name. + * + * @param xml_name The XML name to convert. + * @return The XML name with dashes replaced by spaces and capitalized. + */ + private static String prettyName(String xml_name) { + char c[] = xml_name.toCharArray(); + if (c.length > 0) { + c[0] = Character.toUpperCase(c[0]); + } + return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java new file mode 100644 index 0000000..cab9883 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; + +/** + * Describes a text attribute that can only contains some predefined values. + * It is displayed by a {@link UiListAttributeNode}. + */ +public class EnumAttributeDescriptor extends ListAttributeDescriptor { + + public EnumAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * @return A new {@link UiListAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiListAttributeNode(this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java new file mode 100644 index 0000000..903417b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java @@ -0,0 +1,85 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.editors.ui.FlagValueCellEditor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.swt.widgets.Composite; + +/** + * Describes a text attribute that can only contains some predefined values. + * It is displayed by a {@link UiListAttributeNode}. + * + * Note: in Android resources, a "flag" is a list of fixed values where one or + * more values can be selected using an "or", e.g. "align='left|top'". + * By contrast, an "enum" is a list of fixed values of which only one can be + * selected at a given time, e.g. "gravity='right'". + *

          + * This class handles the "flag" case. + * The "enum" case is done using {@link ListAttributeDescriptor}. + */ +public class FlagAttributeDescriptor extends TextAttributeDescriptor { + + private String[] mNames; + + /** + * Creates a new {@link FlagAttributeDescriptor} which automatically gets its + * values from the FrameworkResourceManager. + */ + public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * Creates a new {@link FlagAttributeDescriptor} which uses the provided values. + */ + public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip, String[] names) { + super(xmlLocalName, uiName, nsUri, tooltip); + mNames = names; + } + + /** + * @return The initial names of the flags. Can be null, in which case the Framework + * resource parser should be checked. + */ + public String[] getNames() { + return mNames; + } + + /** + * @return A new {@link UiListAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiFlagAttributeNode(this, uiParent); + } + + // ------- IPropertyDescriptor Methods + + @Override + public CellEditor createPropertyEditor(Composite parent) { + return new FlagValueCellEditor(parent); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/IDescriptorProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/IDescriptorProvider.java new file mode 100644 index 0000000..4c115e9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/IDescriptorProvider.java @@ -0,0 +1,24 @@ +/* + * 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.editors.descriptors; + +public interface IDescriptorProvider { + + ElementDescriptor[] getRootElementDescriptors(); + + ElementDescriptor getDescriptor(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java new file mode 100644 index 0000000..93969e9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.editors.ui.ListValueCellEditor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.swt.widgets.Composite; + +/** + * Describes a text attribute that can contains some predefined values. + * It is displayed by a {@link UiListAttributeNode}. + */ +public class ListAttributeDescriptor extends TextAttributeDescriptor { + + private String[] mValues = null; + + /** + * Creates a new {@link ListAttributeDescriptor} which automatically gets its + * values from the FrameworkResourceManager. + */ + public ListAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * Creates a new {@link ListAttributeDescriptor} which uses the provided values. + */ + public ListAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip, String[] values) { + super(xmlLocalName, uiName, nsUri, tooltip); + mValues = values; + } + + public String[] getValues() { + return mValues; + } + + /** + * @return A new {@link UiListAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiListAttributeNode(this, uiParent); + } + + // ------- IPropertyDescriptor Methods + + @Override + public CellEditor createPropertyEditor(Composite parent) { + return new ListValueCellEditor(parent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java new file mode 100644 index 0000000..3d3ff29 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java @@ -0,0 +1,84 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.ui.ResourceValueCellEditor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.swt.widgets.Composite; + +/** + * Describes an XML attribute displayed containing a value or a reference to a resource. + * It is displayed by a {@link UiResourceAttributeNode}. + */ +public final class ReferenceAttributeDescriptor extends TextAttributeDescriptor { + + private ResourceType mResourceType; + + /** + * Creates a reference attributes that can contain any type of resources. + * @param xmlLocalName The XML name of the attribute (case sensitive) + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip A non-empty tooltip string or null + */ + public ReferenceAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * Creates a reference attributes that can contain a reference to a specific + * {@link ResourceType}. + * @param resourceType The specific {@link ResourceType} that this reference attribute supports. + * It can be null, in which case, all resource types are supported. + * @param xmlLocalName The XML name of the attribute (case sensitive) + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip A non-empty tooltip string or null + */ + public ReferenceAttributeDescriptor(ResourceType resourceType, + String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + mResourceType = resourceType; + } + + + /** + * @return A new {@link UiResourceAttributeNode} linked to this reference descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiResourceAttributeNode(mResourceType, this, uiParent); + } + + // ------- IPropertyDescriptor Methods + + @Override + public CellEditor createPropertyEditor(Composite parent) { + return new ResourceValueCellEditor(parent); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java new file mode 100644 index 0000000..8fb1c7c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java @@ -0,0 +1,45 @@ +/* + * 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiSeparatorAttributeNode; + +/** + * {@link SeparatorAttributeDescriptor} does not represent any real attribute. + *

          + * It is used to separate groups of attributes visually. + */ +public class SeparatorAttributeDescriptor extends AttributeDescriptor { + + /** + * Creates a new {@link SeparatorAttributeDescriptor} + */ + public SeparatorAttributeDescriptor(String label) { + super(label /* xmlLocalName */, null /* nsUri */); + } + + /** + * @return A new {@link UiAttributeNode} linked to this descriptor or null if this + * attribute has no user interface. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiSeparatorAttributeNode(this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java new file mode 100644 index 0000000..632471d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.ui.TextValueCellEditor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; + +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.views.properties.IPropertyDescriptor; + + +/** + * Describes a textual XML attribute. + *

          + * Such an attribute has a tooltip and would typically be displayed by + * {@link UiTextAttributeNode} using a label widget and text field. + *

          + * This is the "default" kind of attribute. If in doubt, use this. + */ +public class TextAttributeDescriptor extends AttributeDescriptor implements IPropertyDescriptor { + private String mUiName; + private String mTooltip; + + /** + * Creates a new {@link TextAttributeDescriptor} + * + * @param xmlLocalName The XML name of the attribute (case sensitive) + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip A non-empty tooltip string or null + */ + public TextAttributeDescriptor(String xmlLocalName, String uiName, + String nsUri, String tooltip) { + super(xmlLocalName, nsUri); + mUiName = uiName; + mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; + } + + /** + * @return The UI name of the attribute. Cannot be an empty string and cannot be null. + */ + public final String getUiName() { + return mUiName; + } + + /** + * The tooltip string is either null or a non-empty string. + *

          + * The tooltip is based on the Javadoc of the attribute and already processed via + * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as + * a UI tooltip. + *

          + * An empty string is converted to null, to match the behavior of setToolTipText() in + * {@link Control}. + * + * @return A non-empty tooltip string or null + */ + public final String getTooltip() { + return mTooltip; + } + + /** + * @return A new {@link UiTextAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiTextAttributeNode(this, uiParent); + } + + // ------- IPropertyDescriptor Methods + + public CellEditor createPropertyEditor(Composite parent) { + return new TextValueCellEditor(parent); + } + + public String getCategory() { + ElementDescriptor parent = getParent(); + if (parent != null) { + return parent.getUiName(); + } + + return null; + } + + public String getDescription() { + return mTooltip; + } + + public String getDisplayName() { + return mUiName; + } + + public String[] getFilterFlags() { + return null; + } + + public Object getHelpContextIds() { + return null; + } + + public Object getId() { + return this; + } + + public ILabelProvider getLabelProvider() { + return AttributeDescriptorLabelProvider.getProvider(); + } + + public boolean isCompatibleWith(IPropertyDescriptor anotherProperty) { + return anotherProperty == this; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java new file mode 100644 index 0000000..2015d71 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiTextValueNode; + + +/** + * Describes the value of an XML element. + *

          + * The value is a simple text string, displayed by an {@link UiTextValueNode}. + */ +public class TextValueDescriptor extends TextAttributeDescriptor { + + /** + * Creates a new {@link TextValueDescriptor} + * + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param tooltip A non-empty tooltip string or null + */ + public TextValueDescriptor(String uiName, String tooltip) { + super("#text" /* xmlLocalName */, uiName, null /* nsUri */, tooltip); + } + + /** + * @return A new {@link UiTextValueNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiTextValueNode(this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java new file mode 100644 index 0000000..ed9c897 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2007 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.editors.descriptors; + +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + + +/** + * Describes an XMLNS attribute that is hidden. + *

          + * Such an attribute has no user interface and no corresponding {@link UiAttributeNode}. + * It also has a single constant default value. + *

          + * When loading an XML, we'll ignore this attribute. + * However when writing a new XML, we should always write this attribute. + *

          + * Currently this is used for the xmlns:android attribute in the manifest element. + */ +public final class XmlnsAttributeDescriptor extends AttributeDescriptor { + + /** + * URI of the reserved "xmlns" prefix, as described in + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#normalizeDocumentAlgo + */ + public final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$ + + private String mValue; + + + public XmlnsAttributeDescriptor(String defaultPrefix, String value) { + super(defaultPrefix, XMLNS_URI); + mValue = value; + } + + /** + * Returns the value of this specialized attribute descriptor, which is the URI associated + * to the declared namespace prefix. + */ + public String getValue() { + return mValue; + } + + /** + * Returns the "xmlns" prefix that is always used by this node for its namespace URI. + * This is defined by the XML specification. + */ + public String getXmlNsPrefix() { + return "xmlns"; //$NON-NLS-1$ + } + + /** + * Returns the fully-qualified attribute name, namely "xmlns:xxx" where xxx is + * the defaultPrefix passed in the constructor. + */ + public String getXmlNsName() { + return getXmlNsPrefix() + ":" + getXmlLocalName(); //$NON-NLS-1$ + } + + /** + * @return Always returns null. {@link XmlnsAttributeDescriptor} has no user interface. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java new file mode 100644 index 0000000..c512625 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/BasePullParser.java @@ -0,0 +1,220 @@ +/* + * 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.editors.layout; + +import com.android.layoutlib.api.IXmlPullParser; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * Base implementation of an {@link IXmlPullParser} for cases where the parser is not sitting + * on top of an actual XML file. + *

          It's designed to work on layout files, and will most likely not work on other resource + * files. + */ +public abstract class BasePullParser implements IXmlPullParser { + + protected int mParsingState = START_DOCUMENT; + + public BasePullParser() { + } + + // --- new methods to override --- + + public abstract void onNextFromStartDocument(); + public abstract void onNextFromStartTag(); + public abstract void onNextFromEndTag(); + + // --- basic implementation of IXmlPullParser --- + + public void setFeature(String name, boolean state) throws XmlPullParserException { + if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { + return; + } + if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { + return; + } + throw new XmlPullParserException("Unsupported feature: " + name); + } + + public boolean getFeature(String name) { + if (FEATURE_PROCESS_NAMESPACES.equals(name)) { + return true; + } + if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) { + return true; + } + return false; + } + + public void setProperty(String name, Object value) throws XmlPullParserException { + throw new XmlPullParserException("setProperty() not supported"); + } + + public Object getProperty(String name) { + return null; + } + + public void setInput(Reader in) throws XmlPullParserException { + throw new XmlPullParserException("setInput() not supported"); + } + + public void setInput(InputStream inputStream, String inputEncoding) + throws XmlPullParserException { + throw new XmlPullParserException("setInput() not supported"); + } + + public void defineEntityReplacementText(String entityName, String replacementText) + throws XmlPullParserException { + throw new XmlPullParserException("defineEntityReplacementText() not supported"); + } + + public String getNamespacePrefix(int pos) throws XmlPullParserException { + throw new XmlPullParserException("getNamespacePrefix() not supported"); + } + + public String getInputEncoding() { + return null; + } + + public String getNamespace(String prefix) { + throw new RuntimeException("getNamespace() not supported"); + } + + public int getNamespaceCount(int depth) throws XmlPullParserException { + throw new XmlPullParserException("getNamespaceCount() not supported"); + } + + public String getNamespaceUri(int pos) throws XmlPullParserException { + throw new XmlPullParserException("getNamespaceUri() not supported"); + } + + public int getColumnNumber() { + return -1; + } + + public int getLineNumber() { + return -1; + } + + public String getAttributeType(int arg0) { + return "CDATA"; + } + + public int getEventType() { + return mParsingState; + } + + public String getText() { + return null; + } + + public char[] getTextCharacters(int[] arg0) { + return null; + } + + public boolean isAttributeDefault(int arg0) { + return false; + } + + public boolean isWhitespace() { + return false; + } + + public int next() throws XmlPullParserException { + switch (mParsingState) { + case END_DOCUMENT: + throw new XmlPullParserException("Nothing after the end"); + case START_DOCUMENT: + onNextFromStartDocument(); + break; + case START_TAG: + onNextFromStartTag(); + break; + case END_TAG: + onNextFromEndTag(); + break; + case TEXT: + // not used + break; + case CDSECT: + // not used + break; + case ENTITY_REF: + // not used + break; + case IGNORABLE_WHITESPACE: + // not used + break; + case PROCESSING_INSTRUCTION: + // not used + break; + case COMMENT: + // not used + break; + case DOCDECL: + // not used + break; + } + + return mParsingState; + } + + public int nextTag() throws XmlPullParserException, IOException { + int eventType = next(); + if (eventType != START_TAG && eventType != END_TAG) { + throw new XmlPullParserException("expected start or end tag", this, null); + } + return eventType; + } + + public String nextText() throws XmlPullParserException, IOException { + if (getEventType() != START_TAG) { + throw new XmlPullParserException("parser must be on START_TAG to read next text", this, + null); + } + int eventType = next(); + if (eventType == TEXT) { + String result = getText(); + eventType = next(); + if (eventType != END_TAG) { + throw new XmlPullParserException( + "event TEXT it must be immediately followed by END_TAG", this, null); + } + return result; + } else if (eventType == END_TAG) { + return ""; + } else { + throw new XmlPullParserException("parser must be on START_TAG or TEXT to read text", + this, null); + } + } + + public int nextToken() throws XmlPullParserException, IOException { + return next(); + } + + public void require(int type, String namespace, String name) throws XmlPullParserException { + if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace())) + || (name != null && !name.equals(getName()))) + throw new XmlPullParserException("expected " + TYPES[type] + getPositionDescription()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java new file mode 100644 index 0000000..77467cd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java @@ -0,0 +1,2355 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.LoadStatus; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData.LayoutBridge; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions; +import com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; +import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.editors.layout.parts.ElementCreateCommand; +import com.android.ide.eclipse.editors.layout.parts.UiElementEditPart; +import com.android.ide.eclipse.editors.layout.parts.UiElementsEditPartFactory; +import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier; +import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; +import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier; +import com.android.ide.eclipse.editors.resources.configurations.NetworkCodeQualifier; +import com.android.ide.eclipse.editors.resources.configurations.PixelDensityQualifier; +import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; +import com.android.ide.eclipse.editors.resources.configurations.ScreenDimensionQualifier; +import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier; +import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier; +import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier; +import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier.KeyboardState; +import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier.NavigationMethod; +import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier.ScreenOrientation; +import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier.TextInputMethod; +import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier.TouchScreenType; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.ide.eclipse.editors.resources.manager.ResourceFile; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.ui.tree.CopyCutAction; +import com.android.ide.eclipse.editors.ui.tree.PasteAction; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DensityVerifier; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DimensionVerifier; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.LanguageRegionVerifier; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.MobileCodeVerifier; +import com.android.layoutlib.api.ILayoutLog; +import com.android.layoutlib.api.ILayoutResult; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.api.IStyleResourceValue; +import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; +import com.android.sdklib.IAndroidTarget; + +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.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.gef.DefaultEditDomain; +import org.eclipse.gef.EditPart; +import org.eclipse.gef.EditPartViewer; +import org.eclipse.gef.GraphicalViewer; +import org.eclipse.gef.SelectionManager; +import org.eclipse.gef.dnd.TemplateTransferDragSourceListener; +import org.eclipse.gef.dnd.TemplateTransferDropTargetListener; +import org.eclipse.gef.editparts.ScalableFreeformRootEditPart; +import org.eclipse.gef.palette.PaletteRoot; +import org.eclipse.gef.requests.CreationFactory; +import org.eclipse.gef.ui.parts.GraphicalEditorWithPalette; +import org.eclipse.gef.ui.parts.SelectionSynchronizer; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.FileEditorInput; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.awt.image.Raster; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Graphical layout editor, based on GEF. + *

          + * To understand GEF: http://www.ibm.com/developerworks/opensource/library/os-gef/ + *

          + * To understand Drag'n'drop: http://www.eclipse.org/articles/Article-Workbench-DND/drag_drop.html + */ +public class GraphicalLayoutEditor extends GraphicalEditorWithPalette + implements ILayoutReloadListener { + + private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$ + + /** Reference to the layout editor */ + private final LayoutEditor mLayoutEditor; + + /** reference to the file being edited. */ + private IFile mEditedFile; + + private Clipboard mClipboard; + private Composite mParent; + private PaletteRoot mPaletteRoot; + + private Text mCountry; + private Text mNetwork; + private Combo mLanguage; + private Combo mRegion; + private Combo mOrientation; + private Text mDensity; + private Combo mTouch; + private Combo mKeyboard; + private Combo mTextInput; + private Combo mNavigation; + private Text mSize1; + private Text mSize2; + private Combo mThemeCombo; + private Button mCreateButton; + + private Label mCountryIcon; + private Label mNetworkIcon; + private Label mLanguageIcon; + private Label mRegionIcon; + private Label mOrientationIcon; + private Label mDensityIcon; + private Label mTouchIcon; + private Label mKeyboardIcon; + private Label mTextInputIcon; + private Label mNavigationIcon; + private Label mSizeIcon; + + private Label mCurrentLayoutLabel; + + private Image mWarningImage; + private Image mMatchImage; + private Image mErrorImage; + + /** The {@link FolderConfiguration} representing the state of the UI controls */ + private FolderConfiguration mCurrentConfig = new FolderConfiguration(); + /** The {@link FolderConfiguration} being edited. */ + private FolderConfiguration mEditedConfig; + + private Map> mConfiguredFrameworkRes; + private Map> mConfiguredProjectRes; + private ProjectCallback mProjectCallback; + private ILayoutLog mLogger; + + private boolean mNeedsRecompute = false; + private int mPlatformThemeCount = 0; + private boolean mDisableUpdates = false; + private boolean mActive = false; + + private Runnable mFrameworkResourceChangeListener = new Runnable() { + public void run() { + // because the SDK changed we must reset the configured framework resource. + mConfiguredFrameworkRes = null; + + updateUIFromResources(); + + mThemeCombo.getParent().layout(); + + // updateUiFromFramework will reset language/region combo, so we must call + // setConfiguration after, or the settext on language/region will be lost. + if (mEditedConfig != null) { + setConfiguration(mEditedConfig); + } + + // make sure we remove the custom view loader, since its parent class loader is the + // bridge class loader. + mProjectCallback = null; + + recomputeLayout(); + } + }; + + private final Runnable mConditionalRecomputeRunnable = new Runnable() { + public void run() { + if (mActive) { + recomputeLayout(); + } else { + mNeedsRecompute = true; + } + } + }; + + private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() { + public void run() { + updateUIFromResources(); + mThemeCombo.getParent().layout(); + } + }; + + public GraphicalLayoutEditor(LayoutEditor layoutEditor) { + mLayoutEditor = layoutEditor; + setEditDomain(new DefaultEditDomain(this)); + setPartName("Layout"); + + IconFactory factory = IconFactory.getInstance(); + mWarningImage = factory.getIcon("warning"); //$NON-NLS-1$ + mMatchImage = factory.getIcon("match"); //$NON-NLS-1$ + mErrorImage = factory.getIcon("error"); //$NON-NLS-1$ + + AdtPlugin.getDefault().addResourceChangedListener(mFrameworkResourceChangeListener); + } + + // ------------------------------------ + // Methods overridden from base classes + //------------------------------------ + + @Override + public void createPartControl(Composite parent) { + mParent = parent; + GridLayout gl; + GridData gd; + + mClipboard = new Clipboard(parent.getDisplay()); + + parent.setLayout(gl = new GridLayout(1, false)); + gl.marginHeight = gl.marginWidth = 0; + + // create the top part for the configuration control + int cols = 10; + + Composite topParent = new Composite(parent, SWT.NONE); + topParent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + topParent.setLayout(gl = new GridLayout(cols, false)); + + new Label(topParent, SWT.NONE).setText("MCC"); + mCountryIcon = createControlComposite(topParent, true /* grab_horizontal */); + mCountry = new Text(mCountryIcon.getParent(), SWT.BORDER); + mCountry.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mCountry.addVerifyListener(new MobileCodeVerifier()); + mCountry.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onCountryCodeChange(); + } + }); + mCountry.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onCountryCodeChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("MNC"); + mNetworkIcon = createControlComposite(topParent, true /* grab_horizontal */); + mNetwork = new Text(mNetworkIcon.getParent(), SWT.BORDER); + mNetwork.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mNetwork.addVerifyListener(new MobileCodeVerifier()); + mNetwork.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onNetworkCodeChange(); + } + }); + mNetwork.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onNetworkCodeChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Lang"); + mLanguageIcon = createControlComposite(topParent, true /* grab_horizontal */); + mLanguage = new Combo(mLanguageIcon.getParent(), SWT.DROP_DOWN); + mLanguage.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mLanguage.addVerifyListener(new LanguageRegionVerifier()); + mLanguage.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onLanguageChange(); + } + public void widgetSelected(SelectionEvent e) { + onLanguageChange(); + } + }); + mLanguage.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onLanguageChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Region"); + mRegionIcon = createControlComposite(topParent, true /* grab_horizontal */); + mRegion = new Combo(mRegionIcon.getParent(), SWT.DROP_DOWN); + mRegion.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mRegion.addVerifyListener(new LanguageRegionVerifier()); + mRegion.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onRegionChange(); + } + public void widgetSelected(SelectionEvent e) { + onRegionChange(); + } + }); + mRegion.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onRegionChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Orient"); + mOrientationIcon = createControlComposite(topParent, true /* grab_horizontal */); + mOrientation = new Combo(mOrientationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); + ScreenOrientation[] soValues = ScreenOrientation.values(); + mOrientation.add("(Default)"); + for (ScreenOrientation value : soValues) { + mOrientation.add(value.getDisplayValue()); + } + mOrientation.select(0); + mOrientation.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mOrientation.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onOrientationChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Density"); + mDensityIcon = createControlComposite(topParent, true /* grab_horizontal */); + mDensity = new Text(mDensityIcon.getParent(), SWT.BORDER); + mDensity.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mDensity.addVerifyListener(new DensityVerifier()); + mDensity.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + onDensityChange(); + } + }); + mDensity.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onDensityChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Touch"); + mTouchIcon = createControlComposite(topParent, true /* grab_horizontal */); + mTouch = new Combo(mTouchIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); + TouchScreenType[] tstValues = TouchScreenType.values(); + mTouch.add("(Default)"); + for (TouchScreenType value : tstValues) { + mTouch.add(value.getDisplayValue()); + } + mTouch.select(0); + mTouch.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mTouch.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onTouchChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Keybrd"); + mKeyboardIcon = createControlComposite(topParent, true /* grab_horizontal */); + mKeyboard = new Combo(mKeyboardIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); + KeyboardState[] ksValues = KeyboardState.values(); + mKeyboard.add("(Default)"); + for (KeyboardState value : ksValues) { + mKeyboard.add(value.getDisplayValue()); + } + mKeyboard.select(0); + mKeyboard.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mKeyboard.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onKeyboardChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Input"); + mTextInputIcon = createControlComposite(topParent, true /* grab_horizontal */); + mTextInput = new Combo(mTextInputIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); + TextInputMethod[] timValues = TextInputMethod.values(); + mTextInput.add("(Default)"); + for (TextInputMethod value : timValues) { + mTextInput.add(value.getDisplayValue()); + } + mTextInput.select(0); + mTextInput.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mTextInput.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onTextInputChange(); + } + }); + + new Label(topParent, SWT.NONE).setText("Nav"); + mNavigationIcon = createControlComposite(topParent, true /* grab_horizontal */); + mNavigation = new Combo(mNavigationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); + NavigationMethod[] nValues = NavigationMethod.values(); + mNavigation.add("(Default)"); + for (NavigationMethod value : nValues) { + mNavigation.add(value.getDisplayValue()); + } + mNavigation.select(0); + mNavigation.setLayoutData(new GridData( + GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + mNavigation.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onNavigationChange(); + } + }); + + Composite labelParent = new Composite(topParent, SWT.NONE); + labelParent.setLayout(gl = new GridLayout(8, false)); + gl.marginWidth = gl.marginHeight = 0; + labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = cols; + + new Label(labelParent, SWT.NONE).setText("Editing config:"); + mCurrentLayoutLabel = new Label(labelParent, SWT.NONE); + mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.widthHint = 50; + + new Label(labelParent, SWT.NONE).setText("Size"); + mSizeIcon = createControlComposite(labelParent, false); + Composite sizeParent = new Composite(mSizeIcon.getParent(), SWT.NONE); + sizeParent.setLayout(gl = new GridLayout(3, false)); + gl.marginWidth = gl.marginHeight = 0; + gl.horizontalSpacing = 0; + + mSize1 = new Text(sizeParent, SWT.BORDER); + mSize1.setLayoutData(gd = new GridData()); + gd.widthHint = 30; + new Label(sizeParent, SWT.NONE).setText("x"); + mSize2 = new Text(sizeParent, SWT.BORDER); + mSize2.setLayoutData(gd = new GridData()); + gd.widthHint = 30; + + DimensionVerifier verifier = new DimensionVerifier(); + mSize1.addVerifyListener(verifier); + mSize2.addVerifyListener(verifier); + + SelectionListener sl = new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onSizeChange(); + } + public void widgetSelected(SelectionEvent e) { + onSizeChange(); + } + }; + + mSize1.addSelectionListener(sl); + mSize2.addSelectionListener(sl); + + ModifyListener sizeModifyListener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + mSize1.addModifyListener(sizeModifyListener); + mSize2.addModifyListener(sizeModifyListener); + + // first separator + Label separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL); + separator.setLayoutData(gd = new GridData( + GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); + gd.heightHint = 0; + + mThemeCombo = new Combo(labelParent, SWT.READ_ONLY | SWT.DROP_DOWN); + mThemeCombo.setEnabled(false); + updateUIFromResources(); + mThemeCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onThemeChange(); + } + }); + + // second separator + separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL); + separator.setLayoutData(gd = new GridData( + GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); + gd.heightHint = 0; + + mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT); + mCreateButton.setText("Create..."); + mCreateButton.setEnabled(false); + mCreateButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + LayoutCreatorDialog dialog = new LayoutCreatorDialog(mCreateButton.getShell(), + mEditedFile.getName(), mCurrentConfig); + if (dialog.open() == Dialog.OK) { + final FolderConfiguration config = new FolderConfiguration(); + dialog.getConfiguration(config); + + createAlternateLayout(config); + } + } + }); + + // create a new composite that will contain the standard editor controls. + Composite editorParent = new Composite(parent, SWT.NONE); + editorParent.setLayoutData(new GridData(GridData.FILL_BOTH)); + editorParent.setLayout(new FillLayout()); + super.createPartControl(editorParent); + } + + @Override + public void dispose() { + if (mFrameworkResourceChangeListener != null) { + AdtPlugin.getDefault().removeResourceChangedListener( + mFrameworkResourceChangeListener); + mFrameworkResourceChangeListener = null; + } + + LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this); + + if (mClipboard != null) { + mClipboard.dispose(); + mClipboard = null; + } + + super.dispose(); + } + + /* (non-Javadoc) + * Creates the palette root. + */ + @Override + protected PaletteRoot getPaletteRoot() { + mPaletteRoot = PaletteFactory.createPaletteRoot(mPaletteRoot, + mLayoutEditor.getTargetData()); + return mPaletteRoot; + } + + public Clipboard getClipboard() { + return mClipboard; + } + + /** + * Save operation in the Graphical Layout Editor. + *

          + * In our workflow, the model is owned by the Structured XML Editor. + * The graphical layout editor just displays it -- thus we don't really + * save anything here. + *

          + * This must NOT call the parent editor part. At the contrary, the parent editor + * part will call this *after* having done the actual save operation. + *

          + * The only action this editor must do is mark the undo command stack as + * being no longer dirty. + */ + @Override + public void doSave(IProgressMonitor monitor) { + getCommandStack().markSaveLocation(); + firePropertyChange(PROP_DIRTY); + } + + @Override + protected void configurePaletteViewer() { + super.configurePaletteViewer(); + + // Create a drag source listener on an edit part that is a viewer. + // What this does is use DND with a TemplateTransfer type which is actually + // the PaletteTemplateEntry held in the PaletteRoot. + TemplateTransferDragSourceListener dragSource = + new TemplateTransferDragSourceListener(getPaletteViewer()); + + // Create a drag source on the palette viewer. + // See the drag target associated with the GraphicalViewer in configureGraphicalViewer. + getPaletteViewer().addDragSourceListener(dragSource); + } + + /* (non-javadoc) + * Configure the graphical viewer before it receives its contents. + */ + @Override + protected void configureGraphicalViewer() { + super.configureGraphicalViewer(); + + GraphicalViewer viewer = getGraphicalViewer(); + viewer.setEditPartFactory(new UiElementsEditPartFactory(mParent.getDisplay())); + viewer.setRootEditPart(new ScalableFreeformRootEditPart()); + + // Disable the following -- we don't drag *from* the GraphicalViewer yet: + // viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer)); + + viewer.addDropTargetListener(new DropListener(viewer)); + } + + class DropListener extends TemplateTransferDropTargetListener { + public DropListener(EditPartViewer viewer) { + super(viewer); + } + + // TODO explain + @Override + protected CreationFactory getFactory(final Object template) { + return new CreationFactory() { + public Object getNewObject() { + // We don't know the newly created EditPart since "creating" new + // elements is done by ElementCreateCommand.execute() directly by + // manipulating the XML elements.. + return null; + } + + public Object getObjectType() { + return template; + } + + }; + } + } + + /* (non-javadoc) + * Set the contents of the GraphicalViewer after it has been created. + */ + @Override + protected void initializeGraphicalViewer() { + GraphicalViewer viewer = getGraphicalViewer(); + viewer.setContents(getModel()); + + IEditorInput input = getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + mEditedFile = fileInput.getFile(); + + updateUIFromResources(); + + LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), this); + } else { + // really this shouldn't happen! Log it in case it happens + mEditedFile = null; + AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s", + input.toString()); + } + } + + /* (non-javadoc) + * Sets the graphicalViewer for this EditorPart. + * @param viewer the graphical viewer + */ + @Override + protected void setGraphicalViewer(GraphicalViewer viewer) { + super.setGraphicalViewer(viewer); + + // TODO: viewer.setKeyHandler() + viewer.setContextMenu(createContextMenu(viewer)); + } + + /** + * Used by LayoutEditor.UiEditorActions.selectUiNode to select a new UI Node + * created by {@link ElementCreateCommand#execute()}. + * + * @param uiNodeModel The {@link UiElementNode} to select. + */ + public void selectModel(UiElementNode uiNodeModel) { + GraphicalViewer viewer = getGraphicalViewer(); + + // Give focus to the graphical viewer (in case the outline has it) + viewer.getControl().forceFocus(); + + Object editPart = viewer.getEditPartRegistry().get(uiNodeModel); + + if (editPart instanceof EditPart) { + viewer.select((EditPart)editPart); + } + } + + + //-------------- + // Local methods + //-------------- + + public LayoutEditor getLayoutEditor() { + return mLayoutEditor; + } + + private MenuManager createContextMenu(GraphicalViewer viewer) { + MenuManager menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + menuManager.addMenuListener(new ActionMenuListener(viewer)); + + return menuManager; + } + + private class ActionMenuListener implements IMenuListener { + private final GraphicalViewer mViewer; + + public ActionMenuListener(GraphicalViewer viewer) { + mViewer = viewer; + } + + /** + * The menu is about to be shown. The menu manager has already been + * requested to remove any existing menu item. This method gets the + * tree selection and if it is of the appropriate type it re-creates + * the necessary actions. + */ + public void menuAboutToShow(IMenuManager manager) { + ArrayList selected = new ArrayList(); + + // filter selected items and only keep those we can handle + for (Object obj : mViewer.getSelectedEditParts()) { + if (obj instanceof UiElementEditPart) { + UiElementEditPart part = (UiElementEditPart) obj; + UiElementNode uiNode = part.getUiNode(); + if (uiNode != null) { + selected.add(uiNode); + } + } + } + + if (selected.size() > 0) { + doCreateMenuAction(manager, mViewer, selected); + } + } + } + + private void doCreateMenuAction(IMenuManager manager, + final GraphicalViewer viewer, + final ArrayList selected) { + if (selected != null) { + boolean hasXml = false; + for (UiElementNode uiNode : selected) { + if (uiNode.getXmlNode() != null) { + hasXml = true; + break; + } + } + + if (hasXml) { + manager.add(new CopyCutAction(mLayoutEditor, getClipboard(), + null, selected, true /* cut */)); + manager.add(new CopyCutAction(mLayoutEditor, getClipboard(), + null, selected, false /* cut */)); + + // Can't paste with more than one element selected (the selection is the target) + if (selected.size() <= 1) { + // Paste is not valid if it would add a second element on a terminal element + // which parent is a document -- an XML document can only have one child. This + // means paste is valid if the current UI node can have children or if the + // parent is not a document. + UiElementNode ui_root = selected.get(0).getUiRoot(); + if (ui_root.getDescriptor().hasChildren() || + !(ui_root.getUiParent() instanceof UiDocumentNode)) { + manager.add(new PasteAction(mLayoutEditor, getClipboard(), + selected.get(0))); + } + } + manager.add(new Separator()); + } + } + + // Append "add" and "remove" actions. They do the same thing as the add/remove + // buttons on the side. + IconFactory factory = IconFactory.getInstance(); + + final UiEditorActions uiActions = mLayoutEditor.getUiEditorActions(); + + // "Add" makes sense only if there's 0 or 1 item selected since the + // one selected item becomes the target. + if (selected == null || selected.size() <= 1) { + manager.add(new Action("Add...", factory.getImageDescriptor("add")) { //$NON-NLS-2$ + @Override + public void run() { + UiElementNode node = selected != null && selected.size() > 0 ? selected.get(0) + : null; + uiActions.doAdd(node, viewer.getControl().getShell()); + } + }); + } + + if (selected != null) { + manager.add(new Action("Remove", factory.getImageDescriptor("delete")) { //$NON-NLS-2$ + @Override + public void run() { + uiActions.doRemove(selected, viewer.getControl().getShell()); + } + }); + + manager.add(new Separator()); + + manager.add(new Action("Up", factory.getImageDescriptor("up")) { //$NON-NLS-2$ + @Override + public void run() { + uiActions.doUp(selected); + } + }); + manager.add(new Action("Down", factory.getImageDescriptor("down")) { //$NON-NLS-2$ + @Override + public void run() { + uiActions.doDown(selected); + } + }); + } + + } + + /** + * Sets the UI for the edition of a new file. + * @param configuration the configuration of the new file. + */ + public void editNewFile(FolderConfiguration configuration) { + // update the configuration UI + setConfiguration(configuration); + + // enable the create button if the current and edited config are not equals + mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); + } + + public Rectangle getBounds() { + ScreenOrientation orientation = null; + if (mOrientation.getSelectionIndex() == 0) { + orientation = ScreenOrientation.PORTRAIT; + } else { + orientation = ScreenOrientation.getByIndex( + mOrientation.getSelectionIndex() - 1); + } + + int s1, s2; + + // get the size from the UI controls. If it fails, revert to default values. + try { + s1 = Integer.parseInt(mSize1.getText().trim()); + } catch (NumberFormatException e) { + s1 = 480; + } + + try { + s2 = Integer.parseInt(mSize2.getText().trim()); + } catch (NumberFormatException e) { + s2 = 320; + } + + // make sure s1 is bigger than s2 + if (s1 < s2) { + int tmp = s1; + s1 = s2; + s2 = tmp; + } + + switch (orientation) { + default: + case PORTRAIT: + return new Rectangle(0, 0, s2, s1); + case LANDSCAPE: + return new Rectangle(0, 0, s1, s2); + case SQUARE: + return new Rectangle(0, 0, s1, s1); + } + } + + /** + * Renders an Android View described by a {@link ViewElementDescriptor}. + *

          This uses the wrap_content mode for both layout_width and + * layout_height, and use the class name for the text attribute. + * @param descriptor the descriptor for the class to render. + * @return an ImageData containing the rendering or null if rendering failed. + */ + public ImageData renderWidget(ViewElementDescriptor descriptor) { + if (mEditedFile == null) { + return null; + } + + IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject()); + if (target == null) { + return null; + } + + AndroidTargetData data = Sdk.getCurrent().getTargetData(target); + if (data == null) { + return null; + } + + LayoutBridge bridge = data.getLayoutBridge(); + + if (bridge.bridge != null) { // bridge can never be null. + ResourceManager resManager = ResourceManager.getInstance(); + + ProjectCallback projectCallback = null; + Map> configuredProjectResources = null; + if (mEditedFile != null) { + ProjectResources projectRes = resManager.getProjectResources( + mEditedFile.getProject()); + projectCallback = new ProjectCallback(bridge.classLoader, + projectRes, mEditedFile.getProject()); + + // get the configured resources for the project + // get the resources of the file's project. + if (mConfiguredProjectRes == null && projectRes != null) { + // make sure they are loaded + projectRes.loadAll(); + + // get the project resource values based on the current config + mConfiguredProjectRes = projectRes.getConfiguredResources(mCurrentConfig); + } + + configuredProjectResources = mConfiguredProjectRes; + } else { + // we absolutely need a Map of configured project resources. + configuredProjectResources = new HashMap>(); + } + + // get the framework resources + Map> frameworkResources = + getConfiguredFrameworkResources(); + + if (configuredProjectResources != null && frameworkResources != null) { + // get the selected theme + int themeIndex = mThemeCombo.getSelectionIndex(); + if (themeIndex != -1) { + String theme = mThemeCombo.getItem(themeIndex); + + // change the string if it's a custom theme to make sure we can + // differentiate them + if (themeIndex >= mPlatformThemeCount) { + theme = "*" + theme; //$NON-NLS-1$ + } + + // Render a single object as described by the ViewElementDescriptor. + WidgetPullParser parser = new WidgetPullParser(descriptor); + ILayoutResult result = bridge.bridge.computeLayout(parser, + null /* projectKey */, + 300 /* width */, 300 /* height */, theme, + configuredProjectResources, frameworkResources, projectCallback, + null /* logger */); + + // update the UiElementNode with the layout info. + if (result.getSuccess() == ILayoutResult.SUCCESS) { + BufferedImage largeImage = result.getImage(); + + // we need to resize it to the actual widget size, and convert it into + // an SWT image object. + int width = result.getRootView().getRight(); + int height = result.getRootView().getBottom(); + Raster raster = largeImage.getData(new java.awt.Rectangle(width, height)); + int[] imageDataBuffer = ((DataBufferInt)raster.getDataBuffer()).getData(); + + ImageData imageData = new ImageData(width, height, 32, + new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF)); + + imageData.setPixels(0, 0, imageDataBuffer.length, imageDataBuffer, 0); + + return imageData; + } + } + } + } + return null; + } + + /** + * Reloads this editor, by getting the new model from the {@link LayoutEditor}. + */ + void reloadEditor() { + GraphicalViewer viewer = getGraphicalViewer(); + viewer.setContents(getModel()); + + IEditorInput input = mLayoutEditor.getEditorInput(); + setInput(input); + + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + mEditedFile = fileInput.getFile(); + } else { + // really this shouldn't happen! Log it in case it happens + mEditedFile = null; + AdtPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s", + input.toString()); + } + } + + /** + * Update the layout editor when the Xml model is changed. + */ + void onXmlModelChanged() { + GraphicalViewer viewer = getGraphicalViewer(); + + // try to preserve the selection before changing the content + SelectionManager selMan = viewer.getSelectionManager(); + ISelection selection = selMan.getSelection(); + + try { + viewer.setContents(getModel()); + } finally { + selMan.setSelection(selection); + } + + if (mLayoutEditor.isGraphicalEditorActive()) { + recomputeLayout(); + } else { + mNeedsRecompute = true; + } + } + + /** + * Update the UI controls state with a given {@link FolderConfiguration}. + *

          If a qualifier is not present in the {@link FolderConfiguration} object, the UI control + * is not modified. However if the value in the control is not the default value, a warning + * icon is showed. + */ + void setConfiguration(FolderConfiguration config) { + mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets. + + mEditedConfig = config; + mConfiguredFrameworkRes = mConfiguredProjectRes = null; + + mCountryIcon.setImage(mMatchImage); + CountryCodeQualifier countryQualifier = config.getCountryCodeQualifier(); + if (countryQualifier != null) { + mCountry.setText(String.format("%1$d", countryQualifier.getCode())); + mCurrentConfig.setCountryCodeQualifier(countryQualifier); + } else if (mCountry.getText().length() > 0) { + mCountryIcon.setImage(mWarningImage); + } + + mNetworkIcon.setImage(mMatchImage); + NetworkCodeQualifier networkQualifier = config.getNetworkCodeQualifier(); + if (networkQualifier != null) { + mNetwork.setText(String.format("%1$d", networkQualifier.getCode())); + mCurrentConfig.setNetworkCodeQualifier(networkQualifier); + } else if (mNetwork.getText().length() > 0) { + mNetworkIcon.setImage(mWarningImage); + } + + mLanguageIcon.setImage(mMatchImage); + LanguageQualifier languageQualifier = config.getLanguageQualifier(); + if (languageQualifier != null) { + mLanguage.setText(languageQualifier.getValue()); + mCurrentConfig.setLanguageQualifier(languageQualifier); + } else if (mLanguage.getText().length() > 0) { + mLanguageIcon.setImage(mWarningImage); + } + + mRegionIcon.setImage(mMatchImage); + RegionQualifier regionQualifier = config.getRegionQualifier(); + if (regionQualifier != null) { + mRegion.setText(regionQualifier.getValue()); + mCurrentConfig.setRegionQualifier(regionQualifier); + } else if (mRegion.getText().length() > 0) { + mRegionIcon.setImage(mWarningImage); + } + + mOrientationIcon.setImage(mMatchImage); + ScreenOrientationQualifier orientationQualifier = config.getScreenOrientationQualifier(); + if (orientationQualifier != null) { + mOrientation.select( + ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1); + mCurrentConfig.setScreenOrientationQualifier(orientationQualifier); + } else if (mOrientation.getSelectionIndex() != 0) { + mOrientationIcon.setImage(mWarningImage); + } + + mDensityIcon.setImage(mMatchImage); + PixelDensityQualifier densityQualifier = config.getPixelDensityQualifier(); + if (densityQualifier != null) { + mDensity.setText(String.format("%1$d", densityQualifier.getValue())); + mCurrentConfig.setPixelDensityQualifier(densityQualifier); + } else if (mDensity.getText().length() > 0) { + mDensityIcon.setImage(mWarningImage); + } + + mTouchIcon.setImage(mMatchImage); + TouchScreenQualifier touchQualifier = config.getTouchTypeQualifier(); + if (touchQualifier != null) { + mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1); + mCurrentConfig.setTouchTypeQualifier(touchQualifier); + } else if (mTouch.getSelectionIndex() != 0) { + mTouchIcon.setImage(mWarningImage); + } + + mKeyboardIcon.setImage(mMatchImage); + KeyboardStateQualifier keyboardQualifier = config.getKeyboardStateQualifier(); + if (keyboardQualifier != null) { + mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1); + mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier); + } else if (mKeyboard.getSelectionIndex() != 0) { + mKeyboardIcon.setImage(mWarningImage); + } + + mTextInputIcon.setImage(mMatchImage); + TextInputMethodQualifier inputQualifier = config.getTextInputMethodQualifier(); + if (inputQualifier != null) { + mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1); + mCurrentConfig.setTextInputMethodQualifier(inputQualifier); + } else if (mTextInput.getSelectionIndex() != 0) { + mTextInputIcon.setImage(mWarningImage); + } + + mNavigationIcon.setImage(mMatchImage); + NavigationMethodQualifier navigationQualifiter = config.getNavigationMethodQualifier(); + if (navigationQualifiter != null) { + mNavigation.select( + NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1); + mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter); + } else if (mNavigation.getSelectionIndex() != 0) { + mNavigationIcon.setImage(mWarningImage); + } + + mSizeIcon.setImage(mMatchImage); + ScreenDimensionQualifier sizeQualifier = config.getScreenDimensionQualifier(); + if (sizeQualifier != null) { + mSize1.setText(String.format("%1$d", sizeQualifier.getValue1())); + mSize2.setText(String.format("%1$d", sizeQualifier.getValue2())); + mCurrentConfig.setScreenDimensionQualifier(sizeQualifier); + } else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) { + mSizeIcon.setImage(mWarningImage); + } + + // update the string showing the folder name + String current = config.toDisplayString(); + mCurrentLayoutLabel.setText(current != null ? current : "(Default)"); + + mDisableUpdates = false; + } + + /** + * Displays an error icon in front of all the non-null qualifiers. + */ + void displayConfigError() { + mCountryIcon.setImage(mMatchImage); + CountryCodeQualifier countryQualifier = mCurrentConfig.getCountryCodeQualifier(); + if (countryQualifier != null) { + mCountryIcon.setImage(mErrorImage); + } + + mNetworkIcon.setImage(mMatchImage); + NetworkCodeQualifier networkQualifier = mCurrentConfig.getNetworkCodeQualifier(); + if (networkQualifier != null) { + mNetworkIcon.setImage(mErrorImage); + } + + mLanguageIcon.setImage(mMatchImage); + LanguageQualifier languageQualifier = mCurrentConfig.getLanguageQualifier(); + if (languageQualifier != null) { + mLanguageIcon.setImage(mErrorImage); + } + + mRegionIcon.setImage(mMatchImage); + RegionQualifier regionQualifier = mCurrentConfig.getRegionQualifier(); + if (regionQualifier != null) { + mRegionIcon.setImage(mErrorImage); + } + + mOrientationIcon.setImage(mMatchImage); + ScreenOrientationQualifier orientationQualifier = + mCurrentConfig.getScreenOrientationQualifier(); + if (orientationQualifier != null) { + mOrientationIcon.setImage(mErrorImage); + } + + mDensityIcon.setImage(mMatchImage); + PixelDensityQualifier densityQualifier = mCurrentConfig.getPixelDensityQualifier(); + if (densityQualifier != null) { + mDensityIcon.setImage(mErrorImage); + } + + mTouchIcon.setImage(mMatchImage); + TouchScreenQualifier touchQualifier = mCurrentConfig.getTouchTypeQualifier(); + if (touchQualifier != null) { + mTouchIcon.setImage(mErrorImage); + } + + mKeyboardIcon.setImage(mMatchImage); + KeyboardStateQualifier keyboardQualifier = mCurrentConfig.getKeyboardStateQualifier(); + if (keyboardQualifier != null) { + mKeyboardIcon.setImage(mErrorImage); + } + + mTextInputIcon.setImage(mMatchImage); + TextInputMethodQualifier inputQualifier = mCurrentConfig.getTextInputMethodQualifier(); + if (inputQualifier != null) { + mTextInputIcon.setImage(mErrorImage); + } + + mNavigationIcon.setImage(mMatchImage); + NavigationMethodQualifier navigationQualifiter = + mCurrentConfig.getNavigationMethodQualifier(); + if (navigationQualifiter != null) { + mNavigationIcon.setImage(mErrorImage); + } + + mSizeIcon.setImage(mMatchImage); + ScreenDimensionQualifier sizeQualifier = mCurrentConfig.getScreenDimensionQualifier(); + if (sizeQualifier != null) { + mSizeIcon.setImage(mErrorImage); + } + + // update the string showing the folder name + String current = mCurrentConfig.toDisplayString(); + mCurrentLayoutLabel.setText(current != null ? current : "(Default)"); + } + + UiDocumentNode getModel() { + return mLayoutEditor.getUiRootNode(); + } + + void reloadPalette() { + PaletteFactory.createPaletteRoot(mPaletteRoot, mLayoutEditor.getTargetData()); + } + + private void onCountryCodeChange() { + // because mCountry triggers onCountryCodeChange at each modification, calling setText() + // will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String value = mCountry.getText(); + + // empty string, means no qualifier. + if (value.length() == 0) { + mCurrentConfig.setCountryCodeQualifier(null); + } else { + try { + CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier( + CountryCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mCurrentConfig.setCountryCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + // We do nothing in this case. + mCountryIcon.setImage(mErrorImage); + return; + } + } catch (NumberFormatException e) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mCurrentConfig.setCountryCodeQualifier(null); + mCountryIcon.setImage(mErrorImage); + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onNetworkCodeChange() { + // because mNetwork triggers onNetworkCodeChange at each modification, calling setText() + // will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String value = mNetwork.getText(); + + // empty string, means no qualifier. + if (value.length() == 0) { + mCurrentConfig.setNetworkCodeQualifier(null); + } else { + try { + NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier( + NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mCurrentConfig.setNetworkCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + // We do nothing in this case. + mNetworkIcon.setImage(mErrorImage); + return; + } + } catch (NumberFormatException e) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mCurrentConfig.setNetworkCodeQualifier(null); + mNetworkIcon.setImage(mErrorImage); + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + /** + * Call back for language combo selection + */ + private void onLanguageChange() { + // because mLanguage triggers onLanguageChange at each modification, the filling + // of the combo with data will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String value = mLanguage.getText(); + + updateRegionUi(null /* projectResources */, null /* frameworkResources */); + + // empty string, means no qualifier. + if (value.length() == 0) { + mCurrentConfig.setLanguageQualifier(null); + } else { + LanguageQualifier qualifier = null; + String segment = LanguageQualifier.getFolderSegment(value); + if (segment != null) { + qualifier = LanguageQualifier.getQualifier(segment); + } + + if (qualifier != null) { + mCurrentConfig.setLanguageQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + mCurrentConfig.setLanguageQualifier(null); + mLanguageIcon.setImage(mErrorImage); + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onRegionChange() { + // because mRegion triggers onRegionChange at each modification, the filling + // of the combo with data will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String value = mRegion.getText(); + + // empty string, means no qualifier. + if (value.length() == 0) { + mCurrentConfig.setRegionQualifier(null); + } else { + RegionQualifier qualifier = null; + String segment = RegionQualifier.getFolderSegment(value); + if (segment != null) { + qualifier = RegionQualifier.getQualifier(segment); + } + + if (qualifier != null) { + mCurrentConfig.setRegionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + mCurrentConfig.setRegionQualifier(null); + mRegionIcon.setImage(mErrorImage); + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onOrientationChange() { + // update the current config + int index = mOrientation.getSelectionIndex(); + if (index != 0) { + mCurrentConfig.setScreenOrientationQualifier(new ScreenOrientationQualifier( + ScreenOrientation.getByIndex(index-1))); + } else { + mCurrentConfig.setScreenOrientationQualifier(null); + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onDensityChange() { + // because mDensity triggers onDensityChange at each modification, calling setText() + // will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String value = mDensity.getText(); + + // empty string, means no qualifier. + if (value.length() == 0) { + mCurrentConfig.setPixelDensityQualifier(null); + } else { + try { + PixelDensityQualifier qualifier = PixelDensityQualifier.getQualifier( + PixelDensityQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mCurrentConfig.setPixelDensityQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + // We do nothing in this case. + return; + } + } catch (NumberFormatException e) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + // We do nothing in this case. + mDensityIcon.setImage(mErrorImage); + return; + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onTouchChange() { + // update the current config + int index = mTouch.getSelectionIndex(); + if (index != 0) { + mCurrentConfig.setTouchTypeQualifier(new TouchScreenQualifier( + TouchScreenType.getByIndex(index-1))); + } else { + mCurrentConfig.setTouchTypeQualifier(null); + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onKeyboardChange() { + // update the current config + int index = mKeyboard.getSelectionIndex(); + if (index != 0) { + mCurrentConfig.setKeyboardStateQualifier(new KeyboardStateQualifier( + KeyboardState.getByIndex(index-1))); + } else { + mCurrentConfig.setKeyboardStateQualifier(null); + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onTextInputChange() { + // update the current config + int index = mTextInput.getSelectionIndex(); + if (index != 0) { + mCurrentConfig.setTextInputMethodQualifier(new TextInputMethodQualifier( + TextInputMethod.getByIndex(index-1))); + } else { + mCurrentConfig.setTextInputMethodQualifier(null); + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onNavigationChange() { + // update the current config + int index = mNavigation.getSelectionIndex(); + if (index != 0) { + mCurrentConfig.setNavigationMethodQualifier(new NavigationMethodQualifier( + NavigationMethod.getByIndex(index-1))); + } else { + mCurrentConfig.setNavigationMethodQualifier(null); + } + + // look for a file to open/create + onConfigurationChange(); + } + + private void onSizeChange() { + // because mSize1 and mSize2 trigger onSizeChange at each modification, calling setText() + // will trigger notifications, and we don't want that. + if (mDisableUpdates == true) { + return; + } + + // update the current config + String size1 = mSize1.getText(); + String size2 = mSize2.getText(); + + // if only one of the strings is empty, do nothing + if ((size1.length() == 0) ^ (size2.length() == 0)) { + mSizeIcon.setImage(mErrorImage); + return; + } else if (size1.length() == 0 && size2.length() == 0) { + // both sizes are empty: remove the qualifier. + mCurrentConfig.setScreenDimensionQualifier(null); + } else { + ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1, + size2); + + if (qualifier != null) { + mCurrentConfig.setScreenDimensionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong. + // we do nothing in this case. + return; + } + } + + // look for a file to open/create + onConfigurationChange(); + } + + + /** + * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it. + *

          If there is no match, notify the user. + */ + private void onConfigurationChange() { + mConfiguredFrameworkRes = mConfiguredProjectRes = null; + + if (mEditedFile == null || mEditedConfig == null) { + return; + } + + // get the resources of the file's project. + ProjectResources resources = ResourceManager.getInstance().getProjectResources( + mEditedFile.getProject()); + + // from the resources, look for a matching file + ResourceFile match = null; + if (resources != null) { + match = resources.getMatchingFile(mEditedFile.getName(), + ResourceFolderType.LAYOUT, + mCurrentConfig); + } + + if (match != null) { + if (match.getFile().equals(mEditedFile) == false) { + try { + IDE.openEditor( + getSite().getWorkbenchWindow().getActivePage(), + match.getFile().getIFile()); + + // we're done! + return; + } catch (PartInitException e) { + // FIXME: do something! + } + } + + // at this point, we have not opened a new file. + + // update the configuration icons with the new edited config. + setConfiguration(mEditedConfig); + + // enable the create button if the current and edited config are not equals + mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); + + // Even though the layout doesn't change, the config changed, and referenced + // resources need to be updated. + recomputeLayout(); + } else { + // update the configuration icons with the new edited config. + displayConfigError(); + + // enable the Create button + mCreateButton.setEnabled(true); + + // display the error. + String message = String.format( + "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.", + mCurrentConfig.toDisplayString(), + mCurrentConfig.getFolderName(ResourceFolderType.LAYOUT), + mEditedFile.getName()); + showErrorInEditor(message); + } + } + + private void onThemeChange() { + int themeIndex = mThemeCombo.getSelectionIndex(); + if (themeIndex != -1) { + String theme = mThemeCombo.getItem(themeIndex); + + if (theme.equals(THEME_SEPARATOR)) { + mThemeCombo.select(0); + } + + recomputeLayout(); + } + } + + /** + * Creates a composite with no margin/spacing, and puts a {@link Label} in it with the matching + * icon. + * @param parent the parent to receive the composite + * @return the created {@link Label} object. + */ + private Label createControlComposite(Composite parent, boolean grab) { + GridLayout gl; + + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(gl = new GridLayout(2, false)); + gl.marginHeight = gl.marginWidth = 0; + gl.horizontalSpacing = 0; + if (grab) { + composite.setLayoutData( + new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); + } + + // create the label + Label icon = new Label(composite, SWT.NONE); + icon.setImage(mMatchImage); + + return icon; + } + + /** + * Recomputes the layout with the help of layoutlib. + */ + void recomputeLayout() { + try { + // check that the resource exists. If the file is opened but the project is closed + // or deleted for some reason (changed from outside of eclipse), then this will + // return false; + if (mEditedFile.exists() == false) { + String message = String.format("Resource '%1$s' does not exist.", + mEditedFile.getFullPath().toString()); + + showErrorInEditor(message); + + return; + } + + IProject iProject = mEditedFile.getProject(); + + if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) { + String message = String.format("%1$s is out of sync. Please refresh.", + mEditedFile.getName()); + + showErrorInEditor(message); + + // also print it in the error console. + AdtPlugin.printErrorToConsole(iProject.getName(), message); + return; + } + + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + if (target == null) { + showErrorInEditor("The project target is not set."); + return; + } + + AndroidTargetData data = currentSdk.getTargetData(target); + if (data == null) { + // It can happen that the workspace refreshes while the SDK is loading its + // data, which could trigger a redraw of the opened layout if some resources + // changed while Eclipse is closed. + // In this case data could be null, but this is not an error. + // We can just silently return, as all the opened editors are automatically + // refreshed once the SDK finishes loading. + if (AdtPlugin.getDefault().getSdkLoadStatus(null) != LoadStatus.LOADING) { + showErrorInEditor(String.format( + "The project target (%s) was not properly loaded.", + target.getName())); + } + return; + } + + // check there is actually a model (maybe the file is empty). + UiDocumentNode model = getModel(); + + if (model.getUiChildren().size() == 0) { + showErrorInEditor("No Xml content. Go to the Outline view and add nodes."); + return; + } + + LayoutBridge bridge = data.getLayoutBridge(); + + if (bridge.bridge != null) { // bridge can never be null. + ResourceManager resManager = ResourceManager.getInstance(); + + ProjectResources projectRes = resManager.getProjectResources(iProject); + if (projectRes == null) { + return; + } + + // get the resources of the file's project. + if (mConfiguredProjectRes == null) { + // make sure they are loaded + projectRes.loadAll(); + + // get the project resource values based on the current config + mConfiguredProjectRes = projectRes.getConfiguredResources(mCurrentConfig); + } + + // get the framework resources + Map> frameworkResources = + getConfiguredFrameworkResources(); + + if (mConfiguredProjectRes != null && frameworkResources != null) { + if (mProjectCallback == null) { + mProjectCallback = new ProjectCallback( + bridge.classLoader, projectRes, iProject); + } + + if (mLogger == null) { + mLogger = new ILayoutLog() { + public void error(String message) { + AdtPlugin.printErrorToConsole(mEditedFile.getName(), message); + } + + public void error(Throwable error) { + String message = error.getMessage(); + if (message == null) { + message = error.getClass().getName(); + } + + PrintStream ps = new PrintStream(AdtPlugin.getErrorStream()); + error.printStackTrace(ps); + } + + public void warning(String message) { + AdtPlugin.printToConsole(mEditedFile.getName(), message); + } + }; + } + + // get the selected theme + int themeIndex = mThemeCombo.getSelectionIndex(); + if (themeIndex != -1) { + String theme = mThemeCombo.getItem(themeIndex); + + // change the string if it's a custom theme to make sure we can + // differentiate them + if (themeIndex >= mPlatformThemeCount) { + theme = "*" + theme; //$NON-NLS-1$ + } + + // Compute the layout + UiElementPullParser parser = new UiElementPullParser(getModel()); + Rectangle rect = getBounds(); + ILayoutResult result = bridge.bridge.computeLayout(parser, + iProject /* projectKey */, + rect.width, rect.height, theme, + mConfiguredProjectRes, frameworkResources, mProjectCallback, + mLogger); + + // update the UiElementNode with the layout info. + if (result.getSuccess() == ILayoutResult.SUCCESS) { + model.setEditData(result.getImage()); + + updateNodeWithBounds(result.getRootView()); + } else { + String message = result.getErrorMessage(); + + // Reset the edit data for all the nodes. + resetNodeBounds(model); + + if (message != null) { + // set the error in the top element. + model.setEditData(message); + } + } + + model.refreshUi(); + } + } + } else { + // SDK is loaded but not the layout library! + String message = null; + // check whether the bridge managed to load, or not + if (bridge.status == LoadStatus.LOADING) { + message = String.format( + "Eclipse is loading framework information and the Layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.", + mEditedFile.getName()); + } else { + message = String.format("Eclipse failed to load the framework information and the Layout library!"); + } + showErrorInEditor(message); + } + } else { + String message = String.format( + "Eclipse is loading the SDK.\n%1$s will refresh automatically once the process is finished.", + mEditedFile.getName()); + + showErrorInEditor(message); + } + } finally { + // no matter the result, we are done doing the recompute based on the latest + // resource/code change. + mNeedsRecompute = false; + } + } + + private void showErrorInEditor(String message) { + // get the model to display the error directly in the editor + UiDocumentNode model = getModel(); + + // Reset the edit data for all the nodes. + resetNodeBounds(model); + + if (message != null) { + // set the error in the top element. + model.setEditData(message); + } + + model.refreshUi(); + } + + private void resetNodeBounds(UiElementNode node) { + node.setEditData(null); + + List children = node.getUiChildren(); + for (UiElementNode child : children) { + resetNodeBounds(child); + } + } + + private void updateNodeWithBounds(ILayoutViewInfo r) { + if (r != null) { + // update the node itself, as the viewKey is the XML node in this implementation. + Object viewKey = r.getViewKey(); + if (viewKey instanceof UiElementNode) { + Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(), + r.getRight()-r.getLeft(), r.getBottom() - r.getTop()); + + ((UiElementNode)viewKey).setEditData(bounds); + } + + // and then its children. + ILayoutViewInfo[] children = r.getChildren(); + if (children != null) { + for (ILayoutViewInfo child : children) { + updateNodeWithBounds(child); + } + } + } + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean) + * + * Called when the file changes triggered a redraw of the layout + */ + public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) { + boolean recompute = rChange; + + if (resChange) { + recompute = true; + + // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache. + + // force a reparse in case a value XML file changed. + mConfiguredProjectRes = null; + + // clear the cache in the bridge in case a bitmap/9-patch changed. + IAndroidTarget target = Sdk.getCurrent().getTarget(mEditedFile.getProject()); + if (target != null) { + + AndroidTargetData data = Sdk.getCurrent().getTargetData(target); + if (data != null) { + LayoutBridge bridge = data.getLayoutBridge(); + + if (bridge.bridge != null) { + bridge.bridge.clearCaches(mEditedFile.getProject()); + } + } + } + + mParent.getDisplay().asyncExec(mUiUpdateFromResourcesRunnable); + } + + if (codeChange) { + // only recompute if the custom view loader was used to load some code. + if (mProjectCallback != null && mProjectCallback.isUsed()) { + mProjectCallback = null; + recompute = true; + } + } + + if (recompute) { + mParent.getDisplay().asyncExec(mConditionalRecomputeRunnable); + } + } + + /** + * Responds to a page change that made the Graphical editor page the activated page. + */ + void activated() { + mActive = true; + if (mNeedsRecompute) { + recomputeLayout(); + } + } + + /** + * Responds to a page change that made the Graphical editor page the deactivated page + */ + void deactivated() { + mActive = false; + } + + /** + * Updates the UI from values in the resources, such as languages, regions, themes, etc... + * This must be called from the UI thread. + */ + private void updateUIFromResources() { + + ResourceManager manager = ResourceManager.getInstance(); + + ProjectResources frameworkProject = getFrameworkResources(); + + mDisableUpdates = true; + + // Reset stuff + int selection = mThemeCombo.getSelectionIndex(); + mThemeCombo.removeAll(); + mPlatformThemeCount = 0; + mLanguage.removeAll(); + + Set languages = new HashSet(); + ArrayList themes = new ArrayList(); + + // get the themes, and languages from the Framework. + if (frameworkProject != null) { + // get the configured resources for the framework + Map> frameworResources = + getConfiguredFrameworkResources(); + + if (frameworResources != null) { + // get the styles. + Map styles = frameworResources.get( + ResourceType.STYLE.getName()); + + + // collect the themes out of all the styles. + for (IResourceValue value : styles.values()) { + String name = value.getName(); + if (name.startsWith("Theme.") || name.equals("Theme")) { + themes.add(value.getName()); + mPlatformThemeCount++; + } + } + + // sort them and add them to the combo + Collections.sort(themes); + + for (String theme : themes) { + mThemeCombo.add(theme); + } + + mPlatformThemeCount = themes.size(); + themes.clear(); + } + // now get the languages from the framework. + Set frameworkLanguages = frameworkProject.getLanguages(); + if (frameworkLanguages != null) { + languages.addAll(frameworkLanguages); + } + } + + // now get the themes and languages from the project. + ProjectResources project = null; + if (mEditedFile != null) { + project = manager.getProjectResources(mEditedFile.getProject()); + + // in cases where the opened file is not linked to a project, this could be null. + if (project != null) { + // get the configured resources for the project + if (mConfiguredProjectRes == null) { + // make sure they are loaded + project.loadAll(); + + // get the project resource values based on the current config + mConfiguredProjectRes = project.getConfiguredResources(mCurrentConfig); + } + + if (mConfiguredProjectRes != null) { + // get the styles. + Map styleMap = mConfiguredProjectRes.get( + ResourceType.STYLE.getName()); + + if (styleMap != null) { + // collect the themes out of all the styles, ie styles that extend, + // directly or indirectly a platform theme. + for (IResourceValue value : styleMap.values()) { + if (isTheme(value, styleMap)) { + themes.add(value.getName()); + } + } + + // sort them and add them the to the combo. + if (mPlatformThemeCount > 0 && themes.size() > 0) { + mThemeCombo.add(THEME_SEPARATOR); + } + + Collections.sort(themes); + + for (String theme : themes) { + mThemeCombo.add(theme); + } + } + } + + // now get the languages from the project. + Set projectLanguages = project.getLanguages(); + if (projectLanguages != null) { + languages.addAll(projectLanguages); + } + } + } + + // add the languages to the Combo + for (String language : languages) { + mLanguage.add(language); + } + + mDisableUpdates = false; + + // and update the Region UI based on the current language + updateRegionUi(project, frameworkProject); + + // handle default selection of themes + if (mThemeCombo.getItemCount() > 0) { + mThemeCombo.setEnabled(true); + if (selection == -1) { + selection = 0; + } + + if (mThemeCombo.getItemCount() <= selection) { + mThemeCombo.select(0); + } else { + mThemeCombo.select(selection); + } + } else { + mThemeCombo.setEnabled(false); + } + } + + /** + * Returns whether the given style is a theme. + * This is done by making sure the parent is a theme. + * @param value the style to check + * @param styleMap the map of styles for the current project. Key is the style name. + * @return True if the given style is a theme. + */ + private boolean isTheme(IResourceValue value, Map styleMap) { + if (value instanceof IStyleResourceValue) { + IStyleResourceValue style = (IStyleResourceValue)value; + + boolean frameworkStyle = false; + String parentStyle = style.getParentStyle(); + if (parentStyle == null) { + // if there is no specified parent style we look an implied one. + // For instance 'Theme.light' is implied child style of 'Theme', + // and 'Theme.light.fullscreen' is implied child style of 'Theme.light' + String name = style.getName(); + int index = name.lastIndexOf('.'); + if (index != -1) { + parentStyle = name.substring(0, index); + } + } else { + // remove the useless @ if it's there + if (parentStyle.startsWith("@")) { + parentStyle = parentStyle.substring(1); + } + + // check for framework identifier. + if (parentStyle.startsWith("android:")) { + frameworkStyle = true; + parentStyle = parentStyle.substring("android:".length()); + } + + // at this point we could have the format style/. we want only the name + if (parentStyle.startsWith("style/")) { + parentStyle = parentStyle.substring("style/".length()); + } + } + + if (frameworkStyle) { + // if the parent is a framework style, it has to be 'Theme' or 'Theme.*' + return parentStyle.equals("Theme") || parentStyle.startsWith("Theme."); + } else { + // if it's a project style, we check this is a theme. + value = styleMap.get(parentStyle); + if (value != null) { + return isTheme(value, styleMap); + } + } + } + + return false; + } + + /** + * Update the Region UI widget based on the current language selection + * @param projectResources the project resources or {@code null}. + * @param frameworkResources the framework resource or {@code null} + */ + private void updateRegionUi(ProjectResources projectResources, + ProjectResources frameworkResources) { + if (projectResources == null && mEditedFile != null) { + projectResources = ResourceManager.getInstance().getProjectResources( + mEditedFile.getProject()); + } + + if (frameworkResources == null) { + frameworkResources = getFrameworkResources(); + } + + String currentLanguage = mLanguage.getText(); + + Set set = null; + + if (projectResources != null) { + set = projectResources.getRegions(currentLanguage); + } + + if (frameworkResources != null) { + if (set != null) { + Set set2 = frameworkResources.getRegions(currentLanguage); + set.addAll(set2); + } else { + set = frameworkResources.getRegions(currentLanguage); + } + } + + if (set != null) { + mDisableUpdates = true; + + mRegion.removeAll(); + for (String region : set) { + mRegion.add(region); + } + + mDisableUpdates = false; + } + } + + private Map> getConfiguredFrameworkResources() { + if (mConfiguredFrameworkRes == null) { + ProjectResources frameworkRes = getFrameworkResources(); + + if (frameworkRes == null) { + AdtPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework"); + } + + // get the framework resource values based on the current config + mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(mCurrentConfig); + } + + return mConfiguredFrameworkRes; + } + + /** + * Returns the selection synchronizer object. + * The synchronizer can be used to sync the selection of 2 or more EditPartViewers. + *

          + * This is changed from protected to public so that the outline can use it. + * + * @return the synchronizer + */ + @Override + public SelectionSynchronizer getSelectionSynchronizer() { + return super.getSelectionSynchronizer(); + } + + /** + * Returns the edit domain. + *

          + * This is changed from protected to public so that the outline can use it. + * + * @return the edit domain + */ + @Override + public DefaultEditDomain getEditDomain() { + return super.getEditDomain(); + } + + /** + * Creates a new layout file from the specificed {@link FolderConfiguration}. + */ + private void createAlternateLayout(final FolderConfiguration config) { + new Job("Create Alternate Resource") { + @Override + protected IStatus run(IProgressMonitor monitor) { + // get the folder name + String folderName = config.getFolderName(ResourceFolderType.LAYOUT); + try { + + // look to see if it exists. + // get the res folder + IFolder res = (IFolder)mEditedFile.getParent().getParent(); + String path = res.getLocation().toOSString(); + + File newLayoutFolder = new File(path + File.separator + folderName); + if (newLayoutFolder.isFile()) { + // this should not happen since aapt would have complained + // before, but if one disable the automatic build, this could + // happen. + String message = String.format("File 'res/%1$s' is in the way!", + folderName); + + AdtPlugin.displayError("Layout Creation", message); + + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, message); + } else if (newLayoutFolder.exists() == false) { + // create it. + newLayoutFolder.mkdir(); + } + + // now create the file + File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() + + File.separator + mEditedFile.getName()); + + newLayoutFile.createNewFile(); + + InputStream input = mEditedFile.getContents(); + + FileOutputStream fos = new FileOutputStream(newLayoutFile); + + byte[] data = new byte[512]; + int count; + while ((count = input.read(data)) != -1) { + fos.write(data, 0, count); + } + + input.close(); + fos.close(); + + // refreshes the res folder to show up the new + // layout folder (if needed) and the file. + // We use a progress monitor to catch the end of the refresh + // to trigger the edit of the new file. + res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() { + public void done() { + mCurrentConfig.set(config); + mParent.getDisplay().asyncExec(new Runnable() { + public void run() { + onConfigurationChange(); + } + }); + } + + public void beginTask(String name, int totalWork) { + // pass + } + + public void internalWorked(double work) { + // pass + } + + public boolean isCanceled() { + // pass + return false; + } + + public void setCanceled(boolean value) { + // pass + } + + public void setTaskName(String name) { + // pass + } + + public void subTask(String name) { + // pass + } + + public void worked(int work) { + // pass + } + }); + } catch (IOException e2) { + String message = String.format( + "Failed to create File 'res/%1$s/%2$s' : %3$s", + folderName, mEditedFile.getName(), e2.getMessage()); + + AdtPlugin.displayError("Layout Creation", message); + + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + message, e2); + } catch (CoreException e2) { + String message = String.format( + "Failed to create File 'res/%1$s/%2$s' : %3$s", + folderName, mEditedFile.getName(), e2.getMessage()); + + AdtPlugin.displayError("Layout Creation", message); + + return e2.getStatus(); + } + + return Status.OK_STATUS; + + } + }.schedule(); + } + + /** + * Returns a {@link ProjectResources} for the framework resources. + * @return the framework resources or null if not found. + */ + private ProjectResources getFrameworkResources() { + if (mEditedFile != null) { + Sdk currentSdk = Sdk.getCurrent(); + if (currentSdk != null) { + IAndroidTarget target = currentSdk.getTarget(mEditedFile.getProject()); + + if (target != null) { + AndroidTargetData data = currentSdk.getTargetData(target); + + if (data != null) { + return data.getFrameworkResources(); + } + } + } + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutConstants.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutConstants.java new file mode 100644 index 0000000..d4ec5e1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutConstants.java @@ -0,0 +1,65 @@ +/* + * 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.editors.layout; + +/** + * A bunch of constants that map to either: + *

            + *
          • Android Layouts XML element names (Linear, Relative, Absolute, etc.) + *
          • Attributes for layout XML elements. + *
          • Values for attributes. + *
          + */ +public class LayoutConstants { + + public static final String RELATIVE_LAYOUT = "RelativeLayout"; //$NON-NLS-1$ + public static final String LINEAR_LAYOUT = "LinearLayout"; //$NON-NLS-1$ + public static final String ABSOLUTE_LAYOUT = "AbsoluteLayout"; //$NON-NLS-1$ + + public static final String ATTR_TEXT = "text"; //$NON-NLS-1$ + public static final String ATTR_ID = "id"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_HEIGHT = "layout_height"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_WIDTH = "layout_width"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_ALIGN_PARENT_TOP = "layout_alignParentTop"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_BOTTOM = "layout_alignParentBottom"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_LEFT = "layout_alignParentLeft";//$NON-NLS-1$ + public static final String ATTR_LAYOUT_ALIGN_PARENT_RIGHT = "layout_alignParentRight"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_ALIGN_BASELINE = "layout_alignBaseline"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_CENTER_VERTICAL = "layout_centerVertical"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_CENTER_HORIZONTAL = "layout_centerHorizontal"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_TO_RIGHT_OF = "layout_toRightOf"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_TO_LEFT_OF = "layout_toLeftOf"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_BELOW = "layout_below"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_ABOVE = "layout_above"; //$NON-NLS-1$ + + public static final String ATTR_LAYOUT_Y = "layout_y"; //$NON-NLS-1$ + public static final String ATTR_LAYOUT_X = "layout_x"; //$NON-NLS-1$ + + public static final String VALUE_WRAP_CONTENT = "wrap_content"; //$NON-NLS-1$ + public static final String VALUE_FILL_PARENT = "fill_parent"; //$NON-NLS-1$ + public static final String VALUE_TRUE = "true"; //$NON-NLS-1$ + public static final String VALUE_N_DIP = "%ddip"; //$NON-NLS-1$ + + private LayoutConstants() { + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java new file mode 100644 index 0000000..9f39495 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 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.editors.layout; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidContentAssist; + +/** + * Content Assist Processor for /res/layout XML files + */ +class LayoutContentAssist extends AndroidContentAssist { + + /** + * Constructor for LayoutContentAssist + */ + public LayoutContentAssist() { + super(AndroidTargetData.DESCRIPTOR_LAYOUT); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java new file mode 100644 index 0000000..c4a8f5c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java @@ -0,0 +1,140 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** + * Dialog to choose a non existing {@link FolderConfiguration}. + */ +class LayoutCreatorDialog extends TrayDialog { + + private ConfigurationSelector mSelector; + private Composite mStatusComposite; + private Label mStatusLabel; + private Label mStatusImage; + + private final FolderConfiguration mConfig = new FolderConfiguration(); + private final String mFileName; + + /** + * Creates a dialog, and init the UI from a {@link FolderConfiguration}. + * @param parentShell the parent {@link Shell}. + * @param config The starting configuration. + */ + LayoutCreatorDialog(Shell parentShell, String fileName, FolderConfiguration config) { + super(parentShell); + + mFileName = fileName; + // FIXME: add some data to know what configurations already exist. + mConfig.set(config); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = new Composite(parent, SWT.NONE); + top.setLayoutData(new GridData()); + top.setLayout(new GridLayout(1, false)); + + new Label(top, SWT.NONE).setText( + String.format("Configuration for the alternate version of %1$s", mFileName)); + + mSelector = new ConfigurationSelector(top); + mSelector.setConfiguration(mConfig); + + // parent's layout is a GridLayout as specified in the javadoc. + GridData gd = new GridData(); + gd.widthHint = ConfigurationSelector.WIDTH_HINT; + gd.heightHint = ConfigurationSelector.HEIGHT_HINT; + mSelector.setLayoutData(gd); + + // add a listener to check on the validity of the FolderConfiguration as + // they are built. + mSelector.setOnChangeListener(new Runnable() { + public void run() { + ConfigurationState state = mSelector.getState(); + + switch (state) { + case OK: + mSelector.getConfiguration(mConfig); + + resetStatus(); + mStatusImage.setImage(null); + getButton(IDialogConstants.OK_ID).setEnabled(true); + break; + case INVALID_CONFIG: + ResourceQualifier invalidQualifier = mSelector.getInvalidQualifier(); + mStatusLabel.setText(String.format( + "Invalid Configuration: %1$s has no filter set.", + invalidQualifier.getName())); + mStatusImage.setImage(IconFactory.getInstance().getIcon("warning")); //$NON-NLS-1$ + getButton(IDialogConstants.OK_ID).setEnabled(false); + break; + case REGION_WITHOUT_LANGUAGE: + mStatusLabel.setText( + "The Region qualifier requires the Language qualifier."); + mStatusImage.setImage(IconFactory.getInstance().getIcon("warning")); //$NON-NLS-1$ + getButton(IDialogConstants.OK_ID).setEnabled(false); + break; + } + + // need to relayout, because of the change in size in mErrorImage. + mStatusComposite.layout(); + } + }); + + mStatusComposite = new Composite(top, SWT.NONE); + mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + GridLayout gl = new GridLayout(2, false); + mStatusComposite.setLayout(gl); + gl.marginHeight = gl.marginWidth = 0; + + mStatusImage = new Label(mStatusComposite, SWT.NONE); + mStatusLabel = new Label(mStatusComposite, SWT.NONE); + mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + resetStatus(); + + return top; + } + + public void getConfiguration(FolderConfiguration config) { + config.set(mConfig); + } + + /** + * resets the status label to show the file that will be created. + */ + private void resetStatus() { + mStatusLabel.setText(String.format("New File: res/%1$s/%2$s", + mConfig.getFolderName(ResourceFolderType.LAYOUT), mFileName)); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java new file mode 100644 index 0000000..880ee2b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2007 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.editors.layout; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.EclipseUiHelper; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.ui.tree.UiActions; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.gef.ui.parts.TreeViewer; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IPartListener; +import org.eclipse.ui.IShowEditorInput; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; +import org.eclipse.ui.views.properties.IPropertySheetPage; +import org.w3c.dom.Document; + +/** + * Multi-page form editor for /res/layout XML files. + */ +public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPartListener { + + public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".layout.LayoutEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiDocumentNode mUiRootNode; + + private GraphicalLayoutEditor mGraphicalEditor; + private int mGraphicalEditorIndex; + /** Implementation of the {@link IContentOutlinePage} for this editor */ + private UiContentOutlinePage mOutline; + /** Custom implementation of {@link IPropertySheetPage} for this editor */ + private UiPropertySheetPage mPropertyPage; + + private UiEditorActions mUiEditorActions; + + /** + * Creates the form editor for resources XML files. + */ + public LayoutEditor() { + super(); + } + + /** + * @return The root node of the UI element hierarchy + */ + @Override + public UiDocumentNode getUiRootNode() { + return mUiRootNode; + } + + // ---- Base Class Overrides ---- + + @Override + public void dispose() { + getSite().getPage().removePartListener(this); + + super.dispose(); + } + + /** + * Save the XML. + *

          + * The actual save operation is done in the super class by committing + * all data to the XML model and then having the Structured XML Editor + * save the XML. + *

          + * Here we just need to tell the graphical editor that the model has + * been saved. + */ + @Override + public void doSave(IProgressMonitor monitor) { + super.doSave(monitor); + if (mGraphicalEditor != null) { + mGraphicalEditor.doSave(monitor); + } + } + + /** + * Returns whether the "save as" operation is supported by this editor. + *

          + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Create the various form pages. + */ + @Override + protected void createFormPages() { + try { + // The graphical layout editor is now enabled by default. + // In case there's an issue we provide a way to disable it using an + // env variable. + if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) { + if (mGraphicalEditor == null) { + mGraphicalEditor = new GraphicalLayoutEditor(this); + mGraphicalEditorIndex = addPage(mGraphicalEditor, getEditorInput()); + setPageText(mGraphicalEditorIndex, mGraphicalEditor.getTitle()); + } else { + mGraphicalEditor.reloadEditor(); + } + + // update the config based on the opened file. + IEditorInput input = getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder( + fileInput.getFile()); + if (resFolder != null) { + mGraphicalEditor.editNewFile(resFolder.getConfiguration()); + } + } + + // put in place the listener to handle layout recompute only when needed. + getSite().getPage().addPartListener(this); + } + } catch (PartInitException e) { + AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ + } + } + + /* (non-java doc) + * Change the tab/title name to include the name of the layout. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + handleNewInput(input); + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.part.EditorPart#setInputWithNotify(org.eclipse.ui.IEditorInput) + */ + @Override + protected void setInputWithNotify(IEditorInput input) { + super.setInputWithNotify(input); + handleNewInput(input); + } + + /** + * Called to replace the current {@link IEditorInput} with another one. + *

          This is used when {@link MatchingStrategy} returned true which means we're + * opening a different configuration of the same layout. + */ + public void showEditorInput(IEditorInput editorInput) { + // save the current editor input. + doSave(new NullProgressMonitor()); + + // get the current page + int currentPage = getActivePage(); + + // remove the pages, except for the graphical editor, which will be dynamically adapted + // to the new model. + // page after the graphical editor: + int count = getPageCount(); + for (int i = count - 1 ; i > mGraphicalEditorIndex ; i--) { + removePage(i); + } + // pages before the graphical editor + for (int i = mGraphicalEditorIndex - 1 ; i >= 0 ; i--) { + removePage(i); + } + + // set the current input. + setInputWithNotify(editorInput); + + // re-create or reload the pages with the default page shown as the previous active page. + createAndroidPages(); + selectDefaultPage(Integer.toString(currentPage)); + + // update the outline + if (mOutline != null && mGraphicalEditor != null) { + mOutline.reloadModel(); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // init the ui root on demand + initUiRootNode(false /*force*/); + + mUiRootNode.loadFromXmlNode(xml_doc); + + // update the model first, since it is used by the viewers. + super.xmlModelChanged(xml_doc); + + if (mGraphicalEditor != null) { + mGraphicalEditor.onXmlModelChanged(); + } + + if (mOutline != null) { + mOutline.reloadModel(); + } + } + + /* (non-java doc) + * Returns the IContentOutlinePage when asked for it. + */ + @SuppressWarnings("unchecked") + @Override + public Object getAdapter(Class adapter) { + // for the outline, force it to come from the Graphical Editor. + // This fixes the case where a layout file is opened in XML view first and the outline + // gets stuck in the XML outline. + if (IContentOutlinePage.class == adapter && mGraphicalEditor != null) { + if (mOutline == null) { + mOutline = new UiContentOutlinePage(mGraphicalEditor, new TreeViewer()); + } + + return mOutline; + } + + if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) { + if (mPropertyPage == null) { + mPropertyPage = new UiPropertySheetPage(); + } + + return mPropertyPage; + } + + // return default + return super.getAdapter(adapter); + } + + @Override + protected void pageChange(int newPageIndex) { + super.pageChange(newPageIndex); + + if (mGraphicalEditor != null && newPageIndex == mGraphicalEditorIndex) { + mGraphicalEditor.activated(); + } + } + + // ----- IPartListener Methods ---- + + public void partActivated(IWorkbenchPart part) { + if (part == this) { + if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) { + mGraphicalEditor.activated(); + } + } + } + + public void partBroughtToTop(IWorkbenchPart part) { + partActivated(part); + } + + public void partClosed(IWorkbenchPart part) { + // pass + } + + public void partDeactivated(IWorkbenchPart part) { + if (part == this) { + if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) { + mGraphicalEditor.deactivated(); + } + } + } + + public void partOpened(IWorkbenchPart part) { + EclipseUiHelper.showView(EclipseUiHelper.CONTENT_OUTLINE_VIEW_ID, false /* activate */); + EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, false /* activate */); + } + + public class UiEditorActions extends UiActions { + + @Override + protected UiDocumentNode getRootNode() { + return mUiRootNode; + } + + // Select the new item + @Override + protected void selectUiNode(UiElementNode uiNodeToSelect) { + mGraphicalEditor.selectModel(uiNodeToSelect); + } + + @Override + public void commitPendingXmlChanges() { + // Pass. There is nothing to commit before the XML is changed here. + } + } + + public UiEditorActions getUiEditorActions() { + if (mUiEditorActions == null) { + mUiEditorActions = new UiEditorActions(); + } + return mUiEditorActions; + } + + // ---- Local Methods ---- + + /** + * Returns true if the Graphics editor page is visible. + * This must be called from the UI thread. + */ + boolean isGraphicalEditorActive() { + IWorkbenchPartSite workbenchSite = getSite(); + IWorkbenchPage workbenchPage = workbenchSite.getPage(); + + // check if the editor is visible in the workbench page + if (workbenchPage.isPartVisible(this)) { + // and then if the page of the editor is visible (not to be confused with + // the workbench page) + return mGraphicalEditorIndex == getActivePage(); + } + + return false; + } + + @Override + protected void initUiRootNode(boolean force) { + // The root UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + // get the target data from the opened file (and its project) + AndroidTargetData data = getTargetData(); + + Document doc = null; + if (mUiRootNode != null) { + doc = mUiRootNode.getXmlDocument(); + } + + DocumentDescriptor desc; + if (data == null) { + desc = new DocumentDescriptor("temp", null /*children*/); + } else { + desc = data.getLayoutDescriptors().getDescriptor(); + } + + // get the descriptors from the data. + mUiRootNode = (UiDocumentNode) desc.createUiNode(); + mUiRootNode.setEditor(this); + + onDescriptorsChanged(doc); + } + } + + private void onDescriptorsChanged(Document document) { + if (document != null) { + mUiRootNode.loadFromXmlNode(document); + } else { + mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlDocument()); + } + + if (mOutline != null) { + mOutline.reloadModel(); + } + + if (mGraphicalEditor != null) { + mGraphicalEditor.reloadEditor(); + mGraphicalEditor.reloadPalette(); + mGraphicalEditor.recomputeLayout(); + } + } + + /** + * Handles a new input, and update the part name. + * @param input the new input. + */ + private void handleNewInput(IEditorInput input) { + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", + file.getName())); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java new file mode 100644 index 0000000..cf20288 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java @@ -0,0 +1,214 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceDelta; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Monitor for file changes triggering a layout redraw. + */ +public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener { + + // singleton, enforced by private constructor. + private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor(); + + /** + * Map of listeners by IProject. + */ + private final Map> mListenerMap = + new HashMap>(); + + private final static int CHANGE_CODE = 0; + private final static int CHANGE_RESOURCES = 1; + private final static int CHANGE_R = 2; + private final static int CHANGE_COUNT = 3; + /** + * List of projects having received a file change. the boolean[] contains 3 values: + *

          • CHANGE_CODE: code change flag.
          • + *
          • CHANGE_RESOURCES: resource change flag.
          • + *
          • CHANGE_R: R clas change flag
          + */ + private final Map mChangedProjects = new HashMap(); + + /** + * Classes which implement this interface provide a method to respond to resource changes + * triggering a layout redraw + */ + public interface ILayoutReloadListener { + /** + * Sent when the layout needs to be redrawn + * @param codeChange The trigger happened due to a code change. + * @param rChange The trigger happened due to a change in the R class. + * @param resChange The trigger happened due to a resource change. + */ + void reloadLayout(boolean codeChange, boolean rChange, boolean resChange); + } + + /** + * Returns the single instance of {@link LayoutReloadMonitor}. + */ + public static LayoutReloadMonitor getMonitor() { + return sThis; + } + + private LayoutReloadMonitor() { + ResourceMonitor monitor = ResourceMonitor.getMonitor(); + monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED); + monitor.addResourceEventListener(this); + } + + /** + * Adds a listener for a given {@link IProject}. + * @param project + * @param listener + */ + public void addListener(IProject project, ILayoutReloadListener listener) { + synchronized (mListenerMap) { + List list = mListenerMap.get(project); + if (list == null) { + list = new ArrayList(); + mListenerMap.put(project, list); + } + + list.add(listener); + } + } + + /** + * Removes a listener for a given {@link IProject}. + * @param project + * @param listener + */ + public void removeListener(IProject project, ILayoutReloadListener listener) { + synchronized (mListenerMap) { + List list = mListenerMap.get(project); + if (list != null) { + list.remove(listener); + } + } + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int) + * + * Callback for ResourceMonitor.IFileListener. Called when a file changed. + * This records the changes for each project, but does not notify listeners. + * @see #resourceChangeEventEnd + */ + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + // get the file project + IProject project = file.getProject(); + + // if this project has already been marked as modified, we do nothing. + boolean[] changeFlags = mChangedProjects.get(project); + if (changeFlags != null && changeFlags[CHANGE_CODE] && changeFlags[CHANGE_RESOURCES] && + changeFlags[CHANGE_R]) { + return; + } + + // now check that the file is *NOT* a layout file (those automatically trigger a layout + // reload and we don't want to do it twice. + ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); + if (resFolder != null) { + if (resFolder.getType() != ResourceFolderType.LAYOUT) { + // this is a resource change! + if (changeFlags == null) { + changeFlags = new boolean[CHANGE_COUNT]; + mChangedProjects.put(project, changeFlags); + } + + changeFlags[CHANGE_RESOURCES] = true; + } + } else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) { + if (file.getName().matches("R[\\$\\.](.*)")) { + // this is a R change! + if (changeFlags == null) { + changeFlags = new boolean[CHANGE_COUNT]; + mChangedProjects.put(project, changeFlags); + } + + changeFlags[CHANGE_R] = true; + } else { + // this is a code change! + if (changeFlags == null) { + changeFlags = new boolean[CHANGE_COUNT]; + mChangedProjects.put(project, changeFlags); + } + + changeFlags[CHANGE_CODE] = true; + } + } + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart() + * + * Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource + * change event. This is called once, while fileChanged can be called several times. + * + */ + public void resourceChangeEventStart() { + // nothing to be done here, it all happens in the resourceChangeEventEnd + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd() + * + * Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource + * change event. This is where we notify the listeners. + */ + public void resourceChangeEventEnd() { + // for each IProject that was changed, we notify all the listeners. + synchronized (mListenerMap) { + for (Entry project : mChangedProjects.entrySet()) { + List listeners = mListenerMap.get(project.getKey()); + + boolean[] flags = project.getValue(); + + if (listeners != null) { + for (ILayoutReloadListener listener : listeners) { + listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R], + flags[CHANGE_RESOURCES]); + } + } + } + } + + // empty the list. + mChangedProjects.clear(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java new file mode 100644 index 0000000..1aa1f4c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * 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.editors.layout; + + +import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in LayoutContentAssist. + */ +public class LayoutSourceViewerConfig extends AndroidSourceViewerConfig { + + public LayoutSourceViewerConfig() { + super(new LayoutContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java new file mode 100644 index 0000000..bb075c2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java @@ -0,0 +1,64 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorMatchingStrategy; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; + +/** + * Matching strategy for the Layout Editor. This is used to open all configurations of a layout + * in the same editor. + */ +public class MatchingStrategy implements IEditorMatchingStrategy { + + public boolean matches(IEditorReference editorRef, IEditorInput input) { + // first check that the file being opened is a layout file. + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + + // get the IFile object and check it's in one of the layout folders. + IFile iFile = fileInput.getFile(); + ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile); + + // if it's a layout, we know check the name of the fileInput against the name of the + // file being currently edited by the editor since those are independent of the config. + if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT) { + try { + IEditorInput editorInput = editorRef.getEditorInput(); + if (editorInput instanceof FileEditorInput) { + FileEditorInput editorFileInput = (FileEditorInput)editorInput; + IFile editorIFile = editorFileInput.getFile(); + + return editorIFile.getProject().equals(iFile.getProject()) + && editorIFile.getName().equals(iFile.getName()); + } + } catch (PartInitException e) { + // we do nothing, we'll just return false. + } + } + } + return false; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java new file mode 100644 index 0000000..94df28f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java @@ -0,0 +1,93 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; + +import org.eclipse.gef.palette.PaletteDrawer; +import org.eclipse.gef.palette.PaletteGroup; +import org.eclipse.gef.palette.PaletteRoot; +import org.eclipse.gef.palette.PaletteTemplateEntry; + +import java.util.List; + +/** + * Factory that creates the palette for the {@link GraphicalLayoutEditor}. + */ +public class PaletteFactory { + + /** Static factory, nothing to instantiate here. */ + private PaletteFactory() { + } + + public static PaletteRoot createPaletteRoot(PaletteRoot currentPalette, + AndroidTargetData targetData) { + + if (currentPalette == null) { + currentPalette = new PaletteRoot(); + } + + for (int n = currentPalette.getChildren().size() - 1; n >= 0; n--) { + currentPalette.getChildren().remove(n); + } + + if (targetData != null) { + addTools(currentPalette); + addViews(currentPalette, "Layouts", + targetData.getLayoutDescriptors().getLayoutDescriptors()); + addViews(currentPalette, "Views", + targetData.getLayoutDescriptors().getViewDescriptors()); + } + + return currentPalette; + } + + private static void addTools(PaletteRoot paletteRoot) { + PaletteGroup group = new PaletteGroup("Tools"); + + // Default tools: selection. + // Do not use the MarqueeToolEntry since we don't support multiple selection. + /* -- Do not put the selection tool. It's the unique tool so it looks useless. + Leave this piece of code here in case we want it back later. + PanningSelectionToolEntry entry = new PanningSelectionToolEntry(); + group.add(entry); + paletteRoot.setDefaultEntry(entry); + */ + + paletteRoot.add(group); + } + + private static void addViews(PaletteRoot paletteRoot, String groupName, + List descriptors) { + PaletteDrawer group = new PaletteDrawer(groupName); + + for (ElementDescriptor desc : descriptors) { + PaletteTemplateEntry entry = new PaletteTemplateEntry( + desc.getUiName(), // label + desc.getTooltip(), // short description + desc, // template + desc.getImageDescriptor(), // small icon + desc.getImageDescriptor() // large icon + ); + + group.add(entry); + } + + paletteRoot.add(group); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java new file mode 100644 index 0000000..81fd2ed --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java @@ -0,0 +1,161 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.editors.resources.manager.ProjectClassLoader; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.layoutlib.api.IProjectCallback; + +import org.eclipse.core.resources.IProject; + +import java.lang.reflect.Constructor; +import java.util.HashMap; + +/** + * Loader for Android Project class in order to use them in the layout editor. + */ +public final class ProjectCallback implements IProjectCallback { + + private final HashMap> mLoadedClasses = new HashMap>(); + private final IProject mProject; + private final ClassLoader mParentClassLoader; + private final ProjectResources mProjectRes; + private boolean mUsed = false; + private String mNamespace; + + ProjectCallback(ClassLoader classLoader, ProjectResources projectRes, IProject project) { + mParentClassLoader = classLoader; + mProjectRes = projectRes; + mProject = project; + } + + + /** + * {@inheritDoc} + * + * This implementation goes through the output directory of the Eclipse project and loads the + * .class file directly. + */ + @SuppressWarnings("unchecked") + public Object loadView(String className, Class[] constructorSignature, + Object[] constructorParameters) + throws ClassNotFoundException, Exception { + + // look for a cached version + Class clazz = mLoadedClasses.get(className); + if (clazz != null) { + return instantiateClass(clazz, constructorSignature, constructorParameters); + } + + // load the class. + ProjectClassLoader loader = new ProjectClassLoader(mParentClassLoader, mProject); + try { + clazz = loader.loadClass(className); + + if (clazz != null) { + mUsed = true; + mLoadedClasses.put(className, clazz); + return instantiateClass(clazz, constructorSignature, constructorParameters); + } + } catch (Error e) { + // Log this error with the class name we're trying to load and abort. + AdtPlugin.log(e, "ProjectCallback.loadView failed to find class %1$s", className); //$NON-NLS-1$ + } + + return null; + } + + /** + * {@inheritDoc} + * + * Returns the namespace for the project. The namespace contains a standard part + the + * application package. + */ + public String getNamespace() { + if (mNamespace == null) { + AndroidManifestHelper manifest = new AndroidManifestHelper(mProject); + String javaPackage = manifest.getPackageName(); + + mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage); + } + + return mNamespace; + } + + /* + * (non-Javadoc) + * @see com.android.layoutlib.api.IProjectCallback#resolveResourceValue(int) + */ + public String[] resolveResourceValue(int id) { + if (mProjectRes != null) { + return mProjectRes.resolveResourceValue(id); + } + + return null; + } + + /* + * (non-Javadoc) + * @see com.android.layoutlib.api.IProjectCallback#resolveResourceValue(int[]) + */ + public String resolveResourceValue(int[] id) { + if (mProjectRes != null) { + return mProjectRes.resolveResourceValue(id); + } + + return null; + } + + /* + * (non-Javadoc) + * @see com.android.layoutlib.api.IProjectCallback#getResourceValue(java.lang.String, java.lang.String) + */ + public Integer getResourceValue(String type, String name) { + if (mProjectRes != null) { + return mProjectRes.getResourceValue(type, name); + } + + return null; + } + + /** + * Returns whether the loader has received requests to load custom views. + *

          This allows to efficiently only recreate when needed upon code change in the project. + */ + boolean isUsed() { + return mUsed; + } + + /** + * Instantiate a class object, using a specific constructor and parameters. + * @param clazz the class to instantiate + * @param constructorSignature the signature of the constructor to use + * @param constructorParameters the parameters to use in the constructor. + * @return A new class object, created using a specific constructor and parameters. + * @throws Exception + */ + @SuppressWarnings("unchecked") + private Object instantiateClass(Class clazz, Class[] constructorSignature, + Object[] constructorParameters) throws Exception { + Constructor constructor = clazz.getConstructor(constructorSignature); + constructor.setAccessible(true); + return constructor.newInstance(constructorParameters); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java new file mode 100644 index 0000000..3e0f5d8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java @@ -0,0 +1,615 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.editors.layout; + +import com.android.ide.eclipse.common.EclipseUiHelper; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.layout.parts.UiDocumentTreeEditPart; +import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPart; +import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPartFactory; +import com.android.ide.eclipse.editors.layout.parts.UiLayoutTreeEditPart; +import com.android.ide.eclipse.editors.layout.parts.UiViewTreeEditPart; +import com.android.ide.eclipse.editors.ui.tree.CopyCutAction; +import com.android.ide.eclipse.editors.ui.tree.PasteAction; +import com.android.ide.eclipse.editors.ui.tree.UiActions; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.gef.EditPartViewer; +import org.eclipse.gef.ui.parts.ContentOutlinePage; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.IActionBars; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}. + */ +class UiContentOutlinePage extends ContentOutlinePage { + + private GraphicalLayoutEditor mEditor; + + private Action mAddAction; + private Action mDeleteAction; + private Action mUpAction; + private Action mDownAction; + + private UiOutlineActions mUiActions = new UiOutlineActions(); + + public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) { + super(viewer); + mEditor = editor; + IconFactory factory = IconFactory.getInstance(); + + mAddAction = new Action("Add...") { + @Override + public void run() { + List nodes = getModelSelections(); + UiElementNode node = nodes != null && nodes.size() > 0 ? nodes.get(0) : null; + + mUiActions.doAdd(node, viewer.getControl().getShell()); + } + }; + mAddAction.setToolTipText("Adds a new element."); + mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$ + + mDeleteAction = new Action("Remove...") { + @Override + public void run() { + List nodes = getModelSelections(); + + mUiActions.doRemove(nodes, viewer.getControl().getShell()); + } + }; + mDeleteAction.setToolTipText("Removes an existing selected element."); + mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$ + + mUpAction = new Action("Up") { + @Override + public void run() { + List nodes = getModelSelections(); + + mUiActions.doUp(nodes); + } + }; + mUpAction.setToolTipText("Moves the selected element up"); + mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$ + + mDownAction = new Action("Down") { + @Override + public void run() { + List nodes = getModelSelections(); + + mUiActions.doDown(nodes); + } + }; + mDownAction.setToolTipText("Moves the selected element down"); + mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$ + + // all actions disabled by default. + mAddAction.setEnabled(false); + mDeleteAction.setEnabled(false); + mUpAction.setEnabled(false); + mDownAction.setEnabled(false); + + addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + + // the selection is never empty. The least it'll contain is the + // UiDocumentTreeEditPart object. + if (selection instanceof StructuredSelection) { + StructuredSelection structSel = (StructuredSelection)selection; + + if (structSel.size() == 1 && + structSel.getFirstElement() instanceof UiDocumentTreeEditPart) { + mDeleteAction.setEnabled(false); + mUpAction.setEnabled(false); + mDownAction.setEnabled(false); + } else { + mDeleteAction.setEnabled(true); + mUpAction.setEnabled(true); + mDownAction.setEnabled(true); + } + + // the "add" button is always enabled, in order to be able to set the + // initial root node + mAddAction.setEnabled(true); + } + } + }); + } + + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) + */ + @Override + public void createControl(Composite parent) { + // create outline viewer page + getViewer().createControl(parent); + + // configure outline viewer + getViewer().setEditPartFactory(new UiElementTreeEditPartFactory()); + + setupOutline(); + setupContextMenu(); + setupTooltip(); + setupDoubleClick(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars) + * + * Called automatically after createControl + */ + @Override + public void setActionBars(IActionBars actionBars) { + IToolBarManager toolBarManager = actionBars.getToolBarManager(); + toolBarManager.add(mAddAction); + toolBarManager.add(mDeleteAction); + toolBarManager.add(new Separator()); + toolBarManager.add(mUpAction); + toolBarManager.add(mDownAction); + + IMenuManager menuManager = actionBars.getMenuManager(); + menuManager.add(mAddAction); + menuManager.add(mDeleteAction); + menuManager.add(new Separator()); + menuManager.add(mUpAction); + menuManager.add(mDownAction); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#dispose() + */ + @Override + public void dispose() { + breakConnectionWithEditor(); + + // dispose + super.dispose(); + } + + /* (non-Javadoc) + * @see org.eclipse.ui.part.IPage#getControl() + */ + @Override + public Control getControl() { + return getViewer().getControl(); + } + + void setNewEditor(GraphicalLayoutEditor editor) { + mEditor = editor; + setupOutline(); + } + + void breakConnectionWithEditor() { + // unhook outline viewer + mEditor.getSelectionSynchronizer().removeViewer(getViewer()); + } + + private void setupOutline() { + getViewer().setEditDomain(mEditor.getEditDomain()); + + // hook outline viewer + mEditor.getSelectionSynchronizer().addViewer(getViewer()); + + // initialize outline viewer with model + getViewer().setContents(mEditor.getModel()); + } + + private void setupContextMenu() { + MenuManager menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + menuManager.addMenuListener(new IMenuListener() { + /** + * The menu is about to be shown. The menu manager has already been + * requested to remove any existing menu item. This method gets the + * tree selection and if it is of the appropriate type it re-creates + * the necessary actions. + */ + public void menuAboutToShow(IMenuManager manager) { + List selected = getModelSelections(); + + if (selected != null) { + doCreateMenuAction(manager, selected); + return; + } + doCreateMenuAction(manager, null /* ui_node */); + } + }); + Control control = getControl(); + Menu contextMenu = menuManager.createContextMenu(control); + control.setMenu(contextMenu); + } + + /** + * Adds the menu actions to the context menu when the given UI node is selected in + * the tree view. + * + * @param manager The context menu manager + * @param selected The UI node selected in the tree. Can be null, in which case the root + * is to be modified. + */ + private void doCreateMenuAction(IMenuManager manager, List selected) { + + if (selected != null) { + boolean hasXml = false; + for (UiElementNode uiNode : selected) { + if (uiNode.getXmlNode() != null) { + hasXml = true; + break; + } + } + + if (hasXml) { + manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(), + null, selected, true /* cut */)); + manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(), + null, selected, false /* cut */)); + + // Can't paste with more than one element selected (the selection is the target) + if (selected.size() <= 1) { + // Paste is not valid if it would add a second element on a terminal element + // which parent is a document -- an XML document can only have one child. This + // means paste is valid if the current UI node can have children or if the parent + // is not a document. + UiElementNode ui_root = selected.get(0).getUiRoot(); + if (ui_root.getDescriptor().hasChildren() || + !(ui_root.getUiParent() instanceof UiDocumentNode)) { + manager.add(new PasteAction(mEditor.getLayoutEditor(), + mEditor.getClipboard(), + selected.get(0))); + } + } + manager.add(new Separator()); + } + } + + // Append "add" and "remove" actions. They do the same thing as the add/remove + // buttons on the side. + // + // "Add" makes sense only if there's 0 or 1 item selected since the + // one selected item becomes the target. + if (selected == null || selected.size() <= 1) { + manager.add(mAddAction); + } + + if (selected != null) { + manager.add(mDeleteAction); + manager.add(new Separator()); + + manager.add(mUpAction); + manager.add(mDownAction); + } + + if (selected != null && selected.size() == 1) { + manager.add(new Separator()); + + Action propertiesAction = new Action("Properties") { + @Override + public void run() { + EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, + true /* activate */); + } + }; + propertiesAction.setToolTipText("Displays properties of the selected element."); + manager.add(propertiesAction); + } + } + + /** + * Updates the outline view with the model of the {@link GraphicalLayoutEditor}. + *

          + * This attemps to preserve the selection, if any. + */ + public void reloadModel() { + // Attemps to preserve the UiNode selection, if any + List uiNodes = null; + try { + // get current selection using the model rather than the edit part as + // reloading the content may change the actual edit part. + uiNodes = getModelSelections(); + + // perform the update + getViewer().setContents(mEditor.getModel()); + + } finally { + // restore selection + if (uiNodes != null) { + setModelSelection(uiNodes.get(0)); + } + } + } + + /** + * Returns the currently selected element, if any, in the viewer. + * This returns the viewer's elements (i.e. an {@link UiElementTreeEditPart}) + * and not the underlying model node. + *

          + * When there is no actual selection, this might still return the root node, + * which is of type {@link UiDocumentTreeEditPart}. + */ + @SuppressWarnings("unchecked") + private List getViewerSelections() { + ISelection selection = getSelection(); + if (selection instanceof StructuredSelection) { + StructuredSelection structuredSelection = (StructuredSelection)selection; + + if (structuredSelection.size() > 0) { + ArrayList selected = new ArrayList(); + + for (Iterator it = structuredSelection.iterator(); it.hasNext(); ) { + Object selectedObj = it.next(); + + if (selectedObj instanceof UiElementTreeEditPart) { + selected.add((UiElementTreeEditPart) selectedObj); + } + } + + return selected.size() > 0 ? selected : null; + } + } + + return null; + } + + /** + * Returns the currently selected model element, which is either an + * {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}. + *

          + * Returns null if there is no selection or if the implicit root is "selected" + * (which actually represents the lack of a real element selection.) + */ + private List getModelSelections() { + + List parts = getViewerSelections(); + + if (parts != null) { + ArrayList selected = new ArrayList(); + + for (UiElementTreeEditPart part : parts) { + if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) { + selected.add((UiElementNode) part.getModel()); + } + } + + return selected.size() > 0 ? selected : null; + } + + return null; + } + + /** + * Selects the corresponding edit part in the tree viewer. + */ + private void setViewerSelection(UiElementTreeEditPart selectedPart) { + if (selectedPart != null && !(selectedPart instanceof UiDocumentTreeEditPart)) { + LinkedList segments = new LinkedList(); + for (UiElementTreeEditPart part = selectedPart; + !(part instanceof UiDocumentTreeEditPart); + part = (UiElementTreeEditPart) part.getParent()) { + segments.add(0, part); + } + setSelection(new TreeSelection(new TreePath(segments.toArray()))); + } + } + + /** + * Selects the corresponding model element in the tree viewer. + */ + private void setModelSelection(UiElementNode uiNodeToSelect) { + if (uiNodeToSelect != null) { + + // find an edit part that has the requested model element + UiElementTreeEditPart part = findPartForModel( + (UiElementTreeEditPart) getViewer().getContents(), + uiNodeToSelect); + + // if we found a part, select it and reveal it + if (part != null) { + setViewerSelection(part); + getViewer().reveal(part); + } + } + } + + /** + * Utility method that tries to find an edit part that matches a given model UI node. + * + * @param rootPart The root of the viewer edit parts + * @param uiNode The UI node model to find + * @return The part that matches the model or null if it's not in the sub tree. + */ + private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart, + UiElementNode uiNode) { + if (rootPart.getModel() == uiNode) { + return rootPart; + } + + for (Object part : rootPart.getChildren()) { + if (part instanceof UiElementTreeEditPart) { + UiElementTreeEditPart found = findPartForModel( + (UiElementTreeEditPart) part, uiNode); + if (found != null) { + return found; + } + } + } + + return null; + } + + /** + * Sets up a custom tooltip when hovering over tree items. + *

          + * The tooltip will display the element's javadoc, if any, or the item's getText otherwise. + */ + private void setupTooltip() { + final Tree tree = (Tree) getControl(); + + /* + * Reference: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup + */ + + final Listener listener = new Listener() { + Shell tip = null; + Label label = null; + + public void handleEvent(Event event) { + switch(event.type) { + case SWT.Dispose: + case SWT.KeyDown: + case SWT.MouseExit: + case SWT.MouseDown: + case SWT.MouseMove: + if (tip != null) { + tip.dispose(); + tip = null; + label = null; + } + break; + case SWT.MouseHover: + if (tip != null) { + tip.dispose(); + tip = null; + label = null; + } + + String tooltip = null; + + TreeItem item = tree.getItem(new Point(event.x, event.y)); + if (item != null) { + Object data = item.getData(); + if (data instanceof UiElementTreeEditPart) { + Object model = ((UiElementTreeEditPart) data).getModel(); + if (model instanceof UiElementNode) { + tooltip = ((UiElementNode) model).getDescriptor().getTooltip(); + } + } + + if (tooltip == null) { + tooltip = item.getText(); + } else { + tooltip = item.getText() + ":\r" + tooltip; + } + } + + + if (tooltip != null) { + Shell shell = tree.getShell(); + Display display = tree.getDisplay(); + + tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); + tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + FillLayout layout = new FillLayout(); + layout.marginWidth = 2; + tip.setLayout(layout); + label = new Label(tip, SWT.NONE); + label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + label.setData("_TABLEITEM", item); + label.setText(tooltip); + label.addListener(SWT.MouseExit, this); + label.addListener(SWT.MouseDown, this); + Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Rectangle rect = item.getBounds(0); + Point pt = tree.toDisplay(rect.x, rect.y); + tip.setBounds(pt.x, pt.y, size.x, size.y); + tip.setVisible(true); + } + } + } + }; + + tree.addListener(SWT.Dispose, listener); + tree.addListener(SWT.KeyDown, listener); + tree.addListener(SWT.MouseMove, listener); + tree.addListener(SWT.MouseHover, listener); + } + + /** + * Sets up double-click action on the tree. + *

          + * By default, double-click (a.k.a. "default selection") on a valid list item will + * show the property view. + */ + private void setupDoubleClick() { + final Tree tree = (Tree) getControl(); + + tree.addListener(SWT.DefaultSelection, new Listener() { + public void handleEvent(Event event) { + EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, + true /* activate */); + } + }); + } + + // --------------- + + private class UiOutlineActions extends UiActions { + + @Override + protected UiDocumentNode getRootNode() { + return mEditor.getModel(); // this is LayoutEditor.getUiRootNode() + } + + // Select the new item + @Override + protected void selectUiNode(UiElementNode uiNodeToSelect) { + setModelSelection(uiNodeToSelect); + } + + @Override + public void commitPendingXmlChanges() { + // Pass. There is nothing to commit before the XML is changed here. + } + + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java new file mode 100644 index 0000000..b0e6fdb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java @@ -0,0 +1,244 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.layoutlib.api.IXmlPullParser; + +import org.w3c.dom.Node; +import org.xmlpull.v1.XmlPullParserException; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@link IXmlPullParser} implementation on top of {@link UiElementNode}. + *

          It's designed to work on layout files, and will most likely not work on other resource + * files. + */ +public final class UiElementPullParser extends BasePullParser { + + private final ArrayList mNodeStack = new ArrayList(); + private UiElementNode mRoot; + + public UiElementPullParser(UiElementNode top) { + super(); + mRoot = top; + push(mRoot); + } + + private UiElementNode getCurrentNode() { + if (mNodeStack.size() > 0) { + return mNodeStack.get(mNodeStack.size()-1); + } + + return null; + } + + private Node getAttribute(int i) { + if (mParsingState != START_TAG) { + throw new IndexOutOfBoundsException(); + } + + // get the current uiNode + UiElementNode uiNode = getCurrentNode(); + + // get its xml node + Node xmlNode = uiNode.getXmlNode(); + + if (xmlNode != null) { + return xmlNode.getAttributes().item(i); + } + + return null; + } + + private void push(UiElementNode node) { + mNodeStack.add(node); + } + + private UiElementNode pop() { + return mNodeStack.remove(mNodeStack.size()-1); + } + + // ------------- IXmlPullParser -------- + + /** + * {@inheritDoc} + * + * This implementation returns the underlying DOM node. + */ + public Object getViewKey() { + return getCurrentNode(); + } + + // ------------- XmlPullParser -------- + + public String getPositionDescription() { + return "XML DOM element depth:" + mNodeStack.size(); + } + + public int getAttributeCount() { + UiElementNode node = getCurrentNode(); + if (node != null) { + return node.getUiAttributes().size(); + } + + return 0; + } + + public String getAttributeName(int i) { + Node attribute = getAttribute(i); + if (attribute != null) { + return attribute.getLocalName(); + } + + return null; + } + + public String getAttributeNamespace(int i) { + Node attribute = getAttribute(i); + if (attribute != null) { + return attribute.getNamespaceURI(); + } + return ""; //$NON-NLS-1$ + } + + public String getAttributePrefix(int i) { + Node attribute = getAttribute(i); + if (attribute != null) { + return attribute.getPrefix(); + } + return null; + } + + public String getAttributeValue(int i) { + Node attribute = getAttribute(i); + if (attribute != null) { + return attribute.getNodeValue(); + } + + return null; + } + + public String getAttributeValue(String namespace, String localName) { + // get the current uiNode + UiElementNode uiNode = getCurrentNode(); + + // get its xml node + Node xmlNode = uiNode.getXmlNode(); + + if (xmlNode != null) { + Node attribute = xmlNode.getAttributes().getNamedItemNS(namespace, localName); + if (attribute != null) { + return attribute.getNodeValue(); + } + } + + return null; + } + + public int getDepth() { + return mNodeStack.size(); + } + + public String getName() { + if (mParsingState == START_TAG || mParsingState == END_TAG) { + return getCurrentNode().getDescriptor().getXmlLocalName(); + } + + return null; + } + + public String getNamespace() { + if (mParsingState == START_TAG || mParsingState == END_TAG) { + return getCurrentNode().getDescriptor().getNamespace(); + } + + return null; + } + + public String getPrefix() { + if (mParsingState == START_TAG || mParsingState == END_TAG) { + // FIXME will NEVER work + if (getCurrentNode().getDescriptor().getXmlLocalName().startsWith("android:")) { //$NON-NLS-1$ + return "android"; //$NON-NLS-1$ + } + } + + return null; + } + + public boolean isEmptyElementTag() throws XmlPullParserException { + if (mParsingState == START_TAG) { + return getCurrentNode().getUiChildren().size() == 0; + } + + throw new XmlPullParserException("Call to isEmptyElementTag while not in START_TAG", + this, null); + } + + @Override + public void onNextFromStartDocument() { + onNextFromStartTag(); + } + + @Override + public void onNextFromStartTag() { + // get the current node, and look for text or children (children first) + UiElementNode node = getCurrentNode(); + List children = node.getUiChildren(); + if (children.size() > 0) { + // move to the new child, and don't change the state. + push(children.get(0)); + + // in case the current state is CURRENT_DOC, we set the proper state. + mParsingState = START_TAG; + } else { + if (mParsingState == START_DOCUMENT) { + // this handles the case where there's no node. + mParsingState = END_DOCUMENT; + } else { + mParsingState = END_TAG; + } + } + } + + @Override + public void onNextFromEndTag() { + // look for a sibling. if no sibling, go back to the parent + UiElementNode node = getCurrentNode(); + node = node.getUiNextSibling(); + if (node != null) { + // to go to the sibling, we need to remove the current node, + pop(); + // and add its sibling. + push(node); + mParsingState = START_TAG; + } else { + // move back to the parent + pop(); + + // we have only one element left (mRoot), then we're done with the document. + if (mNodeStack.size() == 1) { + mParsingState = END_DOCUMENT; + } else { + mParsingState = END_TAG; + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java new file mode 100644 index 0000000..8093c90 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java @@ -0,0 +1,139 @@ +/* + * 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.editors.layout; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.views.properties.PropertySheetEntry; +import org.eclipse.ui.views.properties.PropertySheetPage; + +/** + * A customized property sheet page for the graphical layout editor. + *

          + * Currently it just provides a custom tooltip to display attributes javadocs. + */ +public class UiPropertySheetPage extends PropertySheetPage { + + + public UiPropertySheetPage() { + super(); + } + + @Override + public void createControl(Composite parent) { + super.createControl(parent); + + setupTooltip(); + } + + /** + * Sets up a custom tooltip when hovering over tree items. + *

          + * The tooltip will display the element's javadoc, if any, or the item's getText otherwise. + */ + private void setupTooltip() { + final Tree tree = (Tree) getControl(); + + /* + * Reference: + * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup + */ + + final Listener listener = new Listener() { + Shell tip = null; + Label label = null; + + public void handleEvent(Event event) { + switch(event.type) { + case SWT.Dispose: + case SWT.KeyDown: + case SWT.MouseExit: + case SWT.MouseDown: + case SWT.MouseMove: + if (tip != null) { + tip.dispose(); + tip = null; + label = null; + } + break; + case SWT.MouseHover: + if (tip != null) { + tip.dispose(); + tip = null; + label = null; + } + + String tooltip = null; + + TreeItem item = tree.getItem(new Point(event.x, event.y)); + if (item != null) { + Object data = item.getData(); + if (data instanceof PropertySheetEntry) { + tooltip = ((PropertySheetEntry) data).getDescription(); + } + + if (tooltip == null) { + tooltip = item.getText(); + } else { + tooltip = item.getText() + ":\r" + tooltip; + } + } + + if (tooltip != null) { + Shell shell = tree.getShell(); + Display display = tree.getDisplay(); + + tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); + tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + FillLayout layout = new FillLayout(); + layout.marginWidth = 2; + tip.setLayout(layout); + label = new Label(tip, SWT.NONE); + label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); + label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); + label.setData("_TABLEITEM", item); + label.setText(tooltip); + label.addListener(SWT.MouseExit, this); + label.addListener(SWT.MouseDown, this); + Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); + Rectangle rect = item.getBounds(0); + Point pt = tree.toDisplay(rect.x, rect.y); + tip.setBounds(pt.x, pt.y, size.x, size.y); + tip.setVisible(true); + } + } + } + }; + + tree.addListener(SWT.Dispose, listener); + tree.addListener(SWT.KeyDown, listener); + tree.addListener(SWT.MouseMove, listener); + tree.addListener(SWT.MouseHover, listener); + + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/WidgetPullParser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/WidgetPullParser.java new file mode 100644 index 0000000..75d10ed --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/WidgetPullParser.java @@ -0,0 +1,142 @@ +/* + * 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.editors.layout; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; +import com.android.layoutlib.api.IXmlPullParser; + +import org.xmlpull.v1.XmlPullParserException; + +/** + * {@link IXmlPullParser} implementation to render android widget bitmap. + *

          The parser emulates a layout that contains just one widget, described by the + * {@link ViewElementDescriptor} passed in the constructor. + */ +public class WidgetPullParser extends BasePullParser { + + private final ViewElementDescriptor mDescriptor; + private String[][] mAttributes = new String[][] { + { "text", null }, + { "layout_width", "wrap_content" }, + { "layout_height", "wrap_content" }, + }; + + public WidgetPullParser(ViewElementDescriptor descriptor) { + mDescriptor = descriptor; + + String[] segments = mDescriptor.getCanonicalClassName().split(AndroidConstants.RE_DOT); + mAttributes[0][1] = segments[segments.length-1]; + } + + public Object getViewKey() { + // we need a viewKey or the ILayoutResult will not contain any ILayoutViewInfo + return mDescriptor; + } + + public int getAttributeCount() { + return mAttributes.length; // text attribute + } + + public String getAttributeName(int index) { + if (index < mAttributes.length) { + return mAttributes[index][0]; + } + + return null; + } + + public String getAttributeNamespace(int index) { + return AndroidConstants.NS_RESOURCES; + } + + public String getAttributePrefix(int index) { + // pass + return null; + } + + public String getAttributeValue(int index) { + if (index < mAttributes.length) { + return mAttributes[index][1]; + } + + return null; + } + + public String getAttributeValue(String ns, String name) { + if (AndroidConstants.NS_RESOURCES.equals(ns)) { + for (String[] attribute : mAttributes) { + if (name.equals(attribute[0])) { + return attribute[1]; + } + } + } + + return null; + } + + public int getDepth() { + // pass + return 0; + } + + public String getName() { + return mDescriptor.getXmlLocalName(); + } + + public String getNamespace() { + // pass + return null; + } + + public String getPositionDescription() { + // pass + return null; + } + + public String getPrefix() { + // pass + return null; + } + + public boolean isEmptyElementTag() throws XmlPullParserException { + if (mParsingState == START_TAG) { + return true; + } + + throw new XmlPullParserException("Call to isEmptyElementTag while not in START_TAG", + this, null); + } + + @Override + public void onNextFromStartDocument() { + // just go to start_tag + mParsingState = START_TAG; + } + + @Override + public void onNextFromStartTag() { + // since we have no children, just go to end_tag + mParsingState = END_TAG; + } + + @Override + public void onNextFromEndTag() { + // just one tag. we are done. + mParsingState = END_DOCUMENT; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java new file mode 100644 index 0000000..0f388f4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java @@ -0,0 +1,274 @@ +/* + * 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.editors.layout.descriptors; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IProject; +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.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; + +import java.util.HashMap; +import java.util.List; + +/** + * Service responsible for creating/managing {@link ElementDescriptor} objects for custom + * View classes per project. + *

          + * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring + * starts once a request for an {@link ElementDescriptor} object has been done for a specific + * class.
          + * The monitoring will notify a listen of any changes in the class triggering a change in its + * associated {@link ElementDescriptor} object. + *

          + * If the custom class does not exist, no monitoring is put in place to avoid having to listen + * to all class changes in the projects. + * + */ +public final class CustomViewDescriptorService { + + private static CustomViewDescriptorService sThis = new CustomViewDescriptorService(); + + /** + * Map where keys are the project, and values are another map containing all the known + * custom View class for this project. The custom View class are stored in a map + * where the keys are the fully qualified class name, and the values are their associated + * {@link ElementDescriptor}. + */ + private HashMap> mCustomDescriptorMap = + new HashMap>(); + + /** + * TODO will be used to update the ElementDescriptor of the custom view when it + * is modified (either the class itself or its attributes.xml) + */ + @SuppressWarnings("unused") + private ICustomViewDescriptorListener mListener; + + /** + * Classes which implements this interface provide a method that deal with modifications + * in custom View class triggering a change in its associated {@link ViewClassInfo} object. + */ + public interface ICustomViewDescriptorListener { + /** + * Sent when a custom View class has changed and its {@link ElementDescriptor} was modified. + * @param project the project containing the class. + * @param className the fully qualified class name. + * @param descriptor the updated ElementDescriptor. + */ + public void updatedClassInfo(IProject project, String className, ElementDescriptor descriptor); + } + + /** + * Returns the singleton instance of {@link CustomViewDescriptorService}. + */ + public static CustomViewDescriptorService getInstance() { + return sThis; + } + + /** + * Sets the listener receiving custom View class modification notifications. + * @param listener the listener to receive the notifications. + * + * TODO will be used to update the ElementDescriptor of the custom view when it + * is modified (either the class itself or its attributes.xml) + */ + public void setListener(ICustomViewDescriptorListener listener) { + mListener = listener; + } + + /** + * Returns the {@link ElementDescriptor} for a particular project/class. + *

          + * If it is the first time the ElementDescriptor is requested, the method + * will check that the specified class is in fact a custom View class. Once this is + * established, a monitoring for that particular class is initiated. Any change will + * trigger a notification to the {@link ICustomViewDescriptorListener}. + * @param project the project containing the class. + * @param fqClassName the fully qualified name of the class. + * @return a ElementDescriptor or null if the class was not + * a custom View class. + */ + public ElementDescriptor getDescriptor(IProject project, String fqClassName) { + // look in the map first + synchronized (mCustomDescriptorMap) { + HashMap map = mCustomDescriptorMap.get(project); + + if (map != null) { + ElementDescriptor descriptor = map.get(fqClassName); + if (descriptor != null) { + return descriptor; + } + } + + // if we step here, it looks like we haven't created it yet. + // First lets check this is in fact a valid type in the project + + try { + // We expect the project to be both opened and of java type (since it's an android + // project), so we can create a IJavaProject object from our IProject. + IJavaProject javaProject = JavaCore.create(project); + + // replace $ by . in the class name + String javaClassName = fqClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$ + + // look for the IType object for this class + IType type = javaProject.findType(javaClassName); + if (type != null && type.exists()) { + // the type exists. Let's get the parent class and its ViewClassInfo. + + // get the type hierarchy + ITypeHierarchy hierarchy = type.newSupertypeHierarchy( + new NullProgressMonitor()); + + ElementDescriptor parentDescriptor = getDescriptor( + hierarchy.getSuperclass(type), project, hierarchy); + + if (parentDescriptor != null) { + // we have a valid parent, lets create a new ElementDescriptor. + + ViewElementDescriptor descriptor = new ViewElementDescriptor(fqClassName, + fqClassName, // ui_name + fqClassName, // canonical class name + null, // tooltip + null, // sdk_url + getAttributeDescriptor(type, parentDescriptor), + null, // layout attributes + null, // children + false /* mandatory */); + + synchronized (mCustomDescriptorMap) { + map = mCustomDescriptorMap.get(project); + if (map == null) { + map = new HashMap(); + mCustomDescriptorMap.put(project, map); + } + + map.put(fqClassName, descriptor); + } + + //TODO setup listener on this resource change. + + return descriptor; + } + } + } catch (JavaModelException e) { + // there was an error accessing any of the IType, we'll just return null; + } + } + + + return null; + } + + /** + * Computes (if needed) and returns the {@link ElementDescriptor} for the specified type. + * + * @param type + * @param project + * @param typeHierarchy + * @return A ViewElementDescriptor + */ + private ViewElementDescriptor getDescriptor(IType type, IProject project, + ITypeHierarchy typeHierarchy) { + // check if the type is a built-in View class. + List builtInList = null; + + Sdk currentSdk = Sdk.getCurrent(); + IAndroidTarget target = currentSdk.getTarget(project); + if (target != null) { + AndroidTargetData data = currentSdk.getTargetData(target); + builtInList = data.getLayoutDescriptors().getViewDescriptors(); + } + + String canonicalName = type.getFullyQualifiedName(); + + if (builtInList != null) { + for (ElementDescriptor desc : builtInList) { + if (desc instanceof ViewElementDescriptor) { + ViewElementDescriptor viewDescriptor = (ViewElementDescriptor)desc; + if (canonicalName.equals(viewDescriptor.getCanonicalClassName())) { + return viewDescriptor; + } + } + } + } + + // it's not a built-in class? Lets look if the superclass is built-in + IType parentType = typeHierarchy.getSuperclass(type); + if (parentType != null) { + ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project, + typeHierarchy); + + if (parentDescriptor != null) { + // parent class is a valid View class with a descriptor, so we create one + // for this class. + ViewElementDescriptor descriptor = new ViewElementDescriptor(canonicalName, + canonicalName, // ui_name + canonicalName, // canonical name + null, // tooltip + null, // sdk_url + getAttributeDescriptor(type, parentDescriptor), + null, // layout attributes + null, // children + false /* mandatory */); + + // add it to the map + synchronized (mCustomDescriptorMap) { + HashMap map = mCustomDescriptorMap.get(project); + + if (map == null) { + map = new HashMap(); + mCustomDescriptorMap.put(project, map); + } + + map.put(canonicalName, descriptor); + + } + + //TODO setup listener on this resource change. + + return descriptor; + } + } + + // class is neither a built-in view class, nor extend one. return null. + return null; + } + + /** + * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}. + *

          + * The array should contain the descriptor for this type and all its supertypes. + * @param type the type for which the {@link AttributeDescriptor} are returned. + * @param parentDescriptor the {@link ElementDescriptor} of the direct superclass. + */ + private AttributeDescriptor[] getAttributeDescriptor(IType type, + ElementDescriptor parentDescriptor) { + // TODO add the class attribute descriptors to the parent descriptors. + return parentDescriptor.getAttributes(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java new file mode 100644 index 0000000..cad9ccf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java @@ -0,0 +1,203 @@ +/* + * 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.editors.layout.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; +import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * Complete description of the layout structure. + */ +public final class LayoutDescriptors implements IDescriptorProvider { + + // Public attributes names, attributes descriptors and elements descriptors + public static final String ID_ATTR = "id"; //$NON-NLS-1$ + + /** The document descriptor. Contains all layouts and views linked together. */ + private DocumentDescriptor mDescriptor = + new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ + + /** The list of all known ViewLayout descriptors. */ + private ArrayList mLayoutDescriptors = new ArrayList(); + + /** Read-Only list of View Descriptors. */ + private List mROLayoutDescriptors; + + /** The list of all known View (not ViewLayout) descriptors. */ + private ArrayList mViewDescriptors = new ArrayList(); + + /** Read-Only list of View Descriptors. */ + private List mROViewDescriptors; + + /** @return the document descriptor. Contains all layouts and views linked together. */ + public DocumentDescriptor getDescriptor() { + return mDescriptor; + } + + /** @return The read-only list of all known ViewLayout descriptors. */ + public List getLayoutDescriptors() { + return mROLayoutDescriptors; + } + + /** @return The read-only list of all known View (not ViewLayout) descriptors. */ + public List getViewDescriptors() { + return mROViewDescriptors; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mDescriptor.getChildren(); + } + + /** + * Updates the document descriptor. + *

          + * It first computes the new children of the descriptor and then update them + * all at once. + *

          + * TODO: differentiate groups from views in the tree UI? => rely on icons + *

          + * + * @param views The list of views in the framework. + * @param layouts The list of layouts in the framework. + */ + public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) { + ArrayList newViews = new ArrayList(); + if (views != null) { + for (ViewClassInfo info : views) { + ElementDescriptor desc = convertView(info); + newViews.add(desc); + } + } + + ArrayList newLayouts = new ArrayList(); + if (layouts != null) { + for (ViewClassInfo info : layouts) { + ElementDescriptor desc = convertView(info); + newLayouts.add(desc); + } + } + + ArrayList newDescriptors = new ArrayList(); + newDescriptors.addAll(newLayouts); + newDescriptors.addAll(newViews); + ElementDescriptor[] newArray = newDescriptors.toArray( + new ElementDescriptor[newDescriptors.size()]); + + // Link all layouts to everything else here.. recursively + for (ElementDescriptor layoutDesc : newLayouts) { + layoutDesc.setChildren(newArray); + } + + mViewDescriptors = newViews; + mLayoutDescriptors = newLayouts; + mDescriptor.setChildren(newArray); + + mROLayoutDescriptors = Collections.unmodifiableList(mLayoutDescriptors); + mROViewDescriptors = Collections.unmodifiableList(mViewDescriptors); + } + + /** + * Creates an element descriptor from a given {@link ViewClassInfo}. + */ + private ElementDescriptor convertView(ViewClassInfo info) { + String xml_name = info.getShortClassName(); + String tooltip = info.getJavaDoc(); + + // Process all View attributes + ArrayList attributes = new ArrayList(); + DescriptorsUtils.appendAttributes(attributes, + null, // elementName + AndroidConstants.NS_RESOURCES, + info.getAttributes(), + null, // requiredAttributes + null /* overrides */); + + for (ViewClassInfo link = info.getSuperClass(); + link != null; + link = link.getSuperClass()) { + AttributeInfo[] attrList = link.getAttributes(); + if (attrList.length > 0) { + attributes.add(new SeparatorAttributeDescriptor( + String.format("Attributes from %1$s", link.getShortClassName()))); + DescriptorsUtils.appendAttributes(attributes, + null, // elementName + AndroidConstants.NS_RESOURCES, + attrList, + null, // requiredAttributes + null /* overrides */); + } + } + + // Process all LayoutParams attributes + ArrayList layoutAttributes = new ArrayList(); + LayoutParamsInfo layoutParams = info.getLayoutData(); + + for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) { + boolean need_separator = true; + for (AttributeInfo attr_info : layoutParams.getAttributes()) { + if (DescriptorsUtils.containsAttribute(layoutAttributes, + AndroidConstants.NS_RESOURCES, attr_info)) { + continue; + } + if (need_separator) { + String title; + if (layoutParams.getShortClassName().equals( + AndroidConstants.CLASS_LAYOUTPARAMS)) { + title = String.format("Layout Attributes from %1$s", + layoutParams.getViewLayoutClass().getShortClassName()); + } else { + title = String.format("Layout Attributes from %1$s (%2$s)", + layoutParams.getViewLayoutClass().getShortClassName(), + layoutParams.getShortClassName()); + } + layoutAttributes.add(new SeparatorAttributeDescriptor(title)); + need_separator = false; + } + DescriptorsUtils.appendAttribute(layoutAttributes, + null, // elementName + AndroidConstants.NS_RESOURCES, + attr_info, + false, // required + null /* overrides */); + } + } + + return new ViewElementDescriptor(xml_name, + xml_name, // ui_name + info.getCanonicalClassName(), + tooltip, + null, // sdk_url + attributes.toArray(new AttributeDescriptor[attributes.size()]), + layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), + null, // children + false /* mandatory */); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java new file mode 100644 index 0000000..d718ebd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java @@ -0,0 +1,139 @@ +/* + * 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.editors.layout.descriptors; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.layout.uimodel.UiViewElementNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * {@link ViewElementDescriptor} describes the properties expected for a given XML element node + * representing a class in an XML Layout file. + * + * @see ElementDescriptor + */ +public final class ViewElementDescriptor extends ElementDescriptor { + + private String mCanonicalClassName; + + /** The list of layout attributes. Can be empty but not null. */ + private AttributeDescriptor[] mLayoutAttributes; + + + /** + * Constructs a new {@link ViewElementDescriptor} based on its XML name, UI name, + * the canonical name of the class it represents, its tooltip, its SDK url, its attributes list, + * its children list and its mandatory flag. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is + * representing. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param layoutAttributes The list of layout attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ViewElementDescriptor(String xml_name, String ui_name, + String canonicalClassName, + String tooltip, String sdk_url, + AttributeDescriptor[] attributes, AttributeDescriptor[] layoutAttributes, + ElementDescriptor[] children, boolean mandatory) { + super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); + mCanonicalClassName = canonicalClassName; + mLayoutAttributes = layoutAttributes != null ? layoutAttributes : new AttributeDescriptor[0]; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name, the canonical + * name of the class it represents, and its children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is + * representing. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ViewElementDescriptor(String xml_name, String canonicalClassName, + ElementDescriptor[] children, + boolean mandatory) { + super(xml_name, children, mandatory); + mCanonicalClassName = canonicalClassName; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and children list. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is + * representing. + * @param children The list of allowed children. Can be null or empty. + */ + public ViewElementDescriptor(String xml_name, String canonicalClassName, + ElementDescriptor[] children) { + super(xml_name, children); + mCanonicalClassName = canonicalClassName; + } + + /** + * Constructs a new {@link ElementDescriptor} based on its XML name and on the canonical + * name of the class it represents. + * The UI name is build by capitalizing the XML name. + * The UI nodes will be non-mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is + * representing. + */ + public ViewElementDescriptor(String xml_name, String canonicalClassName) { + super(xml_name); + mCanonicalClassName = canonicalClassName; + } + + /** + * Returns the canonical name of the class represented by this element descriptor. + */ + public String getCanonicalClassName() { + return mCanonicalClassName; + } + + /** Returns the list of layout attributes. Can be empty but not null. */ + public AttributeDescriptor[] getLayoutAttributes() { + return mLayoutAttributes; + } + + /** + * @return A new {@link UiViewElementNode} linked to this descriptor. + */ + @Override + public UiElementNode createUiNode() { + return new UiViewElementNode(this); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/DropFeedback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/DropFeedback.java new file mode 100644 index 0000000..6e79d64 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/DropFeedback.java @@ -0,0 +1,761 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.layout.LayoutConstants; +import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions; +import com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.Rectangle; + +import java.util.HashMap; +import java.util.Map.Entry; + +/** + * Utility methods used when dealing with dropping EditPart on the GLE. + *

          + * This class uses some temporary static storage to avoid excessive allocations during + * drop operations. It is expected to only be invoked from the main UI thread with no + * concurrent access. + */ +class DropFeedback { + + private static final int TOP = 0; + private static final int LEFT = 1; + private static final int BOTTOM = 2; + private static final int RIGHT = 3; + private static final int MAX_DIR = RIGHT; + + private static final int sOppositeDirection[] = { BOTTOM, RIGHT, TOP, LEFT }; + + private static final UiElementEditPart sTempClosests[] = new UiElementEditPart[4]; + private static final int sTempMinDists[] = new int[4]; + + + /** + * Target information computed from a drop on a RelativeLayout. + * We need only one instance of this and it is sRelativeInfo. + */ + private static class RelativeInfo { + /** The two target parts 0 and 1. They can be null, meaning a border is used. + * The direction from part 0 to 1 is always to-the-right or to-the-bottom. */ + final UiElementEditPart targetParts[] = new UiElementEditPart[2]; + /** Direction from the anchor part to the drop point. */ + int direction; + /** The index of the "anchor" part, i.e. the closest one selected by the drop. + * This can be either 0 or 1. The corresponding part can be null. */ + int anchorIndex; + } + + /** The single RelativeInfo used to compute results from a drop on a RelativeLayout */ + private static final RelativeInfo sRelativeInfo = new RelativeInfo(); + /** A temporary array of 2 {@link UiElementEditPart} to avoid allocations. */ + private static final UiElementEditPart sTempTwoParts[] = new UiElementEditPart[2]; + + + private DropFeedback() { + } + + + //----- Package methods called by users of this helper class ----- + + + /** + * This method is used by {@link ElementCreateCommand#execute()} when a new item + * needs to be "dropped" in the current XML document. It creates the new item using + * the given descriptor as a child of the given parent part. + * + * @param parentPart The parent part. + * @param descriptor The descriptor for the new XML element. + * @param where The drop location (in parent coordinates) + * @param actions The helper that actually modifies the XML model. + */ + static void addElementToXml(UiElementEditPart parentPart, + ElementDescriptor descriptor, Point where, + UiEditorActions actions) { + + String layoutXmlName = getXmlLocalName(parentPart); + RelativeInfo info = null; + UiElementEditPart sibling = null; + + if (LayoutConstants.LINEAR_LAYOUT.equals(layoutXmlName)) { + sibling = findLinearTarget(parentPart, where)[1]; + + } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) { + info = findRelativeTarget(parentPart, where, sRelativeInfo); + if (info != null) { + sibling = info.targetParts[info.anchorIndex]; + sibling = getNextUiSibling(sibling); + } + } + + if (actions != null) { + UiElementNode uiSibling = sibling != null ? sibling.getUiNode() : null; + UiElementNode uiParent = parentPart.getUiNode(); + UiElementNode uiNode = actions.addElement(uiParent, uiSibling, descriptor, + false /*updateLayout*/); + + if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutXmlName)) { + adjustAbsoluteAttributes(uiNode, where); + } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutXmlName)) { + adustRelativeAttributes(uiNode, info); + } + } + } + + /** + * This method is used by {@link UiLayoutEditPart#showDropTarget(Point)} to compute + * highlight information when a drop target is moved over a valid drop area. + *

          + * Since there are no "out" parameters in Java, all the information is returned + * via the {@link HighlightInfo} structure passed as parameter. + * + * @param parentPart The parent part, always a layout. + * @param highlightInfo A structure where result is stored to perform highlight. + * @param where The target drop point, in parent's coordinates + * @return The {@link HighlightInfo} structured passed as a parameter, for convenience. + */ + static HighlightInfo computeDropFeedback(UiLayoutEditPart parentPart, + HighlightInfo highlightInfo, + Point where) { + String layoutType = getXmlLocalName(parentPart); + + if (LayoutConstants.ABSOLUTE_LAYOUT.equals(layoutType)) { + highlightInfo.anchorPoint = where; + + } else if (LayoutConstants.LINEAR_LAYOUT.equals(layoutType)) { + boolean isVertical = isVertical(parentPart); + + highlightInfo.childParts = findLinearTarget(parentPart, where); + computeLinearLine(parentPart, isVertical, highlightInfo); + + } else if (LayoutConstants.RELATIVE_LAYOUT.equals(layoutType)) { + + RelativeInfo info = findRelativeTarget(parentPart, where, sRelativeInfo); + if (info != null) { + highlightInfo.childParts = sRelativeInfo.targetParts; + computeRelativeLine(parentPart, info, highlightInfo); + } + } + + return highlightInfo; + } + + + //----- Misc utilities ----- + + /** + * Returns the next UI sibling of this part, i.e. the element which is just after in + * the UI/XML order in the same parent. Returns null if there's no such part. + *

          + * Note: by "UI sibling" here we mean the sibling in the UiNode hierarchy. By design the + * UiNode model has the exact same order as the XML model. This has nothing to do + * with the "user interface" order that you see on the rendered Android layouts (e.g. for + * LinearLayout they are the same but for AbsoluteLayout or RelativeLayout the UI/XML model + * order can be vastly different from the user interface order.) + */ + private static UiElementEditPart getNextUiSibling(UiElementEditPart part) { + if (part != null) { + UiElementNode uiNode = part.getUiNode(); + if (uiNode != null) { + uiNode = uiNode.getUiNextSibling(); + } + if (uiNode != null) { + for (Object childPart : part.getParent().getChildren()) { + if (childPart instanceof UiElementEditPart && + ((UiElementEditPart) childPart).getUiNode() == uiNode) { + return (UiElementEditPart) childPart; + } + } + } + } + return null; + } + + /** + * Returns the XML local name of the ui node associated with this edit part or null. + */ + private static String getXmlLocalName(UiElementEditPart editPart) { + UiElementNode uiNode = editPart.getUiNode(); + if (uiNode != null) { + ElementDescriptor desc = uiNode.getDescriptor(); + if (desc != null) { + return desc.getXmlLocalName(); + } + } + return null; + } + + /** + * Adjusts the attributes of a new node dropped in an AbsoluteLayout. + * + * @param uiNode The new node being dropped. + * @param where The drop location (in parent coordinates) + */ + private static void adjustAbsoluteAttributes(final UiElementNode uiNode, final Point where) { + if (where == null) { + return; + } + uiNode.getEditor().editXmlModel(new Runnable() { + public void run() { + uiNode.setAttributeValue(LayoutConstants.ATTR_LAYOUT_X, + String.format(LayoutConstants.VALUE_N_DIP, where.x), + false /* override */); + uiNode.setAttributeValue(LayoutConstants.ATTR_LAYOUT_Y, + String.format(LayoutConstants.VALUE_N_DIP, where.y), + false /* override */); + + uiNode.commitDirtyAttributesToXml(); + } + }); + } + + /** + * Adjusts the attributes of a new node dropped in a RelativeLayout: + *

            + *
          • anchor part: the one the user selected (or the closest) and to which the new one + * will "attach". The anchor part can be null, either because the layout is currently + * empty or the user is attaching to an existing empty border. + *
          • direction: the direction from the anchor part to the drop point. That's also the + * direction from the anchor part to the new part. + *
          • the new node; it is created either after the anchor for right or top directions + * or before the anchor for left or bottom directions. This means the new part can + * reference the id of the anchor part. + *
          + * + * Several cases: + *
            + *
          • set: layout_above/below/toLeftOf/toRightOf to point to the anchor. + *
          • copy: layout_centerHorizontal for top/bottom directions + *
          • copy: layout_centerVertical for left/right directions. + *
          • copy: layout_above/below/toLeftOf/toRightOf for the orthogonal direction + * (i.e. top/bottom or left/right.) + *
          + * + * @param uiNode The new node being dropped. + * @param info The context computed by {@link #findRelativeTarget(UiElementEditPart, Point, RelativeInfo)}. + */ + private static void adustRelativeAttributes(final UiElementNode uiNode, RelativeInfo info) { + if (uiNode == null || info == null) { + return; + } + + final UiElementEditPart anchorPart = info.targetParts[info.anchorIndex]; // can be null + final int direction = info.direction; + + uiNode.getEditor().editXmlModel(new Runnable() { + public void run() { + HashMap map = new HashMap(); + + UiElementNode anchorUiNode = anchorPart != null ? anchorPart.getUiNode() : null; + String anchorId = anchorUiNode != null + ? anchorUiNode.getAttributeValue("id") //$NON-NLS-1$ + : null; + + if (anchorId == null) { + anchorId = DescriptorsUtils.getFreeWidgetId(anchorUiNode); + anchorUiNode.setAttributeValue("id", anchorId, true /*override*/); //$NON-NLS-1$ + } + + if (anchorId != null) { + switch(direction) { + case TOP: + map.put(LayoutConstants.ATTR_LAYOUT_ABOVE, anchorId); + break; + case BOTTOM: + map.put(LayoutConstants.ATTR_LAYOUT_BELOW, anchorId); + break; + case LEFT: + map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF, anchorId); + break; + case RIGHT: + map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF, anchorId); + break; + } + + switch(direction) { + case TOP: + case BOTTOM: + map.put(LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL, + anchorUiNode.getAttributeValue( + LayoutConstants.ATTR_LAYOUT_CENTER_HORIZONTAL)); + + map.put(LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF, + anchorUiNode.getAttributeValue( + LayoutConstants.ATTR_LAYOUT_TO_LEFT_OF)); + map.put(LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF, + anchorUiNode.getAttributeValue( + LayoutConstants.ATTR_LAYOUT_TO_RIGHT_OF)); + break; + case LEFT: + case RIGHT: + map.put(LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL, + anchorUiNode.getAttributeValue( + LayoutConstants.ATTR_LAYOUT_CENTER_VERTICAL)); + map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE, + anchorUiNode.getAttributeValue( + LayoutConstants.ATTR_LAYOUT_ALIGN_BASELINE)); + + map.put(LayoutConstants.ATTR_LAYOUT_ABOVE, + anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_ABOVE)); + map.put(LayoutConstants.ATTR_LAYOUT_BELOW, + anchorUiNode.getAttributeValue(LayoutConstants.ATTR_LAYOUT_BELOW)); + break; + } + } else { + // We don't have an anchor node. Assume we're targeting a border and align + // to the parent. + switch(direction) { + case TOP: + map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_TOP, + LayoutConstants.VALUE_TRUE); + break; + case BOTTOM: + map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_BOTTOM, + LayoutConstants.VALUE_TRUE); + break; + case LEFT: + map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT, + LayoutConstants.VALUE_TRUE); + break; + case RIGHT: + map.put(LayoutConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT, + LayoutConstants.VALUE_TRUE); + break; + } + } + + for (Entry entry : map.entrySet()) { + uiNode.setAttributeValue(entry.getKey(), entry.getValue(), true /* override */); + } + uiNode.commitDirtyAttributesToXml(); + } + }); + } + + + //----- LinearLayout -------- + + /** + * For a given parent edit part that MUST represent a LinearLayout, finds the + * element before which the location points. + *

          + * This computes the edit part that corresponds to what will be the "next sibling" of the new + * element. + *

          + * It returns null if it can't be determined, in which case the element will be added at the + * end of the parent child list. + * + * @return The edit parts that correspond to what will be the "prev" and "next sibling" of the + * new element. The previous sibling can be null if adding before the first element. + * The next sibling can be null if adding after the last element. + */ + private static UiElementEditPart[] findLinearTarget(UiElementEditPart parent, Point point) { + // default orientation is horizontal + boolean isVertical = isVertical(parent); + + int target = isVertical ? point.y : point.x; + + UiElementEditPart prev = null; + UiElementEditPart next = null; + + for (Object child : parent.getChildren()) { + if (child instanceof UiElementEditPart) { + UiElementEditPart childPart = (UiElementEditPart) child; + Point p = childPart.getBounds().getCenter(); + int middle = isVertical ? p.y : p.x; + if (target < middle) { + next = childPart; + break; + } + prev = childPart; + } + } + + sTempTwoParts[0] = prev; + sTempTwoParts[1] = next; + return sTempTwoParts; + } + + /** + * Computes the highlight line between two parts. + *

          + * The two parts are listed in HighlightInfo.childParts[2]. Any of the parts + * can be null. + * The result is stored in HighlightInfo. + *

          + * Caller must clear the HighlightInfo as appropriate before this call. + * + * @param parentPart The parent part, always a layout. + * @param isVertical True for vertical parts, thus computing an horizontal line. + * @param highlightInfo The in-out highlight info. + */ + private static void computeLinearLine(UiLayoutEditPart parentPart, + boolean isVertical, HighlightInfo highlightInfo) { + Rectangle r = parentPart.getBounds(); + + if (isVertical) { + Point p = null; + UiElementEditPart part = highlightInfo.childParts[0]; + if (part != null) { + p = part.getBounds().getBottom(); + } else { + part = highlightInfo.childParts[1]; + if (part != null) { + p = part.getBounds().getTop(); + } + } + if (p != null) { + // horizontal line with middle anchor point + highlightInfo.tempPoints[0].setLocation(0, p.y); + highlightInfo.tempPoints[1].setLocation(r.width, p.y); + highlightInfo.linePoints = highlightInfo.tempPoints; + highlightInfo.anchorPoint = p.setLocation(r.width / 2, p.y); + } + } else { + Point p = null; + UiElementEditPart part = highlightInfo.childParts[0]; + if (part != null) { + p = part.getBounds().getRight(); + } else { + part = highlightInfo.childParts[1]; + if (part != null) { + p = part.getBounds().getLeft(); + } + } + if (p != null) { + // vertical line with middle anchor point + highlightInfo.tempPoints[0].setLocation(p.x, 0); + highlightInfo.tempPoints[1].setLocation(p.x, r.height); + highlightInfo.linePoints = highlightInfo.tempPoints; + highlightInfo.anchorPoint = p.setLocation(p.x, r.height / 2); + } + } + } + + /** + * Returns true if the linear layout is marked as vertical. + * + * @param parent The a layout part that must be a LinearLayout + * @return True if the linear layout has a vertical orientation attribute. + */ + private static boolean isVertical(UiElementEditPart parent) { + String orientation = parent.getStringAttr("orientation"); //$NON-NLS-1$ + boolean isVertical = "vertical".equals(orientation) || //$NON-NLS-1$ + "1".equals(orientation); //$NON-NLS-1$ + return isVertical; + } + + + //----- RelativeLayout -------- + + /** + * Finds the "target" relative layout item for the drop operation & feedback. + *

          + * If the drop point is exactly on a current item, simply returns the side the drop will occur + * compared to the center of that element. For the actual XML, we'll need to insert *after* + * that element to make sure that referenced are defined in the right order. + * In that case the result contains two elements, the second one always being on the right or + * bottom side of the first one. When insert in XML, we want to insert right before that + * second element or at the end of the child list if the second element is null. + *

          + * If the drop point is not exactly on a current element, find the closest in each + * direction and align with the two closest of these. + * + * @return null if we fail to find anything (such as there are currently no items to compare + * with); otherwise fills the {@link RelativeInfo} and return it. + */ + private static RelativeInfo findRelativeTarget(UiElementEditPart parent, + Point point, + RelativeInfo outInfo) { + + for (int i = 0; i < 4; i++) { + sTempMinDists[i] = Integer.MAX_VALUE; + sTempClosests[i] = null; + } + + + for (Object child : parent.getChildren()) { + if (child instanceof UiElementEditPart) { + UiElementEditPart childPart = (UiElementEditPart) child; + Rectangle r = childPart.getBounds(); + if (r.contains(point)) { + + float rx = ((float)(point.x - r.x) / (float)r.width ) - 0.5f; + float ry = ((float)(point.y - r.y) / (float)r.height) - 0.5f; + + /* TOP + * \ / + * \ / + * L X R + * / \ + * / \ + * BOT + */ + + int index = 0; + if (Math.abs(rx) >= Math.abs(ry)) { + if (rx < 0) { + outInfo.direction = LEFT; + index = 1; + } else { + outInfo.direction = RIGHT; + } + } else { + if (ry < 0) { + outInfo.direction = TOP; + index = 1; + } else { + outInfo.direction = BOTTOM; + } + } + + outInfo.anchorIndex = index; + outInfo.targetParts[index] = childPart; + outInfo.targetParts[1 - index] = findClosestPart(childPart, + outInfo.direction); + + return outInfo; + } + + computeClosest(point, childPart, sTempClosests, sTempMinDists, TOP); + computeClosest(point, childPart, sTempClosests, sTempMinDists, LEFT); + computeClosest(point, childPart, sTempClosests, sTempMinDists, BOTTOM); + computeClosest(point, childPart, sTempClosests, sTempMinDists, RIGHT); + } + } + + UiElementEditPart closest = null; + int minDist = Integer.MAX_VALUE; + int minDir = -1; + + for (int i = 0; i <= MAX_DIR; i++) { + if (sTempClosests[i] != null && sTempMinDists[i] < minDist) { + closest = sTempClosests[i]; + minDist = sTempMinDists[i]; + minDir = i; + } + } + + if (closest != null) { + int index = 0; + switch(minDir) { + case TOP: + case LEFT: + index = 0; + break; + case BOTTOM: + case RIGHT: + index = 1; + break; + } + outInfo.anchorIndex = index; + outInfo.targetParts[index] = closest; + outInfo.targetParts[1 - index] = findClosestPart(closest, sOppositeDirection[minDir]); + outInfo.direction = sOppositeDirection[minDir]; + return outInfo; + } + + return null; + } + + /** + * Computes the highlight line for a drop on a RelativeLayout. + *

          + * The line is always placed on the side of the anchor part indicated by the + * direction. The direction always point from the anchor part to the drop point. + *

          + * If there's no anchor part, use the other one with a reversed direction. + *

          + * On output, this updates the {@link HighlightInfo}. + */ + private static void computeRelativeLine(UiLayoutEditPart parentPart, + RelativeInfo relInfo, + HighlightInfo highlightInfo) { + + UiElementEditPart[] parts = relInfo.targetParts; + int dir = relInfo.direction; + int index = relInfo.anchorIndex; + UiElementEditPart part = parts[index]; + + if (part == null) { + dir = sOppositeDirection[dir]; + part = parts[1 - index]; + } + if (part == null) { + // give up if both parts are null + return; + } + + Rectangle r = part.getBounds(); + Point p = null; + switch(dir) { + case TOP: + p = r.getTop(); + break; + case BOTTOM: + p = r.getBottom(); + break; + case LEFT: + p = r.getLeft(); + break; + case RIGHT: + p = r.getRight(); + break; + } + + highlightInfo.anchorPoint = p; + + r = parentPart.getBounds(); + switch(dir) { + case TOP: + case BOTTOM: + // horizontal line with middle anchor point + highlightInfo.tempPoints[0].setLocation(0, p.y); + highlightInfo.tempPoints[1].setLocation(r.width, p.y); + highlightInfo.linePoints = highlightInfo.tempPoints; + highlightInfo.anchorPoint = p; + break; + case LEFT: + case RIGHT: + // vertical line with middle anchor point + highlightInfo.tempPoints[0].setLocation(p.x, 0); + highlightInfo.tempPoints[1].setLocation(p.x, r.height); + highlightInfo.linePoints = highlightInfo.tempPoints; + highlightInfo.anchorPoint = p; + break; + } + } + + /** + * Given a certain reference point (drop point), computes the distance to the given + * part in the given direction. For example if direction is top, only accepts parts which + * bottom is above the reference point, computes their distance and then updates the + * current minimal distances and current closest parts arrays accordingly. + */ + private static void computeClosest(Point refPoint, + UiElementEditPart compareToPart, + UiElementEditPart[] currClosests, + int[] currMinDists, + int direction) { + Rectangle r = compareToPart.getBounds(); + + Point p = null; + boolean usable = false; + + switch(direction) { + case TOP: + p = r.getBottom(); + usable = p.y <= refPoint.y; + break; + case BOTTOM: + p = r.getTop(); + usable = p.y >= refPoint.y; + break; + case LEFT: + p = r.getRight(); + usable = p.x <= refPoint.x; + break; + case RIGHT: + p = r.getLeft(); + usable = p.x >= refPoint.x; + break; + } + + if (usable) { + int d = p.getDistance2(refPoint); + if (d < currMinDists[direction]) { + currMinDists[direction] = d; + currClosests[direction] = compareToPart; + } + } + } + + /** + * Given a reference parts, finds the closest part in the parent in the given direction. + * For example if direction is top, finds the closest sibling part which is above the + * reference part and non-overlapping (they can touch.) + */ + private static UiElementEditPart findClosestPart(UiElementEditPart referencePart, + int direction) { + if (referencePart == null || referencePart.getParent() == null) { + return null; + } + + Rectangle r = referencePart.getBounds(); + Point ref = null; + switch(direction) { + case TOP: + ref = r.getTop(); + break; + case BOTTOM: + ref = r.getBottom(); + break; + case LEFT: + ref = r.getLeft(); + break; + case RIGHT: + ref = r.getRight(); + break; + } + + int minDist = Integer.MAX_VALUE; + UiElementEditPart closestPart = null; + + for (Object childPart : referencePart.getParent().getChildren()) { + if (childPart != referencePart && childPart instanceof UiElementEditPart) { + r = ((UiElementEditPart) childPart).getBounds(); + Point p = null; + boolean usable = false; + + switch(direction) { + case TOP: + p = r.getBottom(); + usable = p.y <= ref.y; + break; + case BOTTOM: + p = r.getTop(); + usable = p.y >= ref.y; + break; + case LEFT: + p = r.getRight(); + usable = p.x <= ref.x; + break; + case RIGHT: + p = r.getLeft(); + usable = p.x >= ref.x; + break; + } + + if (usable) { + int d = p.getDistance2(ref); + if (d < minDist) { + minDist = d; + closestPart = (UiElementEditPart) childPart; + } + } + } + } + + return closestPart; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementCreateCommand.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementCreateCommand.java new file mode 100644 index 0000000..d36d9f7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementCreateCommand.java @@ -0,0 +1,98 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.layout.LayoutEditor; +import com.android.ide.eclipse.editors.layout.LayoutEditor.UiEditorActions; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.gef.commands.Command; + +/** + * A command that knows how to instantiate a new element based on a given {@link ElementDescriptor}, + * the parent {@link UiElementEditPart} and an optional target location. + */ +public class ElementCreateCommand extends Command { + + /** Descriptor of the new element to create */ + private final ElementDescriptor mDescriptor; + /** The edit part that hosts the new edit part */ + private final UiElementEditPart mParentPart; + /** The drop location in parent coordinates */ + private final Point mTargetPoint; + + /** + * Creates a new {@link ElementCreateCommand}. + * + * @param descriptor Descriptor of the new element to create + * @param targetPart The edit part that hosts the new edit part + * @param targetPoint The drop location in parent coordinates + */ + public ElementCreateCommand(ElementDescriptor descriptor, + UiElementEditPart targetPart, Point targetPoint) { + mDescriptor = descriptor; + mParentPart = targetPart; + mTargetPoint = targetPoint; + } + + // --- Methods inherited from Command --- + + @Override + public boolean canExecute() { + return mDescriptor != null && + mParentPart != null && + mParentPart.getUiNode() != null && + mParentPart.getUiNode().getEditor() instanceof LayoutEditor; + } + + @Override + public void execute() { + super.execute(); + UiElementNode uiParent = mParentPart.getUiNode(); + if (uiParent != null) { + final AndroidEditor editor = uiParent.getEditor(); + if (editor instanceof LayoutEditor) { + ((LayoutEditor) editor).wrapUndoRecording( + String.format("Create %1$s", mDescriptor.getXmlLocalName()), + new Runnable() { + public void run() { + UiEditorActions actions = ((LayoutEditor) editor).getUiEditorActions(); + if (actions != null) { + DropFeedback.addElementToXml(mParentPart, mDescriptor, mTargetPoint, + actions); + } + } + }); + } + } + } + + @Override + public void redo() { + throw new UnsupportedOperationException("redo not supported by this command"); //$NON-NLS-1$ + } + + @Override + public void undo() { + throw new UnsupportedOperationException("undo not supported by this command"); //$NON-NLS-1$ + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementFigure.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementFigure.java new file mode 100644 index 0000000..f863037 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/ElementFigure.java @@ -0,0 +1,75 @@ +/* + * 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.editors.layout.parts; + +import org.eclipse.draw2d.ColorConstants; +import org.eclipse.draw2d.Figure; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.swt.SWT; + + +/** + * The figure used to draw basic elements. + *

          + * The figure is totally empty and transparent except for the selection border. + */ +class ElementFigure extends Figure { + + private boolean mIsSelected; + private Rectangle mInnerBounds; + + public ElementFigure() { + setOpaque(false); + } + + public void setSelected(boolean isSelected) { + if (isSelected != mIsSelected) { + mIsSelected = isSelected; + repaint(); + } + } + + @Override + public void setBounds(Rectangle rect) { + super.setBounds(rect); + + mInnerBounds = getBounds().getCopy(); + if (mInnerBounds.width > 0) { + mInnerBounds.width--; + } + if (mInnerBounds.height > 0) { + mInnerBounds.height--; + } + } + + public Rectangle getInnerBounds() { + return mInnerBounds; + } + + @Override + protected void paintBorder(Graphics graphics) { + super.paintBorder(graphics); + + if (mIsSelected) { + graphics.setLineWidth(1); + graphics.setLineStyle(SWT.LINE_SOLID); + graphics.setForegroundColor(ColorConstants.red); + graphics.drawRectangle(getInnerBounds()); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/LayoutFigure.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/LayoutFigure.java new file mode 100644 index 0000000..55ed39b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/LayoutFigure.java @@ -0,0 +1,151 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.layout.parts.UiLayoutEditPart.HighlightInfo; + +import org.eclipse.draw2d.ColorConstants; +import org.eclipse.draw2d.Figure; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.swt.SWT; + +/** + * The figure used to draw the feedback on a layout. + *

          + * By default the figure is transparent and empty. + * The base {@link ElementFigure} knows how to draw the selection border. + * This figure knows how to draw the drop feedback. + */ +class LayoutFigure extends ElementFigure { + + private HighlightInfo mHighlightInfo; + + public LayoutFigure() { + super(); + } + + public void setHighlighInfo(HighlightInfo highlightInfo) { + mHighlightInfo = highlightInfo; + repaint(); + } + + /** + * Paints the "border" for this figure. + *

          + * The parent {@link Figure#paint(Graphics)} calls {@link #paintFigure(Graphics)} then + * {@link #paintClientArea(Graphics)} then {@link #paintBorder(Graphics)}. Here we thus + * draw the actual highlight border but also the highlight anchor lines and points so that + * we can make sure they are all drawn on top of the border. + *

          + * Note: This method doesn't really need to restore its graphic state. The parent + * Figure will do it for us. + *

          + * + * @param graphics The Graphics object used for painting + */ + @Override + protected void paintBorder(Graphics graphics) { + super.paintBorder(graphics); + + if (mHighlightInfo == null) { + return; + } + + // Draw the border. We want other highlighting to be drawn on top of the border. + if (mHighlightInfo.drawDropBorder) { + graphics.setLineWidth(3); + graphics.setLineStyle(SWT.LINE_SOLID); + graphics.setForegroundColor(ColorConstants.green); + graphics.drawRectangle(getInnerBounds().getCopy().shrink(1, 1)); + } + + Rectangle bounds = getBounds(); + int bx = bounds.x; + int by = bounds.y; + int w = bounds.width; + int h = bounds.height; + + // Draw frames of target child parts, if any + if (mHighlightInfo.childParts != null) { + graphics.setLineWidth(2); + graphics.setLineStyle(SWT.LINE_DOT); + graphics.setForegroundColor(ColorConstants.lightBlue); + for (UiElementEditPart part : mHighlightInfo.childParts) { + if (part != null) { + graphics.drawRectangle(part.getBounds().getCopy().translate(bx, by)); + } + } + } + + // Draw the target line, if any + if (mHighlightInfo.linePoints != null) { + int x1 = mHighlightInfo.linePoints[0].x; + int y1 = mHighlightInfo.linePoints[0].y; + int x2 = mHighlightInfo.linePoints[1].x; + int y2 = mHighlightInfo.linePoints[1].y; + + // if the line is right to the edge, draw it one pixel more inside so that the + // full 2-pixel width be visible. + if (x1 <= 0) x1++; + if (x2 <= 0) x2++; + if (y1 <= 0) y1++; + if (y2 <= 0) y2++; + + if (x1 >= w - 1) x1--; + if (x2 >= w - 1) x2--; + if (y1 >= h - 1) y1--; + if (y2 >= h - 1) y2--; + + x1 += bx; + x2 += bx; + y1 += by; + y2 += by; + + graphics.setLineWidth(2); + graphics.setLineStyle(SWT.LINE_DASH); + graphics.setLineCap(SWT.CAP_ROUND); + graphics.setForegroundColor(ColorConstants.orange); + graphics.drawLine(x1, y1, x2, y2); + } + + // Draw the anchor point, if any + if (mHighlightInfo.anchorPoint != null) { + int x = mHighlightInfo.anchorPoint.x; + int y = mHighlightInfo.anchorPoint.y; + + // If the point is right on the edge, draw it one pixel inside so that it + // matches the highlight line. It makes it slightly more visible that way. + if (x <= 0) x++; + if (y <= 0) y++; + if (x >= w - 1) x--; + if (y >= h - 1) y--; + x += bx; + y += by; + + graphics.setLineWidth(2); + graphics.setLineStyle(SWT.LINE_SOLID); + graphics.setLineCap(SWT.CAP_ROUND); + graphics.setForegroundColor(ColorConstants.orange); + graphics.drawLine(x-5, y-5, x+5, y+5); + graphics.drawLine(x-5, y+5, x+5, y-5); + // 7 * cos(45) == 5 so we use 8 for the circle radius (it looks slightly better than 7) + graphics.setLineWidth(1); + graphics.drawOval(x-8, y-8, 16, 16); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java new file mode 100644 index 0000000..2f7636d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java @@ -0,0 +1,212 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.AbstractBackground; +import org.eclipse.draw2d.ColorConstants; +import org.eclipse.draw2d.FreeformLayer; +import org.eclipse.draw2d.FreeformLayout; +import org.eclipse.draw2d.Graphics; +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.Label; +import org.eclipse.draw2d.geometry.Insets; +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.gef.EditPart; +import org.eclipse.gef.EditPolicy; +import org.eclipse.gef.Request; +import org.eclipse.gef.RequestConstants; +import org.eclipse.gef.editpolicies.RootComponentEditPolicy; +import org.eclipse.gef.requests.DropRequest; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.PaletteData; +import org.eclipse.swt.widgets.Display; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; + +/** + * Graphical edit part for the root document. + *

          + * It acts as a simple container. + */ +public class UiDocumentEditPart extends UiElementEditPart { + + private Display mDisplay; + private FreeformLayer mLayer; + private ImageBackground mImage; + private Label mChild = null; + + final static class ImageBackground extends AbstractBackground { + + private BufferedImage mBufferedImage; + private Image mImage; + + ImageBackground() { + } + + ImageBackground(BufferedImage image, Display display) { + setImage(image, display); + } + + @Override + public void paintBackground(IFigure figure, Graphics graphics, Insets insets) { + if (mImage != null) { + Rectangle rect = getPaintRectangle(figure, insets); + graphics.drawImage(mImage, rect.x, rect.y); + } + } + + void setImage(BufferedImage image, Display display) { + if (image != null) { + int[] data = ((DataBufferInt)image.getData().getDataBuffer()).getData(); + + ImageData imageData = new ImageData(image.getWidth(), image.getHeight(), 32, + new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF)); + + imageData.setPixels(0, 0, data.length, data, 0); + + mImage = new Image(display, imageData); + } else { + mImage = null; + } + } + + BufferedImage getBufferedImage() { + return mBufferedImage; + } + } + + public UiDocumentEditPart(UiDocumentNode uiDocumentNode, Display display) { + super(uiDocumentNode); + mDisplay = display; + } + + @Override + protected IFigure createFigure() { + mLayer = new FreeformLayer(); + mLayer.setLayoutManager(new FreeformLayout()); + + mLayer.setOpaque(true); + mLayer.setBackgroundColor(ColorConstants.lightGray); + + return mLayer; + } + + @Override + protected void refreshVisuals() { + UiElementNode model = (UiElementNode)getModel(); + + Object editData = model.getEditData(); + if (editData instanceof BufferedImage) { + BufferedImage image = (BufferedImage)editData; + + if (mImage == null || image != mImage.getBufferedImage()) { + mImage = new ImageBackground(image, mDisplay); + } + + mLayer.setBorder(mImage); + + if (mChild != null && mChild.getParent() == mLayer) { + mLayer.remove(mChild); + } + } else if (editData instanceof String) { + mLayer.setBorder(null); + if (mChild == null) { + mChild = new Label(); + } + mChild.setText((String)editData); + + if (mChild != null && mChild.getParent() != mLayer) { + mLayer.add(mChild); + } + Rectangle bounds = mChild.getTextBounds(); + bounds.x = bounds.y = 0; + mLayer.setConstraint(mChild, bounds); + } + + // refresh the children as well + refreshChildrenVisuals(); + } + + @Override + protected void hideSelection() { + // no selection at this level. + } + + @Override + protected void showSelection() { + // no selection at this level. + } + + @Override + protected void createEditPolicies() { + super.createEditPolicies(); + + // This policy indicates this a root component that cannot be removed + installEditPolicy(EditPolicy.COMPONENT_ROLE, new RootComponentEditPolicy()); + + installLayoutEditPolicy(this); + } + + /** + * Returns the EditPart that should be used as the target for the specified Request. + * For instance this is called during drag'n'drop with a CreateRequest. + *

          + * For the root document, we want the first child edit part to the be the target + * since an XML document can have only one root element. + * + * {@inheritDoc} + */ + @Override + public EditPart getTargetEditPart(Request request) { + if (request != null && request.getType() == RequestConstants.REQ_CREATE) { + // We refuse the drop if it's not in the bounds of the document. + if (request instanceof DropRequest) { + Point where = ((DropRequest) request).getLocation().getCopy(); + UiElementNode uiNode = getUiNode(); + if (uiNode instanceof UiDocumentNode) { + // Take the bounds of the background image as the valid drop zone + Object editData = uiNode.getEditData(); + if (editData instanceof BufferedImage) { + BufferedImage image = (BufferedImage)editData; + int w = image.getWidth(); + int h = image.getHeight(); + if (where.x > w || where.y > h) { + return null; + } + } + + } + } + + // For the root document, we want the first child edit part to the be the target + // since an XML document can have only one root element. + if (getChildren().size() > 0) { + Object o = getChildren().get(0); + if (o instanceof EditPart) { + return ((EditPart) o).getTargetEditPart(request); + } + } + } + return super.getTargetEditPart(request); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java new file mode 100644 index 0000000..af22afb --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java @@ -0,0 +1,37 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; + +import java.util.List; + +/** + * Implementation of {@link UiElementTreeEditPart} for the document root. + */ +public class UiDocumentTreeEditPart extends UiElementTreeEditPart { + + public UiDocumentTreeEditPart(UiDocumentNode model) { + super(model); + } + + @SuppressWarnings("unchecked") + @Override + protected List getModelChildren() { + return getUiNode().getUiChildren(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java new file mode 100644 index 0000000..d873005 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java @@ -0,0 +1,345 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.draw2d.geometry.Rectangle; +import org.eclipse.gef.DragTracker; +import org.eclipse.gef.EditPart; +import org.eclipse.gef.EditPolicy; +import org.eclipse.gef.GraphicalEditPart; +import org.eclipse.gef.Request; +import org.eclipse.gef.RequestConstants; +import org.eclipse.gef.commands.Command; +import org.eclipse.gef.editparts.AbstractGraphicalEditPart; +import org.eclipse.gef.editpolicies.LayoutEditPolicy; +import org.eclipse.gef.editpolicies.SelectionEditPolicy; +import org.eclipse.gef.requests.CreateRequest; +import org.eclipse.gef.requests.DropRequest; +import org.eclipse.gef.tools.SelectEditPartTracker; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import java.util.List; + +/** + * An {@link EditPart} for a {@link UiElementNode}. + */ +public abstract class UiElementEditPart extends AbstractGraphicalEditPart + implements IUiUpdateListener { + + public UiElementEditPart(UiElementNode uiElementNode) { + setModel(uiElementNode); + } + + //------------------------- + // Derived classes must define these + + abstract protected void hideSelection(); + abstract protected void showSelection(); + + //------------------------- + // Base class overrides + + @Override + public DragTracker getDragTracker(Request request) { + return new SelectEditPartTracker(this); + } + + @Override + protected void createEditPolicies() { + /* + * This is no longer needed, as a selection edit policy is set by the parent layout. + * Leave this code commented out right now, I'll want to play with this later. + * + installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, + new NonResizableSelectionEditPolicy(this)); + */ + } + + /* (non-javadoc) + * Returns a List containing the children model objects. + * Must not return null, instead use the super which returns an empty list. + */ + @SuppressWarnings("unchecked") + @Override + protected List getModelChildren() { + return getUiNode().getUiChildren(); + } + + @Override + public void activate() { + super.activate(); + getUiNode().addUpdateListener(this); + } + + @Override + public void deactivate() { + super.deactivate(); + getUiNode().removeUpdateListener(this); + } + + @Override + protected void refreshVisuals() { + if (getFigure().getParent() != null) { + ((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(), getBounds()); + } + + // update the visuals of the children as well + refreshChildrenVisuals(); + } + + protected void refreshChildrenVisuals() { + if (children != null) { + for (Object child : children) { + if (child instanceof UiElementEditPart) { + UiElementEditPart childPart = (UiElementEditPart)child; + childPart.refreshVisuals(); + } + } + } + } + + //------------------------- + // IUiUpdateListener implementation + + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + // TODO: optimize by refreshing only when needed + switch(state) { + case ATTR_UPDATED: + refreshVisuals(); + break; + case CHILDREN_CHANGED: + refreshChildren(); + + // new children list, need to update the layout + refreshVisuals(); + break; + case CREATED: + refreshVisuals(); + break; + case DELETED: + // pass + break; + } + } + + //------------------------- + // Local methods + + /** @return The object model casted to an {@link UiElementNode} */ + public final UiElementNode getUiNode() { + return (UiElementNode) getModel(); + } + + protected final ElementDescriptor getDescriptor() { + return getUiNode().getDescriptor(); + } + + protected final UiElementEditPart getEditPartParent() { + EditPart parent = getParent(); + if (parent instanceof UiElementEditPart) { + return (UiElementEditPart)parent; + } + return null; + } + + /** + * Returns a given XML attribute. + * @param attrName The local name of the attribute. + * @return the attribute as a {@link String}, if it exists, or null + */ + protected final String getStringAttr(String attrName) { + UiElementNode uiNode = getUiNode(); + if (uiNode.getXmlNode() != null) { + Node xmlNode = uiNode.getXmlNode(); + if (xmlNode != null) { + NamedNodeMap nodeAttributes = xmlNode.getAttributes(); + if (nodeAttributes != null) { + Node attr = nodeAttributes.getNamedItemNS( + AndroidConstants.NS_RESOURCES, attrName); + if (attr != null) { + return attr.getNodeValue(); + } + } + } + } + return null; + } + + protected final Rectangle getBounds() { + UiElementNode model = (UiElementNode)getModel(); + + Object editData = model.getEditData(); + + if (editData != null) { + // assert with fully qualified class name to prevent import changes to another + // Rectangle class. + assert (editData instanceof org.eclipse.draw2d.geometry.Rectangle); + + return (Rectangle)editData; + } + + // return a dummy rect + return new Rectangle(0, 0, 0, 0); + } + + /** + * Returns the EditPart that should be used as the target for the specified Request. + *

          + * For instance this is called during drag'n'drop with a CreateRequest. + *

          + * Reject being a target for elements which descriptor does not allow children. + * + * {@inheritDoc} + */ + @Override + public EditPart getTargetEditPart(Request request) { + if (request != null && request.getType() == RequestConstants.REQ_CREATE) { + // Reject being a target for elements which descriptor does not allow children. + if (!getUiNode().getDescriptor().hasChildren()) { + return null; + } + } + return super.getTargetEditPart(request); + } + + /** + * Used by derived classes {@link UiDocumentEditPart} and {@link UiLayoutEditPart} + * to accept drag'n'drop of new items from the palette. + * + * @param layoutEditPart The layout edit part where this policy is installed. It can + * be either a {@link UiDocumentEditPart} or a {@link UiLayoutEditPart}. + */ + protected void installLayoutEditPolicy(final UiElementEditPart layoutEditPart) { + // This policy indicates how elements can be constrained by the layout. + // TODO Right now we use the XY layout policy since our constraints are + // handled by the android rendering engine rather than GEF. Tweak as + // appropriate. + installEditPolicy(EditPolicy.LAYOUT_ROLE, new LayoutEditPolicy() { + + /** + * We don't allow layout children to be resized yet. + *

          + * Typical choices would be: + *

            + *
          • ResizableEditPolicy, to allow for selection, move and resize. + *
          • NonResizableEditPolicy, to allow for selection, move but not resize. + *
          • SelectionEditPolicy to allow for only selection. + *
          + *

          + * TODO: make this depend on the part layout. For an AbsoluteLayout we should + * probably use a NonResizableEditPolicy and SelectionEditPolicy for the rest. + * Whether to use ResizableEditPolicy or NonResizableEditPolicy should depend + * on the child in an AbsoluteLayout. + */ + @Override + protected EditPolicy createChildEditPolicy(EditPart child) { + if (child instanceof UiElementEditPart) { + return new NonResizableSelectionEditPolicy((UiElementEditPart) child); + } + return null; + } + + @Override + protected Command getCreateCommand(CreateRequest request) { + // We store the ElementDescriptor in the request.factory.type + Object newType = request.getNewObjectType(); + if (newType instanceof ElementDescriptor) { + Point where = request.getLocation().getCopy(); + Point origin = getLayoutContainer().getClientArea().getLocation(); + where.translate(origin.getNegated()); + + // The host is the EditPart where this policy is installed, + // e.g. this UiElementEditPart. + EditPart host = getHost(); + if (host instanceof UiElementEditPart) { + + return new ElementCreateCommand((ElementDescriptor) newType, + (UiElementEditPart) host, + where); + } + } + + return null; + } + + @Override + protected Command getMoveChildrenCommand(Request request) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void showLayoutTargetFeedback(Request request) { + super.showLayoutTargetFeedback(request); + + // for debugging + // System.out.println("target: " + request.toString() + " -- " + layoutEditPart.getUiNode().getBreadcrumbTrailDescription(false)); + + if (layoutEditPart instanceof UiLayoutEditPart && + request instanceof DropRequest) { + Point where = ((DropRequest) request).getLocation().getCopy(); + Point origin = getLayoutContainer().getClientArea().getLocation(); + where.translate(origin.getNegated()); + + ((UiLayoutEditPart) layoutEditPart).showDropTarget(where); + } + } + + @Override + protected void eraseLayoutTargetFeedback(Request request) { + super.eraseLayoutTargetFeedback(request); + if (layoutEditPart instanceof UiLayoutEditPart) { + ((UiLayoutEditPart) layoutEditPart).hideDropTarget(); + } + } + + @Override + protected IFigure createSizeOnDropFeedback(CreateRequest createRequest) { + // TODO understand if this is useful for us or remove + return super.createSizeOnDropFeedback(createRequest); + } + + }); + } + + protected static class NonResizableSelectionEditPolicy extends SelectionEditPolicy { + + private final UiElementEditPart mEditPart; + + public NonResizableSelectionEditPolicy(UiElementEditPart editPart) { + mEditPart = editPart; + } + + @Override + protected void hideSelection() { + mEditPart.hideSelection(); + } + + @Override + protected void showSelection() { + mEditPart.showSelection(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java new file mode 100644 index 0000000..fd788dd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java @@ -0,0 +1,74 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.gef.editparts.AbstractTreeEditPart; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + * Base {@link AbstractTreeEditPart} to represent {@link UiElementNode} objects in the + * {@link IContentOutlinePage} linked to the layout editor. + */ +public class UiElementTreeEditPart extends AbstractTreeEditPart { + + public UiElementTreeEditPart(UiElementNode uiElementNode) { + setModel(uiElementNode); + } + + @Override + protected void createEditPolicies() { + // TODO Auto-generated method stub + super.createEditPolicies(); + } + + @Override + protected Image getImage() { + return getUiNode().getDescriptor().getIcon(); + } + + @Override + protected String getText() { + return getUiNode().getShortDescription(); + } + + @Override + public void activate() { + if (!isActive()) { + super.activate(); + // TODO + } + } + + @Override + public void deactivate() { + if (isActive()) { + super.deactivate(); + // TODO + } + } + + /** + * Returns the casted model object represented by this {@link AbstractTreeEditPart}. + */ + protected UiElementNode getUiNode() { + return (UiElementNode)getModel(); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java new file mode 100644 index 0000000..de6c404 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java @@ -0,0 +1,47 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.gef.EditPart; +import org.eclipse.gef.EditPartFactory; +import org.eclipse.gef.editparts.AbstractTreeEditPart; +import org.eclipse.ui.views.contentoutline.IContentOutlinePage; + +/** + * {@link EditPartFactory} to create {@link AbstractTreeEditPart} for {@link UiElementNode} objects. + * These objects are used in the {@link IContentOutlinePage} linked to the layout editor. + */ +public class UiElementTreeEditPartFactory implements EditPartFactory { + + public EditPart createEditPart(EditPart context, Object model) { + if (model instanceof UiDocumentNode) { + return new UiDocumentTreeEditPart((UiDocumentNode) model); + } else if (model instanceof UiElementNode) { + UiElementNode node = (UiElementNode) model; + if (node.getDescriptor().hasChildren()) { + return new UiLayoutTreeEditPart(node); + } else { + return new UiViewTreeEditPart(node); + } + } + return null; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java new file mode 100644 index 0000000..18dcd9c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java @@ -0,0 +1,55 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.gef.EditPart; +import org.eclipse.gef.EditPartFactory; +import org.eclipse.swt.widgets.Display; + +/** + * A factory that returns the appropriate {@link EditPart} for a given model object. + *

          + * The only model objects we use are {@link UiElementNode} objects and they are + * edited using {@link UiElementEditPart}. + */ +public class UiElementsEditPartFactory implements EditPartFactory { + + private Display mDisplay; + + public UiElementsEditPartFactory(Display display) { + mDisplay = display; + } + + public EditPart createEditPart(EditPart context, Object model) { + if (model instanceof UiDocumentNode) { + return new UiDocumentEditPart((UiDocumentNode) model, mDisplay); + } else if (model instanceof UiElementNode) { + UiElementNode node = (UiElementNode) model; + + if (node.getDescriptor().hasChildren()) { + return new UiLayoutEditPart(node); + } + + return new UiViewEditPart(node); + } + return null; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java new file mode 100644 index 0000000..43a70a5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java @@ -0,0 +1,115 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.XYLayout; +import org.eclipse.draw2d.geometry.Point; +import org.eclipse.gef.EditPolicy; +import org.eclipse.gef.commands.Command; +import org.eclipse.gef.editpolicies.ContainerEditPolicy; +import org.eclipse.gef.requests.CreateRequest; + +/** + * Graphical edit part for an {@link UiElementNode} that represents a ViewLayout. + *

          + * It acts as a simple container. + */ +public final class UiLayoutEditPart extends UiElementEditPart { + + static class HighlightInfo { + public boolean drawDropBorder; + public UiElementEditPart[] childParts; + public Point anchorPoint; + public Point linePoints[]; + + public final Point tempPoints[] = new Point[] { new Point(), new Point() }; + + public void clear() { + drawDropBorder = false; + childParts = null; + anchorPoint = null; + linePoints = null; + } + } + + private final HighlightInfo mHighlightInfo = new HighlightInfo(); + + public UiLayoutEditPart(UiElementNode uiElementNode) { + super(uiElementNode); + } + + @Override + protected void createEditPolicies() { + super.createEditPolicies(); + + installEditPolicy(EditPolicy.CONTAINER_ROLE, new ContainerEditPolicy() { + @Override + protected Command getCreateCommand(CreateRequest request) { + return null; + } + }); + + installLayoutEditPolicy(this); + } + + @Override + protected IFigure createFigure() { + IFigure f = new LayoutFigure(); + f.setLayoutManager(new XYLayout()); + return f; + } + + @Override + protected void showSelection() { + IFigure f = getFigure(); + if (f instanceof ElementFigure) { + ((ElementFigure) f).setSelected(true); + } + } + + @Override + protected void hideSelection() { + IFigure f = getFigure(); + if (f instanceof ElementFigure) { + ((ElementFigure) f).setSelected(false); + } + } + + public void showDropTarget(Point where) { + if (where != null) { + mHighlightInfo.clear(); + mHighlightInfo.drawDropBorder = true; + DropFeedback.computeDropFeedback(this, mHighlightInfo, where); + + IFigure f = getFigure(); + if (f instanceof LayoutFigure) { + ((LayoutFigure) f).setHighlighInfo(mHighlightInfo); + } + } + } + + public void hideDropTarget() { + mHighlightInfo.clear(); + IFigure f = getFigure(); + if (f instanceof LayoutFigure) { + ((LayoutFigure) f).setHighlighInfo(mHighlightInfo); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java new file mode 100644 index 0000000..4359e23 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java @@ -0,0 +1,39 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import java.util.List; + +/** + * Implementation of {@link UiElementTreeEditPart} for layout objects. + */ +public class UiLayoutTreeEditPart extends UiElementTreeEditPart { + + public UiLayoutTreeEditPart(UiElementNode node) { + super(node); + } + + @SuppressWarnings("unchecked") + @Override + protected List getModelChildren() { + return getUiNode().getUiChildren(); + } + + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java new file mode 100644 index 0000000..05329f3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java @@ -0,0 +1,55 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.draw2d.IFigure; +import org.eclipse.draw2d.XYLayout; + +/** + * Graphical edit part for an {@link UiElementNode} that represents a View. + */ +public class UiViewEditPart extends UiElementEditPart { + + public UiViewEditPart(UiElementNode uiElementNode) { + super(uiElementNode); + } + + @Override + protected IFigure createFigure() { + IFigure f = new ElementFigure(); + f.setLayoutManager(new XYLayout()); + return f; + } + + @Override + protected void showSelection() { + IFigure f = getFigure(); + if (f instanceof ElementFigure) { + ((ElementFigure) f).setSelected(true); + } + } + + @Override + protected void hideSelection() { + IFigure f = getFigure(); + if (f instanceof ElementFigure) { + ((ElementFigure) f).setSelected(false); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java new file mode 100644 index 0000000..62b5e8a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java @@ -0,0 +1,30 @@ +/* + * 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.editors.layout.parts; + +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * Implementation of {@link UiElementTreeEditPart} for view objects. + */ +public class UiViewTreeEditPart extends UiElementTreeEditPart { + + public UiViewTreeEditPart(UiElementNode node) { + super(node); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java new file mode 100644 index 0000000..45cbc77 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java @@ -0,0 +1,129 @@ +/* + * 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.editors.layout.uimodel; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IProject; + +import java.util.List; + +/** + * Specialized version of {@link UiElementNode} for the {@link ViewElementDescriptor}s. + */ +public class UiViewElementNode extends UiElementNode { + + private AttributeDescriptor[] mCachedAttributeDescriptors; + + public UiViewElementNode(ViewElementDescriptor elementDescriptor) { + super(elementDescriptor); + } + + /** + * Returns an AttributeDescriptor array that depends on the current UiParent. + *

          + * The array merges both "direct" attributes with the descriptor layout attributes. + * The array instance is cached and cleared if the UiParent is changed. + */ + @Override + public AttributeDescriptor[] getAttributeDescriptors() { + if (mCachedAttributeDescriptors != null) { + return mCachedAttributeDescriptors; + } + + UiElementNode ui_parent = getUiParent(); + AttributeDescriptor[] direct_attrs = super.getAttributeDescriptors(); + mCachedAttributeDescriptors = direct_attrs; + + AttributeDescriptor[] layout_attrs = null; + boolean need_xmlns = false; + + if (ui_parent instanceof UiDocumentNode) { + // Limitation: right now the layout behaves as if everything was + // owned by a FrameLayout. + // TODO replace by something user-configurable. + + List layoutDescriptors = null; + IProject project = getEditor().getProject(); + if (project != null) { + Sdk currentSdk = Sdk.getCurrent(); + IAndroidTarget target = currentSdk.getTarget(project); + if (target != null) { + AndroidTargetData data = currentSdk.getTargetData(target); + layoutDescriptors = data.getLayoutDescriptors().getLayoutDescriptors(); + } + } + + if (layoutDescriptors != null) { + for (ElementDescriptor desc : layoutDescriptors) { + if (desc instanceof ViewElementDescriptor && + desc.getXmlName().equals(AndroidConstants.CLASS_FRAMELAYOUT)) { + layout_attrs = ((ViewElementDescriptor) desc).getLayoutAttributes(); + need_xmlns = true; + break; + } + } + } + } else if (ui_parent instanceof UiViewElementNode){ + layout_attrs = + ((ViewElementDescriptor) ui_parent.getDescriptor()).getLayoutAttributes(); + } + + if (layout_attrs == null || layout_attrs.length == 0) { + return mCachedAttributeDescriptors; + } + + mCachedAttributeDescriptors = + new AttributeDescriptor[direct_attrs.length + + layout_attrs.length + + (need_xmlns ? 1 : 0)]; + System.arraycopy(direct_attrs, 0, + mCachedAttributeDescriptors, 0, + direct_attrs.length); + System.arraycopy(layout_attrs, 0, + mCachedAttributeDescriptors, direct_attrs.length, + layout_attrs.length); + if (need_xmlns) { + AttributeDescriptor desc = new XmlnsAttributeDescriptor( + "android", //$NON-NLS-1$ + AndroidConstants.NS_RESOURCES); + mCachedAttributeDescriptors[direct_attrs.length + layout_attrs.length] = desc; + } + + return mCachedAttributeDescriptors; + } + + /** + * Sets the parent of this UI node. + *

          + * Also removes the cached AttributeDescriptor array that depends on the current UiParent. + */ + @Override + protected void setUiParent(UiElementNode parent) { + super.setUiParent(parent); + mCachedAttributeDescriptors = null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java new file mode 100644 index 0000000..b40e458 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 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.editors.manifest; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidContentAssist; + +/** + * Content Assist Processor for AndroidManifest.xml + */ +final class ManifestContentAssist extends AndroidContentAssist { + + /** + * Constructor for ManifestContentAssist + */ + public ManifestContentAssist() { + super(AndroidTargetData.DESCRIPTOR_MANIFEST); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java new file mode 100644 index 0000000..d0f8d7b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2007 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.editors.manifest; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidXPathFactory; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.manifest.pages.ApplicationPage; +import com.android.ide.eclipse.editors.manifest.pages.InstrumentationPage; +import com.android.ide.eclipse.editors.manifest.pages.OverviewPage; +import com.android.ide.eclipse.editors.manifest.pages.PermissionPage; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.util.List; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; + +/** + * Multi-page form editor for AndroidManifest.xml. + */ +public final class ManifestEditor extends AndroidEditor { + private final static String EMPTY = ""; //$NON-NLS-1$ + + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiManifestNode; + /** The Application Page tab */ + private ApplicationPage mAppPage; + /** The Overview Manifest Page tab */ + private OverviewPage mOverviewPage; + /** The Permission Page tab */ + private PermissionPage mPermissionPage; + /** The Instrumentation Page tab */ + private InstrumentationPage mInstrumentationPage; + + private IFileListener mMarkerMonitor; + + + /** + * Creates the form editor for AndroidManifest.xml. + */ + public ManifestEditor() { + super(); + } + + @Override + public void dispose() { + super.dispose(); + + ResourceMonitor.getMonitor().removeFileListener(mMarkerMonitor); + } + + /** + * Return the root node of the UI element hierarchy, which here + * is the "manifest" node. + */ + @Override + public UiElementNode getUiRootNode() { + return mUiManifestNode; + } + + /** + * Returns the Manifest descriptors for the file being edited. + */ + public AndroidManifestDescriptors getManifestDescriptors() { + AndroidTargetData data = getTargetData(); + if (data != null) { + return data.getManifestDescriptors(); + } + + return null; + } + + // ---- Base Class Overrides ---- + + /** + * Returns whether the "save as" operation is supported by this editor. + *

          + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Creates the various form pages. + */ + @Override + protected void createFormPages() { + try { + addPage(mOverviewPage = new OverviewPage(this)); + addPage(mAppPage = new ApplicationPage(this)); + addPage(mPermissionPage = new PermissionPage(this)); + addPage(mInstrumentationPage = new InstrumentationPage(this)); + } catch (PartInitException e) { + AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ + } + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + IFile inputFile = getInputFile(); + if (inputFile != null) { + startMonitoringMarkers(); + setPartName(String.format("%1$s Manifest", inputFile.getProject().getName())); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // create the ui root node on demand. + initUiRootNode(false /*force*/); + + loadFromXml(xml_doc); + + super.xmlModelChanged(xml_doc); + } + + private void loadFromXml(Document xmlDoc) { + mUiManifestNode.setXmlDocument(xmlDoc); + if (xmlDoc != null) { + ElementDescriptor manifest_desc = mUiManifestNode.getDescriptor(); + try { + XPath xpath = AndroidXPathFactory.newXPath(); + Node node = (Node) xpath.evaluate("/" + manifest_desc.getXmlName(), //$NON-NLS-1$ + xmlDoc, + XPathConstants.NODE); + assert node != null && node.getNodeName().equals(manifest_desc.getXmlName()); + + // Refresh the manifest UI node and all its descendants + mUiManifestNode.loadFromXmlNode(node); + } catch (XPathExpressionException e) { + AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ + manifest_desc.getXmlName()); + } + } + } + + private void onDescriptorsChanged(UiElementNode oldManifestNode) { + mUiManifestNode.reloadFromXmlNode(oldManifestNode.getXmlNode()); + + if (mOverviewPage != null) { + mOverviewPage.refreshUiApplicationNode(); + } + + if (mAppPage != null) { + mAppPage.refreshUiApplicationNode(); + } + + if (mPermissionPage != null) { + mPermissionPage.refreshUiNode(); + } + + if (mInstrumentationPage != null) { + mInstrumentationPage.refreshUiNode(); + } + } + + /** + * Reads and processes the current markers and adds a listener for marker changes. + */ + private void startMonitoringMarkers() { + final IFile inputFile = getInputFile(); + if (inputFile != null) { + updateFromExistingMarkers(inputFile); + + mMarkerMonitor = new IFileListener() { + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + if (file.equals(inputFile)) { + processMarkerChanges(markerDeltas); + } + } + }; + + ResourceMonitor.getMonitor().addFileListener(mMarkerMonitor, IResourceDelta.CHANGED); + } + } + + /** + * Processes the markers of the specified {@link IFile} and updates the error status of + * {@link UiElementNode}s and {@link UiAttributeNode}s. + * @param inputFile the file being edited. + */ + private void updateFromExistingMarkers(IFile inputFile) { + try { + // get the markers for the file + IMarker[] markers = inputFile.findMarkers(AndroidConstants.MARKER_ANDROID, true, + IResource.DEPTH_ZERO); + + AndroidManifestDescriptors desc = getManifestDescriptors(); + if (desc != null) { + ElementDescriptor appElement = desc.getApplicationElement(); + + if (appElement != null) { + UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( + appElement.getXmlName()); + List children = app_ui_node.getUiChildren(); + + for (IMarker marker : markers) { + processMarker(marker, children, IResourceDelta.ADDED); + } + } + } + + } catch (CoreException e) { + // findMarkers can throw an exception, in which case, we'll do nothing. + } + } + + /** + * Processes a {@link IMarker} change. + * @param markerDeltas the list of {@link IMarkerDelta} + */ + private void processMarkerChanges(IMarkerDelta[] markerDeltas) { + AndroidManifestDescriptors descriptors = getManifestDescriptors(); + if (descriptors != null && descriptors.getApplicationElement() != null) { + UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( + descriptors.getApplicationElement().getXmlName()); + List children = app_ui_node.getUiChildren(); + + for (IMarkerDelta markerDelta : markerDeltas) { + processMarker(markerDelta.getMarker(), children, markerDelta.getKind()); + } + } + } + + /** + * Processes a new/old/updated marker. + * @param marker The marker being added/removed/changed + * @param nodeList the list of activity/service/provider/receiver nodes. + * @param kind the change kind. Can be {@link IResourceDelta#ADDED}, + * {@link IResourceDelta#REMOVED}, or {@link IResourceDelta#CHANGED} + */ + private void processMarker(IMarker marker, List nodeList, int kind) { + // get the data from the marker + String nodeType = marker.getAttribute(AndroidConstants.MARKER_ATTR_TYPE, EMPTY); + if (nodeType == EMPTY) { + return; + } + + String className = marker.getAttribute(AndroidConstants.MARKER_ATTR_CLASS, EMPTY); + if (className == EMPTY) { + return; + } + + for (UiElementNode ui_node : nodeList) { + if (ui_node.getDescriptor().getXmlName().equals(nodeType)) { + for (UiAttributeNode attr : ui_node.getUiAttributes()) { + if (attr.getDescriptor().getXmlLocalName().equals( + AndroidManifestDescriptors.ANDROID_NAME_ATTR)) { + if (attr.getCurrentValue().equals(className)) { + if (kind == IResourceDelta.REMOVED) { + attr.setHasError(false); + } else { + attr.setHasError(true); + } + return; + } + } + } + } + } + } + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiManifestNode is recreated even if it already exists. + */ + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiManifestNode != null && force == false) { + return; + } + + + AndroidManifestDescriptors manifestDescriptor = getManifestDescriptors(); + + if (manifestDescriptor != null) { + // save the old manifest node if it exists + UiElementNode oldManifestNode = mUiManifestNode; + + ElementDescriptor manifestElement = manifestDescriptor.getManifestElement(); + mUiManifestNode = manifestElement.createUiNode(); + mUiManifestNode.setEditor(this); + + // Similarly, always create the /manifest/application and /manifest/uses-sdk nodes + ElementDescriptor appElement = manifestDescriptor.getApplicationElement(); + boolean present = false; + for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { + if (ui_node.getDescriptor() == appElement) { + present = true; + break; + } + } + if (!present) { + mUiManifestNode.appendNewUiChild(appElement); + } + + appElement = manifestDescriptor.getUsesSdkElement(); + present = false; + for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { + if (ui_node.getDescriptor() == appElement) { + present = true; + break; + } + } + if (!present) { + mUiManifestNode.appendNewUiChild(appElement); + } + + if (oldManifestNode != null) { + onDescriptorsChanged(oldManifestNode); + } + } else { + // create a dummy descriptor/uinode until we have real descriptors + ElementDescriptor desc = new ElementDescriptor("manifest", //$NON-NLS-1$ + "temporary descriptors due to missing decriptors", //$NON-NLS-1$ + null /*tooltip*/, null /*sdk_url*/, null /*attributes*/, + null /*children*/, false /*mandatory*/); + mUiManifestNode = desc.createUiNode(); + mUiManifestNode.setEditor(this); + } + } + + /** + * Returns the {@link IFile} being edited, or null if it couldn't be computed. + */ + private IFile getInputFile() { + IEditorInput input = getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + return fileInput.getFile(); + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java new file mode 100644 index 0000000..911faa1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 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.editors.manifest; + +import org.eclipse.jface.action.IAction; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.ide.IDEActionFactory; +import org.eclipse.ui.part.MultiPageEditorActionBarContributor; +import org.eclipse.ui.texteditor.ITextEditor; +import org.eclipse.ui.texteditor.ITextEditorActionConstants; + +/** + * Manages the installation/deinstallation of global actions for multi-page + * editors. Responsible for the redirection of global actions to the active + * editor. Multi-page contributor replaces the contributors for the individual + * editors in the multi-page editor. + * + * TODO: Doesn't look like we need this. Remove it if not needed. + * @deprecated + */ +final class ManifestEditorContributor extends MultiPageEditorActionBarContributor { + private IEditorPart mActiveEditorPart; + + /** + * Creates a multi-page contributor. + * + * Marked as Private so it can't be instanciated. This is a cheap way to make sure + * it's not being used. As noted in constructor, should be removed if not used. + * @deprecated + */ + private ManifestEditorContributor() { + super(); + } + + /** + * Returns the action registed with the given text editor. + * + * @return IAction or null if editor is null. + */ + protected IAction getAction(ITextEditor editor, String actionID) { + return (editor == null ? null : editor.getAction(actionID)); + } + + /* + * (non-JavaDoc) Method declared in + * AbstractMultiPageEditorActionBarContributor. + */ + + @Override + public void setActivePage(IEditorPart part) { + if (mActiveEditorPart == part) + return; + + mActiveEditorPart = part; + + IActionBars actionBars = getActionBars(); + if (actionBars != null) { + + ITextEditor editor = + (part instanceof ITextEditor) ? (ITextEditor)part : null; + + actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), + getAction(editor, ITextEditorActionConstants.DELETE)); + actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), + getAction(editor, ITextEditorActionConstants.UNDO)); + actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), + getAction(editor, ITextEditorActionConstants.REDO)); + actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), + getAction(editor, ITextEditorActionConstants.CUT)); + actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), + getAction(editor, ITextEditorActionConstants.COPY)); + actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), + getAction(editor, ITextEditorActionConstants.PASTE)); + actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), + getAction(editor, ITextEditorActionConstants.SELECT_ALL)); + actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(), + getAction(editor, ITextEditorActionConstants.FIND)); + actionBars.setGlobalActionHandler( + IDEActionFactory.BOOKMARK.getId(), getAction(editor, + IDEActionFactory.BOOKMARK.getId())); + actionBars.updateActionBars(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java new file mode 100644 index 0000000..e33e1ef --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 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.editors.manifest; + + +import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in ManifestContentAssist. + */ +public final class ManifestSourceViewerConfig extends AndroidSourceViewerConfig { + + public ManifestSourceViewerConfig() { + super(new ManifestContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java new file mode 100644 index 0000000..87a14ad --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ReferenceAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; + +import org.eclipse.core.runtime.IStatus; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeSet; +import java.util.Map.Entry; + + +/** + * Complete description of the AndroidManifest.xml structure. + *

          + * The root element are static instances which always exists. + * However their sub-elements and attributes are created only when the SDK changes or is + * loaded the first time. + */ +public final class AndroidManifestDescriptors implements IDescriptorProvider { + + private static final String MANIFEST_NODE_NAME = "manifest"; //$NON-NLS-1$ + private static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$ + + // Public attributes names, attributes descriptors and elements descriptors + + public static final String ANDROID_LABEL_ATTR = "label"; //$NON-NLS-1$ + public static final String ANDROID_NAME_ATTR = "name"; //$NON-NLS-1$ + public static final String PACKAGE_ATTR = "package"; //$NON-NLS-1$ + + /** The {@link ElementDescriptor} for the root Manifest element. */ + private final ElementDescriptor MANIFEST_ELEMENT; + /** The {@link ElementDescriptor} for the root Application element. */ + private final ElementDescriptor APPLICATION_ELEMENT; + + /** The {@link ElementDescriptor} for the root Instrumentation element. */ + private final ElementDescriptor INTRUMENTATION_ELEMENT; + /** The {@link ElementDescriptor} for the root Permission element. */ + private final ElementDescriptor PERMISSION_ELEMENT; + /** The {@link ElementDescriptor} for the root UsesPermission element. */ + private final ElementDescriptor USES_PERMISSION_ELEMENT; + /** The {@link ElementDescriptor} for the root UsesSdk element. */ + private final ElementDescriptor USES_SDK_ELEMENT; + + /** The {@link ElementDescriptor} for the root PermissionGroup element. */ + private final ElementDescriptor PERMISSION_GROUP_ELEMENT; + /** The {@link ElementDescriptor} for the root PermissionTree element. */ + private final ElementDescriptor PERMISSION_TREE_ELEMENT; + + /** Private package attribute for the manifest element. Needs to be handled manually. */ + private final TextAttributeDescriptor PACKAGE_ATTR_DESC; + + public AndroidManifestDescriptors() { + APPLICATION_ELEMENT = createElement("application", null, true); //$NON-NLS-1$ + no child & mandatory + INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$ + + PERMISSION_ELEMENT = createElement("permission"); //$NON-NLS-1$ + USES_PERMISSION_ELEMENT = createElement("uses-permission"); //$NON-NLS-1$ + USES_SDK_ELEMENT = createElement("uses-sdk", null, true); //$NON-NLS-1$ + no child & mandatory + + PERMISSION_GROUP_ELEMENT = createElement("permission-group"); //$NON-NLS-1$ + PERMISSION_TREE_ELEMENT = createElement("permission-tree"); //$NON-NLS-1$ + + MANIFEST_ELEMENT = createElement( + MANIFEST_NODE_NAME, // xml name + new ElementDescriptor[] { + APPLICATION_ELEMENT, + INTRUMENTATION_ELEMENT, + PERMISSION_ELEMENT, + USES_PERMISSION_ELEMENT, + PERMISSION_GROUP_ELEMENT, + PERMISSION_TREE_ELEMENT, + USES_SDK_ELEMENT, + }, + true /* mandatory */); + + // The "package" attribute is treated differently as it doesn't have the standard + // Android XML namespace. + PACKAGE_ATTR_DESC = new PackageAttributeDescriptor(PACKAGE_ATTR, + "Package", + null /* nsUri */, + "This attribute gives a unique name for the package, using a Java-style naming convention to avoid name collisions.\nFor example, applications published by Google could have names of the form com.google.app.appname"); + } + + public ElementDescriptor[] getRootElementDescriptors() { + return new ElementDescriptor[] { MANIFEST_ELEMENT }; + } + + public ElementDescriptor getDescriptor() { + return getManifestElement(); + } + + public ElementDescriptor getApplicationElement() { + return APPLICATION_ELEMENT; + } + + public ElementDescriptor getManifestElement() { + return MANIFEST_ELEMENT; + } + + public ElementDescriptor getUsesSdkElement() { + return USES_SDK_ELEMENT; + } + + public ElementDescriptor getInstrumentationElement() { + return INTRUMENTATION_ELEMENT; + } + + public ElementDescriptor getPermissionElement() { + return PERMISSION_ELEMENT; + } + + public ElementDescriptor getUsesPermissionElement() { + return USES_PERMISSION_ELEMENT; + } + + public ElementDescriptor getPermissionGroupElement() { + return PERMISSION_GROUP_ELEMENT; + } + + public ElementDescriptor getPermissionTreeElement() { + return PERMISSION_TREE_ELEMENT; + } + + /** + * Updates the document descriptor. + *

          + * It first computes the new children of the descriptor and then updates them + * all at once. + * + * @param manifestMap The map style => attributes from the attrs_manifest.xml file + */ + public synchronized void updateDescriptors( + Map manifestMap) { + + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( + "android", //$NON-NLS-1$ + AndroidConstants.NS_RESOURCES); + + // -- setup the various attribute format overrides -- + // The key for each override is "element1,element2,.../attr-xml-local-name" or + // "*/attr-xml-local-name" to match the attribute in any element. + + Map overrides = new HashMap(); + + overrides.put("*/icon", new DescriptorsUtils.ITextAttributeCreator() { //$NON-NLS-1$ + public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, + String tooltip) { + return new ReferenceAttributeDescriptor( + ResourceType.DRAWABLE, + xmlName, uiName, nsUri, + tooltip); + } + }); + + overrides.put("*/theme", ThemeAttributeDescriptor.class); //$NON-NLS-1$ + overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$ + overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$ + + overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$ + ListAttributeDescriptor.class); + overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$ + + overrideClassName(overrides, "activity", AndroidConstants.CLASS_ACTIVITY); //$NON-NLS-1$ + overrideClassName(overrides, "receiver", AndroidConstants.CLASS_BROADCASTRECEIVER); //$NON-NLS-1$ + overrideClassName(overrides, "service", AndroidConstants.CLASS_SERVICE); //$NON-NLS-1$ + overrideClassName(overrides, "provider", AndroidConstants.CLASS_CONTENTPROVIDER); //$NON-NLS-1$ + + // -- list element nodes already created -- + // These elements are referenced by already opened editors, so we want to update them + // but not re-create them when reloading an SDK on the fly. + + HashMap elementDescs = + new HashMap(); + elementDescs.put(MANIFEST_ELEMENT.getXmlLocalName(), MANIFEST_ELEMENT); + elementDescs.put(APPLICATION_ELEMENT.getXmlLocalName(), APPLICATION_ELEMENT); + elementDescs.put(INTRUMENTATION_ELEMENT.getXmlLocalName(), INTRUMENTATION_ELEMENT); + elementDescs.put(PERMISSION_ELEMENT.getXmlLocalName(), PERMISSION_ELEMENT); + elementDescs.put(USES_PERMISSION_ELEMENT.getXmlLocalName(), USES_PERMISSION_ELEMENT); + elementDescs.put(USES_SDK_ELEMENT.getXmlLocalName(), USES_SDK_ELEMENT); + elementDescs.put(PERMISSION_GROUP_ELEMENT.getXmlLocalName(), PERMISSION_GROUP_ELEMENT); + elementDescs.put(PERMISSION_TREE_ELEMENT.getXmlLocalName(), PERMISSION_TREE_ELEMENT); + + // -- + + inflateElement(manifestMap, overrides, elementDescs, + MANIFEST_ELEMENT, "AndroidManifest"); //$NON-NLS-1$ + insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC); + + sanityCheck(manifestMap, MANIFEST_ELEMENT); + } + + /** + * Sets up an attribute override for ANDROID_NAME_ATTR using a ClassAttributeDescriptor + * with the specified class name. + */ + private static void overrideClassName(Map overrides, + String elementName, final String className) { + overrides.put(elementName + "/" + ANDROID_NAME_ATTR, + new DescriptorsUtils.ITextAttributeCreator() { + public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, + String tooltip) { + if (AndroidConstants.CLASS_ACTIVITY.equals(className)) { + return new ClassAttributeDescriptor( + className, + PostActivityCreationAction.getAction(), + xmlName, uiName + "*", //$NON-NLS-1$ + nsUri, + tooltip, + true /*mandatory */); + } else if (AndroidConstants.CLASS_BROADCASTRECEIVER.equals(className)) { + return new ClassAttributeDescriptor( + className, + PostReceiverCreationAction.getAction(), + xmlName, uiName + "*", //$NON-NLS-1$ + nsUri, + tooltip, + true /*mandatory */); + + } else { + return new ClassAttributeDescriptor( + className, + xmlName, uiName + "*", //$NON-NLS-1$ + nsUri, + tooltip, + true /*mandatory */); + } + } + }); + } + + /** + * Returns a new ElementDescriptor constructed from the information given here + * and the javadoc & attributes extracted from the style map if any. + *

          + * Creates an element with no attribute overrides. + */ + private ElementDescriptor createElement( + String xmlName, + ElementDescriptor[] childrenElements, + boolean mandatory) { + // Creates an element with no attribute overrides. + String styleName = guessStyleName(xmlName); + String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName; + String uiName = getUiName(xmlName); + + ElementDescriptor element = new ManifestElementDescriptor(xmlName, uiName, null, sdkUrl, + null, childrenElements, mandatory); + + return element; + } + + /** + * Returns a new ElementDescriptor constructed from its XML local name. + *

          + * This version creates an element not mandatory. + */ + private ElementDescriptor createElement(String xmlName) { + // Creates an element with no child and not mandatory + return createElement(xmlName, null, false); + } + + /** + * Inserts an attribute in this element attribute list if it is not present there yet + * (based on the attribute XML name.) + * The attribute is inserted at the beginning of the attribute list. + */ + private void insertAttribute(ElementDescriptor element, AttributeDescriptor newAttr) { + AttributeDescriptor[] attributes = element.getAttributes(); + for (AttributeDescriptor attr : attributes) { + if (attr.getXmlLocalName().equals(newAttr.getXmlLocalName())) { + return; + } + } + + AttributeDescriptor[] newArray = new AttributeDescriptor[attributes.length + 1]; + newArray[0] = newAttr; + System.arraycopy(attributes, 0, newArray, 1, attributes.length); + element.setAttributes(newArray); + } + + /** + * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration. + *

          + * This first creates all the attributes for the given ElementDescriptor. + * It then find all children of the descriptor, inflate them recursively and set them + * as child to this ElementDescriptor. + * + * @param styleMap The input styleable map for manifest elements & attributes + * @param overrides A list of attribute overrides (to customize the type of the attribute + * descriptors) + * @param existingElementDescs A map of already created element descriptors, keyed by + * XML local name. This is used to use the static elements created initially by this + * class, which are referenced directly by editors (so that reloading an SDK won't + * break these references) + * @param elemDesc The current {@link ElementDescriptor} to inflate. + * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name + * will be guessed automatically from the style name. + */ + private void inflateElement( + Map styleMap, + Map overrides, + HashMap existingElementDescs, + ElementDescriptor elemDesc, + String styleName) { + assert elemDesc != null; + assert styleName != null; + + // define attributes + DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; + if (style != null) { + ArrayList attrDescs = new ArrayList(); + DescriptorsUtils.appendAttributes(attrDescs, + elemDesc.getXmlLocalName(), + AndroidConstants.NS_RESOURCES, + style.getAttributes(), null, overrides); + elemDesc.setTooltip(style.getJavaDoc()); + elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()])); + } + + // find all elements that have this one as parent + ArrayList children = new ArrayList(); + for (Entry entry : styleMap.entrySet()) { + DeclareStyleableInfo childStyle = entry.getValue(); + boolean isParent = false; + String[] parents = childStyle.getParents(); + if (parents != null) { + for (String parent: parents) { + if (styleName.equals(parent)) { + isParent = true; + break; + } + } + } + if (isParent) { + String childStyleName = entry.getKey(); + String childXmlName = guessXmlName(childStyleName); + + // create or re-use element + ElementDescriptor child = existingElementDescs.get(childXmlName); + if (child == null) { + child = createElement(childXmlName); + existingElementDescs.put(childXmlName, child); + } + children.add(child); + + inflateElement(styleMap, overrides, existingElementDescs, child, childStyleName); + } + } + elemDesc.setChildren(children.toArray(new ElementDescriptor[children.size()])); + } + + /** + * Get an UI name from the element XML name. + *

          + * Capitalizes the first letter and replace non-alphabet by a space followed by a capital. + */ + private String getUiName(String xmlName) { + StringBuilder sb = new StringBuilder(); + + boolean capitalize = true; + for (char c : xmlName.toCharArray()) { + if (capitalize && c >= 'a' && c <= 'z') { + sb.append((char)(c + 'A' - 'a')); + capitalize = false; + } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + sb.append(' '); + capitalize = true; + } else { + sb.append(c); + } + } + + return sb.toString(); + } + + /** + * Guesses the style name for a given XML element name. + *

          + * The rules are: + * - capitalize the first letter: + * - if there's a dash, skip it and capitalize the next one + * - prefix AndroidManifest + * The exception is "manifest" which just becomes AndroidManifest. + *

          + * Examples: + * - manifest => AndroidManifest + * - application => AndroidManifestApplication + * - uses-permission => AndroidManifestUsesPermission + */ + private String guessStyleName(String xmlName) { + StringBuilder sb = new StringBuilder(); + + if (!xmlName.equals(MANIFEST_NODE_NAME)) { + boolean capitalize = true; + for (char c : xmlName.toCharArray()) { + if (capitalize && c >= 'a' && c <= 'z') { + sb.append((char)(c + 'A' - 'a')); + capitalize = false; + } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + // not a letter -- skip the character and capitalize the next one + capitalize = true; + } else { + sb.append(c); + } + } + } + + sb.insert(0, ANDROID_MANIFEST_STYLEABLE); + return sb.toString(); + } + + /** + * This method performs a sanity check to make sure all the styles declared in the + * manifestMap are actually defined in the actual element descriptors and reachable from + * the manifestElement root node. + */ + private void sanityCheck(Map manifestMap, + ElementDescriptor manifestElement) { + TreeSet elementsDeclared = new TreeSet(); + findAllElementNames(manifestElement, elementsDeclared); + + TreeSet stylesDeclared = new TreeSet(); + for (String styleName : manifestMap.keySet()) { + if (styleName.startsWith(ANDROID_MANIFEST_STYLEABLE)) { + stylesDeclared.add(styleName); + } + } + + for (Iterator it = elementsDeclared.iterator(); it.hasNext();) { + String xmlName = it.next(); + String styleName = guessStyleName(xmlName); + if (stylesDeclared.remove(styleName)) { + it.remove(); + } + } + + StringBuilder sb = new StringBuilder(); + if (!stylesDeclared.isEmpty()) { + sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by the SDK but unknown to ADT: "); + for (String name : stylesDeclared) { + name = guessXmlName(name); + + if (name != stylesDeclared.last()) { + sb.append(", "); //$NON-NLS-1$ + } + } + + AdtPlugin.log(IStatus.WARNING, "%s", sb.toString()); + AdtPlugin.printToConsole((String)null, sb); + sb.setLength(0); + } + + if (!elementsDeclared.isEmpty()) { + sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by ADT but not by the SDK: "); + for (String name : elementsDeclared) { + sb.append(name); + if (name != elementsDeclared.last()) { + sb.append(", "); //$NON-NLS-1$ + } + } + + AdtPlugin.log(IStatus.WARNING, "%s", sb.toString()); + AdtPlugin.printToConsole((String)null, sb); + } + } + + /** + * Performs an approximate translation of the style name into a potential + * xml name. This is more or less the reverse from guessStyleName(). + * + * @return The XML local name for a given style name. + */ + private String guessXmlName(String name) { + StringBuilder sb = new StringBuilder(); + if (ANDROID_MANIFEST_STYLEABLE.equals(name)) { + sb.append(MANIFEST_NODE_NAME); + } else { + name = name.replace(ANDROID_MANIFEST_STYLEABLE, ""); //$NON-NLS-1$ + boolean first_char = true; + for (char c : name.toCharArray()) { + if (c >= 'A' && c <= 'Z') { + if (!first_char) { + sb.append('-'); + } + c = (char) (c - 'A' + 'a'); + } + sb.append(c); + first_char = false; + } + } + return sb.toString(); + } + + /** + * Helper method used by {@link #sanityCheck(Map, ElementDescriptor)} to find all the + * {@link ElementDescriptor} names defined by the tree of descriptors. + *

          + * Note: this assumes no circular reference in the tree of {@link ElementDescriptor}s. + */ + private void findAllElementNames(ElementDescriptor element, TreeSet declared) { + declared.add(element.getXmlName()); + for (ElementDescriptor desc : element.getChildren()) { + findAllElementNames(desc, declared); + } + } + + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java new file mode 100644 index 0000000..eab7f09 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * Describes an 'Application' class XML attribute. It is displayed by a + * {@link UiClassAttributeNode}, that restricts creation and selection to classes + * inheriting from android.app.Application. + */ +public class ApplicationAttributeDescriptor extends TextAttributeDescriptor { + + public ApplicationAttributeDescriptor(String xmlLocalName, String uiName, + String nsUri, String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * @return A new {@link UiClassAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiClassAttributeNode("android.app.Application", //$NON-NLS-1$ + null /* postCreationAction */, false /* mandatory */, this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java new file mode 100644 index 0000000..1144006 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * Describes an XML attribute representing a class name. + * It is displayed by a {@link UiClassAttributeNode}. + */ +public class ClassAttributeDescriptor extends TextAttributeDescriptor { + + /** Superclass of the class value. */ + private String mSuperClassName; + + private IPostTypeCreationAction mPostCreationAction; + + /** indicates if the class parameter is mandatory */ + boolean mMandatory; + + /** + * Creates a new {@link ClassAttributeDescriptor} + * @param superClassName the fully qualified name of the superclass of the class represented + * by the attribute. + * @param xmlLocalName The XML name of the attribute (case sensitive, with android: prefix). + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip A non-empty tooltip string or null. + * @param mandatory indicates if the class attribute is mandatory. + */ + public ClassAttributeDescriptor(String superClassName, + String xmlLocalName, String uiName, String nsUri, + String tooltip, boolean mandatory) { + super(xmlLocalName, uiName, nsUri, tooltip); + mSuperClassName = superClassName; + } + + /** + * Creates a new {@link ClassAttributeDescriptor} + * @param superClassName the fully qualified name of the superclass of the class represented + * by the attribute. + * @param postCreationAction the {@link IPostTypeCreationAction} to be executed on the + * newly created class. + * @param xmlLocalName The XML local name of the attribute (case sensitive). + * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. + * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. + * See {@link AndroidConstants#NS_RESOURCES} for a common value. + * @param tooltip A non-empty tooltip string or null. + * @param mandatory indicates if the class attribute is mandatory. + */ + public ClassAttributeDescriptor(String superClassName, + IPostTypeCreationAction postCreationAction, + String xmlLocalName, String uiName, String nsUri, + String tooltip, boolean mandatory) { + super(xmlLocalName, uiName, nsUri, tooltip); + mSuperClassName = superClassName; + mPostCreationAction = postCreationAction; + } + + /** + * @return A new {@link UiClassAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiClassAttributeNode(mSuperClassName, mPostCreationAction, + mMandatory, this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java new file mode 100644 index 0000000..6e589f7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * Describes a 'Instrumentation' class XML attribute. It is displayed by a + * {@link UiClassAttributeNode}, that restricts creation and selection to classes inheriting from + * android.app.Instrumentation. + */ +public class InstrumentationAttributeDescriptor extends TextAttributeDescriptor { + + public InstrumentationAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * @return A new {@link UiClassAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiClassAttributeNode(AndroidConstants.CLASS_INSTRUMENTATION, + null /* postCreationAction */, true /* mandatory */, this, uiParent); + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java new file mode 100644 index 0000000..d89292b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.model.UiManifestElementNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * {@link ManifestElementDescriptor} describes an XML element node, with its + * element name, its possible attributes, its possible child elements but also + * its display name and tooltip. + * + * This {@link ElementDescriptor} is specialized to create {@link UiManifestElementNode} UI nodes. + */ +public class ManifestElementDescriptor extends ElementDescriptor { + + /** + * Constructs a new {@link ManifestElementDescriptor}. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). + */ + public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children, + boolean mandatory) { + super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); + } + + /** + * Constructs a new {@link ManifestElementDescriptor}. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + */ + public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, + AttributeDescriptor[] attributes, + ElementDescriptor[] children) { + super(xml_name, ui_name, tooltip, sdk_url, attributes, children, false); + } + + /** + * This is a shortcut for + * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, children). + * This constructor is mostly used for unit tests. + * + * @param xml_name The XML element node name. Case sensitive. + */ + public ManifestElementDescriptor(String xml_name, ElementDescriptor[] children) { + super(xml_name, children); + } + + /** + * This is a shortcut for + * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, null). + * This constructor is mostly used for unit tests. + * + * @param xml_name The XML element node name. Case sensitive. + */ + public ManifestElementDescriptor(String xml_name) { + super(xml_name, null); + } + + /** + * @return A new {@link UiElementNode} linked to this descriptor. + */ + @Override + public UiElementNode createUiNode() { + return new UiManifestElementNode(this); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java new file mode 100644 index 0000000..34c5d0d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.model.UiPackageAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * Describes a package XML attribute. It is displayed by a {@link UiPackageAttributeNode}. + */ +public class PackageAttributeDescriptor extends TextAttributeDescriptor { + + public PackageAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * @return A new {@link UiPackageAttributeNode} linked to this descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiPackageAttributeNode(this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java new file mode 100644 index 0000000..3442c24 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +/** + * Action to be executed after an Activity class is created. + */ +class PostActivityCreationAction implements IPostTypeCreationAction { + + private final static PostActivityCreationAction sAction = new PostActivityCreationAction(); + + private PostActivityCreationAction() { + // private constructor to enforce singleton. + } + + + /** + * Returns the action. + */ + public static IPostTypeCreationAction getAction() { + return sAction; + } + + /** + * Processes a newly created Activity. + * + */ + public void processNewType(IType newType) { + try { + String methodContent = + " /** Called when the activity is first created. */\n" + + " @Override\n" + + " public void onCreate(Bundle savedInstanceState) {\n" + + " super.onCreate(savedInstanceState);\n" + + "\n" + + " // TODO Auto-generated method stub\n" + + " }"; + newType.createMethod(methodContent, null /* sibling*/, false /* force */, + new NullProgressMonitor()); + + // we need to add the import for Bundle, so we need the compilation unit. + // Since the type could be enclosed in other types, we loop till we find it. + ICompilationUnit compilationUnit = null; + IJavaElement element = newType; + do { + IJavaElement parentElement = element.getParent(); + if (parentElement != null) { + if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) { + compilationUnit = (ICompilationUnit)parentElement; + } + + element = parentElement; + } else { + break; + } + } while (compilationUnit == null); + + if (compilationUnit != null) { + compilationUnit.createImport(AndroidConstants.CLASS_BUNDLE, + null /* sibling */, new NullProgressMonitor()); + } + } catch (JavaModelException e) { + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java new file mode 100644 index 0000000..5a8137d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; + +/** + * Action to be executed after an BroadcastReceiver class is created. + */ +class PostReceiverCreationAction implements IPostTypeCreationAction { + + private final static PostReceiverCreationAction sAction = new PostReceiverCreationAction(); + + private PostReceiverCreationAction() { + // private constructor to enforce singleton. + } + + /** + * Returns the action. + */ + public static IPostTypeCreationAction getAction() { + return sAction; + } + + /** + * Processes a newly created Activity. + * + */ + public void processNewType(IType newType) { + try { + String methodContent = + " @Override\n" + + " public void onReceive(Context context, Intent intent) {\n" + + " // TODO Auto-generated method stub\n" + + " }"; + newType.createMethod(methodContent, null /* sibling*/, false /* force */, + new NullProgressMonitor()); + + // we need to add the import for Bundle, so we need the compilation unit. + // Since the type could be enclosed in other types, we loop till we find it. + ICompilationUnit compilationUnit = null; + IJavaElement element = newType; + do { + IJavaElement parentElement = element.getParent(); + if (parentElement != null) { + if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) { + compilationUnit = (ICompilationUnit)parentElement; + } + + element = parentElement; + } else { + break; + } + } while (compilationUnit == null); + + if (compilationUnit != null) { + compilationUnit.createImport(AndroidConstants.CLASS_CONTEXT, + null /* sibling */, new NullProgressMonitor()); + compilationUnit.createImport(AndroidConstants.CLASS_INTENT, + null /* sibling */, new NullProgressMonitor()); + } + } catch (JavaModelException e) { + // looks like the class already existed (this happens when the user check to create + // inherited abstract methods). + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java new file mode 100644 index 0000000..4219007 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2007 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.editors.manifest.descriptors; + +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; + +/** + * Describes a Theme/Style XML attribute displayed by a {@link UiResourceAttributeNode} + */ +public final class ThemeAttributeDescriptor extends TextAttributeDescriptor { + + public ThemeAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, + String tooltip) { + super(xmlLocalName, uiName, nsUri, tooltip); + } + + /** + * @return A new {@link UiResourceAttributeNode} linked to this theme descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiResourceAttributeNode(ResourceType.STYLE, this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java new file mode 100644 index 0000000..f8aac1d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java @@ -0,0 +1,624 @@ +/* + * Copyright (C) 2007 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.editors.manifest.model; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.project.BaseProjectHelper; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.SectionHelper; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +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.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.ui.IJavaElementSearchConstants; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction; +import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension; +import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor; +import org.eclipse.jdt.ui.dialogs.ITypeSelectionComponent; +import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension; +import org.eclipse.jdt.ui.wizards.NewClassWizardPage; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.SelectionDialog; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.w3c.dom.Element; + +import java.util.ArrayList; + +/** + * Represents an XML attribute for a class, that can be modified using a simple text field or + * a dialog to choose an existing class. Also, there's a link to create a new class. + *

          + * See {@link UiTextAttributeNode} for more information. + */ +public class UiClassAttributeNode extends UiTextAttributeNode { + + private String mReferenceClass; + private IPostTypeCreationAction mPostCreationAction; + private boolean mMandatory; + + private class HierarchyTypeSelection extends TypeSelectionExtension { + + private IJavaProject mJavaProject; + + public HierarchyTypeSelection(IProject project, String referenceClass) { + mJavaProject = JavaCore.create(project); + mReferenceClass = referenceClass; + } + + @Override + public ITypeInfoFilterExtension getFilterExtension() { + return new ITypeInfoFilterExtension() { + public boolean select(ITypeInfoRequestor typeInfoRequestor) { + String packageName = typeInfoRequestor.getPackageName(); + String typeName = typeInfoRequestor.getTypeName(); + String enclosingType = typeInfoRequestor.getEnclosingName(); + + // build the full class name. + StringBuilder sb = new StringBuilder(packageName); + sb.append('.'); + if (enclosingType.length() > 0) { + sb.append(enclosingType); + sb.append('.'); + } + sb.append(typeName); + + String className = sb.toString(); + + try { + IType type = mJavaProject.findType(className); + if (type != null) { + // get the type hierarchy + ITypeHierarchy hierarchy = type.newSupertypeHierarchy( + new NullProgressMonitor()); + + // if the super class is not the reference class, it may inherit from + // it so we get its supertype. At some point it will be null and we + // will return false; + IType superType = type; + while ((superType = hierarchy.getSuperclass(superType)) != null) { + if (mReferenceClass.equals(superType.getFullyQualifiedName())) { + return true; + } + } + } + } catch (JavaModelException e) { + } + + return false; + } + }; + } + } + + /** + * Classes which implement this interface provide a method processing newly created classes. + */ + public static interface IPostTypeCreationAction { + /** + * Sent to process a newly created class. + * @param newType the IType representing the newly created class. + */ + public void processNewType(IType newType); + } + + /** + * Creates a {@link UiClassAttributeNode} object that will display ui to select or create + * classes. + * @param referenceClass The allowed supertype of the classes that are to be selected + * or created. Can be null. + * @param postCreationAction a {@link IPostTypeCreationAction} object handling post creation + * modification of the class. + * @param mandatory indicates if the class value is mandatory + * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. + */ + public UiClassAttributeNode(String referenceClass, IPostTypeCreationAction postCreationAction, + boolean mandatory, AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + + mReferenceClass = referenceClass; + mPostCreationAction = postCreationAction; + mMandatory = mandatory; + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public void createUiControl(final Composite parent, IManagedForm managedForm) { + setManagedForm(managedForm); + FormToolkit toolkit = managedForm.getToolkit(); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + + StringBuilder label = new StringBuilder(); + label.append("

          "); + label.append(desc.getUiName()); + label.append("

          "); + FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */, + label.toString(), true /* setupLayoutData */); + formText.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + super.linkActivated(e); + handleLabelClick(); + } + }); + formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + SectionHelper.addControlTooltip(formText, desc.getTooltip()); + + Composite composite = toolkit.createComposite(parent); + composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = gl.marginWidth = 0; + composite.setLayout(gl); + // Fixes missing text borders under GTK... also requires adding a 1-pixel margin + // for the text field below + toolkit.paintBordersFor(composite); + + final Text text = toolkit.createText(composite, getCurrentValue()); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK + text.setLayoutData(gd); + Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); + + setTextWidget(text); + + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + handleBrowseClick(); + } + }); + } + + /* (non-java doc) + * + * Add a modify listener that will check the validity of the class + */ + @Override + protected void onAddValidators(final Text text) { + ModifyListener listener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + try { + String textValue = text.getText().trim(); + if (textValue.length() == 0) { + if (mMandatory) { + setErrorMessage("Value is mandatory", text); + } else { + setErrorMessage(null, text); + } + return; + } + // first we need the current java package. + String javaPackage = getManifestPackage(); + + // build the fully qualified name of the class + String className = AndroidManifestHelper.combinePackageAndClassName(javaPackage, + textValue); + + // only test the vilibility for activities. + boolean testVisibility = AndroidConstants.CLASS_ACTIVITY.equals( + mReferenceClass); + + // test the class + setErrorMessage(BaseProjectHelper.testClassForManifest( + BaseProjectHelper.getJavaProject(getProject()), className, + mReferenceClass, testVisibility), text); + } catch (CoreException ce) { + setErrorMessage(ce.getMessage(), text); + } + } + }; + + text.addModifyListener(listener); + + // Make sure the validator removes its message(s) when the widget is disposed + text.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + // we don't want to use setErrorMessage, because we don't want to reset + // the error flag in the UiAttributeNode + getManagedForm().getMessageManager().removeMessage(text, text); + } + }); + + // Finally call the validator once to make sure the initial value is processed + listener.modifyText(null); + } + + private void handleBrowseClick() { + Text text = getTextWidget(); + + // we need to get the project of the manifest. + IProject project = getProject(); + if (project != null) { + + // Create a search scope including only the source folder of the current + // project. + IJavaSearchScope scope = SearchEngine.createJavaSearchScope( + getPackageFragmentRoots(project), false); + + try { + SelectionDialog dlg = JavaUI.createTypeDialog(text.getShell(), + PlatformUI.getWorkbench().getProgressService(), + scope, + IJavaElementSearchConstants.CONSIDER_CLASSES, // style + false, // no multiple selection + "**", //$NON-NLS-1$ //filter + new HierarchyTypeSelection(project, mReferenceClass)); + dlg.setMessage(String.format("Select class name for element %1$s:", + getUiParent().getBreadcrumbTrailDescription(false /* include_root */))); + if (dlg instanceof ITypeSelectionComponent) { + ((ITypeSelectionComponent)dlg).triggerSearch(); + } + + if (dlg.open() == Window.OK) { + Object[] results = dlg.getResult(); + if (results.length == 1) { + handleNewType((IType)results[0]); + } + } + } catch (JavaModelException e1) { + } + } + } + + private void handleLabelClick() { + // get the current value + String className = getTextWidget().getText().trim(); + + // get the package name from the manifest. + String packageName = getManifestPackage(); + + if (className.length() == 0) { + createNewClass(packageName, null /* className */); + } else { + // build back the fully qualified class name. + String fullClassName = className; + if (className.startsWith(".")) { //$NON-NLS-1$ + fullClassName = packageName + className; + } else { + String[] segments = className.split(AndroidConstants.RE_DOT); + if (segments.length == 1) { + fullClassName = packageName + "." + className; //$NON-NLS-1$ + } + } + + // in case the type is enclosed, we need to replace the $ with . + fullClassName = fullClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$ + + // now we try to find the file that contains this class and we open it in the editor. + IProject project = getProject(); + IJavaProject javaProject = JavaCore.create(project); + + try { + IType result = javaProject.findType(fullClassName); + if (result != null) { + JavaUI.openInEditor(result); + } else { + // split the last segment from the fullClassname + int index = fullClassName.lastIndexOf('.'); + if (index != -1) { + createNewClass(fullClassName.substring(0, index), + fullClassName.substring(index+1)); + } else { + createNewClass(packageName, className); + } + } + } catch (JavaModelException e) { + } catch (PartInitException e) { + } + } + } + + private IProject getProject() { + UiElementNode uiNode = getUiParent(); + AndroidEditor editor = uiNode.getEditor(); + IEditorInput input = editor.getEditorInput(); + if (input instanceof IFileEditorInput) { + // from the file editor we can get the IFile object, and from it, the IProject. + IFile file = ((IFileEditorInput)input).getFile(); + return file.getProject(); + } + + return null; + } + + + /** + * Returns the current value of the /manifest/package attribute. + * @return the package or an empty string if not found + */ + private String getManifestPackage() { + // get the root uiNode to get the 'package' attribute value. + UiElementNode rootNode = getUiParent().getUiRoot(); + + Element xmlElement = (Element) rootNode.getXmlNode(); + + if (xmlElement != null) { + return xmlElement.getAttribute(AndroidManifestDescriptors.PACKAGE_ATTR); + } + return ""; //$NON-NLS-1$ + } + + + /** + * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of + * the specified project. + * @param project the project + * @return an array of IPackageFragmentRoot. + */ + private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) { + ArrayList result = new ArrayList(); + try { + IJavaProject javaProject = JavaCore.create(project); + IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); + for (int i = 0; i < roots.length; i++) { + IClasspathEntry entry = roots[i].getRawClasspathEntry(); + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + result.add(roots[i]); + } + } + } catch (JavaModelException e) { + } + + return result.toArray(new IPackageFragmentRoot[result.size()]); + } + + private void handleNewType(IType type) { + Text text = getTextWidget(); + + // get the fully qualified name with $ to properly detect the enclosing types. + String name = type.getFullyQualifiedName('$'); + + String packageValue = getManifestPackage(); + + // check if the class doesn't start with the package. + if (packageValue.length() > 0 && name.startsWith(packageValue)) { + // if it does, we remove the package and the first dot. + name = name.substring(packageValue.length() + 1); + + // look for how many segments we have left. + // if one, just write it that way. + // if more than one, write it with a leading dot. + String[] packages = name.split(AndroidConstants.RE_DOT); + if (packages.length == 1) { + text.setText(name); + } else { + text.setText("." + name); //$NON-NLS-1$ + } + } else { + text.setText(name); + } + } + + private void createNewClass(String packageName, String className) { + // create the wizard page for the class creation, and configure it + NewClassWizardPage page = new NewClassWizardPage(); + + // set the parent class + page.setSuperClass(mReferenceClass, true /* canBeModified */); + + // get the source folders as java elements. + IPackageFragmentRoot[] roots = getPackageFragmentRoots(getProject()); + + IPackageFragmentRoot currentRoot = null; + IPackageFragment currentFragment = null; + int packageMatchCount = -1; + + for (IPackageFragmentRoot root : roots) { + // Get the java element for the package. + // This method is said to always return a IPackageFragment even if the + // underlying folder doesn't exist... + IPackageFragment fragment = root.getPackageFragment(packageName); + if (fragment != null && fragment.exists()) { + // we have a perfect match! we use it. + currentRoot = root; + currentFragment = fragment; + packageMatchCount = -1; + break; + } else { + // we don't have a match. we look for the fragment with the best match + // (ie the closest parent package we can find) + try { + IJavaElement[] children; + children = root.getChildren(); + for (IJavaElement child : children) { + if (child instanceof IPackageFragment) { + fragment = (IPackageFragment)child; + if (packageName.startsWith(fragment.getElementName())) { + // its a match. get the number of segments + String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$ + if (segments.length > packageMatchCount) { + packageMatchCount = segments.length; + currentFragment = fragment; + currentRoot = root; + } + } + } + } + } catch (JavaModelException e) { + // Couldn't get the children: we just ignore this package root. + } + } + } + + ArrayList createdFragments = null; + + if (currentRoot != null) { + // if we have a perfect match, we set it and we're done. + if (packageMatchCount == -1) { + page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); + page.setPackageFragment(currentFragment, true /* canBeModified */); + } else { + // we have a partial match. + // create the package. We have to start with the first segment so that we + // know what to delete in case of a cancel. + try { + createdFragments = new ArrayList(); + + int totalCount = packageName.split("\\.").length; //$NON-NLS-1$ + int count = 0; + int index = -1; + // skip the matching packages + while (count < packageMatchCount) { + index = packageName.indexOf('.', index+1); + count++; + } + + // create the rest of the segments, except for the last one as indexOf will + // return -1; + while (count < totalCount - 1) { + index = packageName.indexOf('.', index+1); + count++; + createdFragments.add(currentRoot.createPackageFragment( + packageName.substring(0, index), + true /* force*/, new NullProgressMonitor())); + } + + // create the last package + createdFragments.add(currentRoot.createPackageFragment( + packageName, true /* force*/, new NullProgressMonitor())); + + // set the root and fragment in the Wizard page + page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); + page.setPackageFragment(createdFragments.get(createdFragments.size()-1), + true /* canBeModified */); + } catch (JavaModelException e) { + // if we can't create the packages, there's a problem. we revert to the default + // package + for (IPackageFragmentRoot root : roots) { + // Get the java element for the package. + // This method is said to always return a IPackageFragment even if the + // underlying folder doesn't exist... + IPackageFragment fragment = root.getPackageFragment(packageName); + if (fragment != null && fragment.exists()) { + page.setPackageFragmentRoot(root, true /* canBeModified*/); + page.setPackageFragment(fragment, true /* canBeModified */); + break; + } + } + } + } + } else if (roots.length > 0) { + // if we haven't found a valid fragment, we set the root to the first source folder. + page.setPackageFragmentRoot(roots[0], true /* canBeModified*/); + } + + // if we have a starting class name we use it + if (className != null) { + page.setTypeName(className, true /* canBeModified*/); + } + + // create the action that will open it the wizard. + OpenNewClassWizardAction action = new OpenNewClassWizardAction(); + action.setConfiguredWizardPage(page); + action.run(); + IJavaElement element = action.getCreatedElement(); + + if (element != null) { + if (element.getElementType() == IJavaElement.TYPE) { + + IType type = (IType)element; + + if (mPostCreationAction != null) { + mPostCreationAction.processNewType(type); + } + + handleNewType(type); + } + } else { + // lets delete the packages we created just for this. + // we need to start with the leaf and go up + if (createdFragments != null) { + try { + for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) { + createdFragments.get(i).delete(true /* force*/, new NullProgressMonitor()); + } + } catch (JavaModelException e) { + e.printStackTrace(); + } + } + } + } + + /** + * Sets the error messages. If message is null, the message is removed. + * @param message the message to set, or null to remove the current message + * @param textWidget the {@link Text} widget associated to the message. + */ + private final void setErrorMessage(String message, Text textWidget) { + if (message != null) { + setHasError(true); + getManagedForm().getMessageManager().addMessage(textWidget, message, null /* data */, + IMessageProvider.ERROR, textWidget); + } else { + setHasError(false); + getManagedForm().getMessageManager().removeMessage(textWidget, textWidget); + } + } + + @Override + public String[] getPossibleValues() { + // TODO: compute a list of existing classes for content assist completion + return null; + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java new file mode 100644 index 0000000..79295a8 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 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.editors.manifest.model; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.manifest.descriptors.ManifestElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.w3c.dom.Element; + +/** + * Represents an XML node that can be modified by the user interface in the XML editor. + *

          + * Each tree viewer used in the application page's parts needs to keep a model representing + * each underlying node in the tree. This interface represents the base type for such a node. + *

          + * Each node acts as an intermediary model between the actual XML model (the real data support) + * and the tree viewers or the corresponding page parts. + *

          + * Element nodes don't contain data per se. Their data is contained in their attributes + * as well as their children's attributes, see {@link UiAttributeNode}. + *

          + * The structure of a given {@link UiElementNode} is declared by a corresponding + * {@link ElementDescriptor}. + */ +public final class UiManifestElementNode extends UiElementNode { + + /** + * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}. + * + * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null. + */ + public UiManifestElementNode(ManifestElementDescriptor elementDescriptor) { + super(elementDescriptor); + } + + /** + * Computes a short string describing the UI node suitable for tree views. + * Uses the element's attribute "android:name" if present, or the "android:label" one + * followed by the element's name. + * + * @return A short string describing the UI node suitable for tree views. + */ + @Override + public String getShortDescription() { + if (getXmlNode() != null && + getXmlNode() instanceof Element && + getXmlNode().hasAttributes()) { + + AndroidManifestDescriptors manifestDescriptors = + getAndroidTarget().getManifestDescriptors(); + + // Application and Manifest nodes have a special treatment: they are unique nodes + // so we don't bother trying to differentiate their strings and we fall back to + // just using the UI name below. + ElementDescriptor desc = getDescriptor(); + if (desc != manifestDescriptors.getManifestElement() && + desc != manifestDescriptors.getApplicationElement()) { + Element elem = (Element) getXmlNode(); + String attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + AndroidManifestDescriptors.ANDROID_NAME_ATTR); + if (attr == null || attr.length() == 0) { + attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + AndroidManifestDescriptors.ANDROID_LABEL_ATTR); + } + if (attr != null && attr.length() > 0) { + return String.format("%1$s (%2$s)", attr, getDescriptor().getUiName()); + } + } + } + + return String.format("%1$s", getDescriptor().getUiName()); + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java new file mode 100644 index 0000000..02fb44f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2007 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.editors.manifest.model; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jdt.ui.actions.OpenNewPackageWizardAction; +import org.eclipse.jdt.ui.actions.ShowInPackageViewAction; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.dialogs.SelectionDialog; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; + +import java.util.ArrayList; + +/** + * Represents an XML attribute for a package, that can be modified using a simple text field or + * a dialog to choose an existing package. Also, there's a link to create a new package. + *

          + * See {@link UiTextAttributeNode} for more information. + */ +public class UiPackageAttributeNode extends UiTextAttributeNode { + + /** + * Creates a {@link UiPackageAttributeNode} object that will display ui to select or create + * a package. + * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. + */ + public UiPackageAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public void createUiControl(final Composite parent, final IManagedForm managedForm) { + setManagedForm(managedForm); + FormToolkit toolkit = managedForm.getToolkit(); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + + StringBuilder label = new StringBuilder(); + label.append("

          "); //$NON-NLS-1$ + label.append(desc.getUiName()); + label.append("

          "); //$NON-NLS-1$ + FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */, + label.toString(), true /* setupLayoutData */); + formText.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + super.linkActivated(e); + doLabelClick(); + } + }); + formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + SectionHelper.addControlTooltip(formText, desc.getTooltip()); + + Composite composite = toolkit.createComposite(parent); + composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = gl.marginWidth = 0; + composite.setLayout(gl); + // Fixes missing text borders under GTK... also requires adding a 1-pixel margin + // for the text field below + toolkit.paintBordersFor(composite); + + final Text text = toolkit.createText(composite, getCurrentValue()); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK + text.setLayoutData(gd); + + setTextWidget(text); + + Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); + + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + doBrowseClick(); + } + }); + + } + + /* (non-java doc) + * Adds a validator to the text field that calls managedForm.getMessageManager(). + */ + @Override + protected void onAddValidators(final Text text) { + ModifyListener listener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + String package_name = text.getText(); + if (package_name.indexOf('.') < 1) { + getManagedForm().getMessageManager().addMessage(text, + "Package name should contain at least two identifiers.", + null /* data */, IMessageProvider.ERROR, text); + } else { + getManagedForm().getMessageManager().removeMessage(text, text); + } + } + }; + + text.addModifyListener(listener); + + // Make sure the validator removes its message(s) when the widget is disposed + text.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + getManagedForm().getMessageManager().removeMessage(text, text); + } + }); + + // Finally call the validator once to make sure the initial value is processed + listener.modifyText(null); + } + + /** + * Handles response to the Browse button by creating a Package dialog. + * */ + private void doBrowseClick() { + Text text = getTextWidget(); + + // we need to get the project of the manifest. + IProject project = getProject(); + if (project != null) { + + try { + SelectionDialog dlg = JavaUI.createPackageDialog(text.getShell(), + JavaCore.create(project), 0); + dlg.setTitle("Select Android Package"); + dlg.setMessage("Select the package for the Android project."); + SelectionDialog.setDefaultImage(AdtPlugin.getAndroidLogo()); + + if (dlg.open() == Window.OK) { + Object[] results = dlg.getResult(); + if (results.length == 1) { + setPackageTextField((IPackageFragment)results[0]); + } + } + } catch (JavaModelException e1) { + } + } + } + + /** + * Handles response to the Label hyper link being activated. + */ + private void doLabelClick() { + // get the current package name + String package_name = getTextWidget().getText().trim(); + + if (package_name.length() == 0) { + createNewPackage(); + } else { + // Try to select the package in the Package Explorer for the current + // project and the current editor's site. + + IProject project = getProject(); + if (project == null) { + AdtPlugin.log(IStatus.ERROR, "Failed to get project for UiPackageAttribute"); //$NON-NLS-1$ + return; + } + + IWorkbenchPartSite site = getUiParent().getEditor().getSite(); + if (site == null) { + AdtPlugin.log(IStatus.ERROR, "Failed to get editor site for UiPackageAttribute"); //$NON-NLS-1$ + return; + } + + for (IPackageFragmentRoot root : getPackageFragmentRoots(project)) { + IPackageFragment fragment = root.getPackageFragment(package_name); + if (fragment != null && fragment.exists()) { + ShowInPackageViewAction action = new ShowInPackageViewAction(site); + action.run(fragment); + // This action's run() doesn't provide the status (although internally it could) + // so we just assume it worked. + return; + } + } + } + } + + /** + * Utility method that returns the project for the current file being edited. + * + * @return The IProject for the current file being edited or null. + */ + private IProject getProject() { + UiElementNode uiNode = getUiParent(); + AndroidEditor editor = uiNode.getEditor(); + IEditorInput input = editor.getEditorInput(); + if (input instanceof IFileEditorInput) { + // from the file editor we can get the IFile object, and from it, the IProject. + IFile file = ((IFileEditorInput)input).getFile(); + return file.getProject(); + } + + return null; + } + + /** + * Utility method that computes and returns the list of {@link IPackageFragmentRoot} + * corresponding to the source folder of the specified project. + * + * @param project the project + * @return an array of IPackageFragmentRoot. Can be empty but not null. + */ + private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) { + ArrayList result = new ArrayList(); + try { + IJavaProject javaProject = JavaCore.create(project); + IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); + for (int i = 0; i < roots.length; i++) { + IClasspathEntry entry = roots[i].getRawClasspathEntry(); + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + result.add(roots[i]); + } + } + } catch (JavaModelException e) { + } + + return result.toArray(new IPackageFragmentRoot[result.size()]); + } + + /** + * Utility method that sets the package's text field to the package fragment's name. + * */ + private void setPackageTextField(IPackageFragment type) { + Text text = getTextWidget(); + + String name = type.getElementName(); + + text.setText(name); + } + + + /** + * Displays and handles a "Create Package Wizard". + * + * This is invoked by doLabelClick() when clicking on the hyperlink label with an + * empty package text field. + */ + private void createNewPackage() { + OpenNewPackageWizardAction action = new OpenNewPackageWizardAction(); + + IProject project = getProject(); + action.setSelection(new StructuredSelection(project)); + action.run(); + + IJavaElement element = action.getCreatedElement(); + if (element != null && + element.exists() && + element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { + setPackageTextField((IPackageFragment) element); + } + } + + @Override + public String[] getPossibleValues() { + // TODO: compute a list of existing packages for content assist completion + return null; + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java new file mode 100644 index 0000000..01b0f8f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.ui.UiElementPart; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +/** + * Application's attributes section part for Application page. + *

          + * This part is displayed at the top of the application page and displays all the possible + * attributes of an application node in the AndroidManifest (icon, class name, label, etc.) + */ +final class ApplicationAttributesPart extends UiElementPart { + + /** Listen to changes to the UI node for and updates the UI */ + private AppNodeUpdateListener mAppNodeUpdateListener; + /** ManagedForm needed to create the UI controls */ + private IManagedForm mManagedForm; + + public ApplicationAttributesPart(Composite body, FormToolkit toolkit, ManifestEditor editor, + UiElementNode applicationUiNode) { + super(body, toolkit, editor, applicationUiNode, + "Application Attributes", // section title + "Defines the attributes specific to the application.", // section description + Section.TWISTIE | Section.EXPANDED); + } + + /** + * Changes and refreshes the Application UI node handle by the this part. + */ + @Override + public void setUiElementNode(UiElementNode uiElementNode) { + super.setUiElementNode(uiElementNode); + + createUiAttributes(mManagedForm); + } + + /* (non-java doc) + * Create the controls to edit the attributes for the given ElementDescriptor. + *

          + * This MUST not be called by the constructor. Instead it must be called from + * initialize (i.e. right after the form part is added to the managed form.) + *

          + * Derived classes can override this if necessary. + * + * @param managedForm The owner managed form + */ + @Override + protected void createFormControls(final IManagedForm managedForm) { + mManagedForm = managedForm; + setTable(createTableLayout(managedForm.getToolkit(), 4 /* numColumns */)); + + mAppNodeUpdateListener = new AppNodeUpdateListener(); + getUiElementNode().addUpdateListener(mAppNodeUpdateListener); + + createUiAttributes(mManagedForm); + } + + @Override + public void dispose() { + super.dispose(); + if (getUiElementNode() != null && mAppNodeUpdateListener != null) { + getUiElementNode().removeUpdateListener(mAppNodeUpdateListener); + mAppNodeUpdateListener = null; + } + } + + @Override + protected void createUiAttributes(IManagedForm managedForm) { + Composite table = getTable(); + if (table == null || managedForm == null) { + return; + } + + // Remove any old UI controls + for (Control c : table.getChildren()) { + c.dispose(); + } + + UiElementNode uiElementNode = getUiElementNode(); + AttributeDescriptor[] attr_desc_list = uiElementNode.getAttributeDescriptors(); + + // Display the attributes in 2 columns: + // attr 0 | attr 4 + // attr 1 | attr 5 + // attr 2 | attr 6 + // attr 3 | attr 7 + // that is we have to fill the grid in order 0, 4, 1, 5, 2, 6, 3, 7 + // thus index = i/2 + (i is odd * n/2) + int n = attr_desc_list.length; + int n2 = (int) Math.ceil(n / 2.0); + for (int i = 0; i < n; i++) { + AttributeDescriptor attr_desc = attr_desc_list[i / 2 + (i & 1) * n2]; + if (attr_desc instanceof XmlnsAttributeDescriptor) { + // Do not show hidden attributes + continue; + } + + UiAttributeNode ui_attr = uiElementNode.findUiAttribute(attr_desc); + if (ui_attr != null) { + ui_attr.createUiControl(table, managedForm); + } else { + // The XML has an extra attribute which wasn't declared in + // AndroidManifestDescriptors. This is not a problem, we just ignore it. + AdtPlugin.log(IStatus.WARNING, + "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ + attr_desc.getXmlLocalName(), + uiElementNode.getDescriptor().getXmlName()); + } + } + + if (n == 0) { + createLabel(table, managedForm.getToolkit(), + "No attributes to display, waiting for SDK to finish loading...", + null /* tooltip */ ); + } + + // Initialize the enabled/disabled state + if (mAppNodeUpdateListener != null) { + mAppNodeUpdateListener.uiElementNodeUpdated(uiElementNode, null /* state, not used */); + } + + // Tell the section that the layout has changed. + layoutChanged(); + } + + /** + * This listener synchronizes the UI with the actual presence of the application XML node. + */ + private class AppNodeUpdateListener implements IUiUpdateListener { + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + // The UiElementNode for the application XML node always exists, even + // if there is no corresponding XML node in the XML file. + // + // We enable the UI here if the XML node is not null. + Composite table = getTable(); + boolean exists = (ui_node.getXmlNode() != null); + if (table != null && table.getEnabled() != exists) { + table.setEnabled(exists); + for (Control c : table.getChildren()) { + c.setEnabled(exists); + } + } + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java new file mode 100644 index 0000000..77527f0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; + + +/** + * Page for "Application" settings, part of the AndroidManifest form editor. + *

          + * Useful reference: + * + * http://www.eclipse.org/articles/Article-Forms/article.html + */ +public final class ApplicationPage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "application_page"; //$NON-NLS-1$ + + /** Container editor */ + ManifestEditor mEditor; + /** The Application Toogle part */ + private ApplicationToggle mTooglePart; + /** The Application Attributes part */ + private ApplicationAttributesPart mAttrPart; + /** The tree view block */ + private UiTreeBlock mTreeBlock; + + public ApplicationPage(ManifestEditor editor) { + super(editor, PAGE_ID, "Application"); // tab's label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Manifest Application"); + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode appUiNode = getUiApplicationNode(); + + Composite body = form.getBody(); + FormToolkit toolkit = managedForm.getToolkit(); + + // We usually prefer to have a ColumnLayout here. However + // MasterDetailsBlock.createContent() below will reset the body's layout to a grid layout. + mTooglePart = new ApplicationToggle(body, toolkit, mEditor, appUiNode); + mTooglePart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + managedForm.addPart(mTooglePart); + mAttrPart = new ApplicationAttributesPart(body, toolkit, mEditor, appUiNode); + mAttrPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + managedForm.addPart(mAttrPart); + + mTreeBlock = new UiTreeBlock(mEditor, appUiNode, + false /* autoCreateRoot */, + null /* element filters */, + "Application Nodes", + "List of all elements in the application"); + mTreeBlock.createContent(managedForm); + } + + /** + * Retrieves the application UI node. Since this is a mandatory node, it *always* + * exists, even if there is no matching XML node. + */ + private UiElementNode getUiApplicationNode() { + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + if (manifestDescriptor != null) { + ElementDescriptor desc = manifestDescriptor.getApplicationElement(); + return mEditor.getUiRootNode().findUiChildNode(desc.getXmlName()); + } else { + // return the ui root node, as a dummy application root node. + return mEditor.getUiRootNode(); + } + } + + /** + * Changes and refreshes the Application UI node handled by the sub parts. + */ + public void refreshUiApplicationNode() { + UiElementNode appUiNode = getUiApplicationNode(); + if (mTooglePart != null) { + mTooglePart.setUiElementNode(appUiNode); + } + if (mAttrPart != null) { + mAttrPart.setUiElementNode(appUiNode); + } + if (mTreeBlock != null) { + mTreeBlock.changeRootAndDescriptors(appUiNode, + null /* element filters */, + true /* refresh */); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java new file mode 100644 index 0000000..139575d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.ui.UiElementPart; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +/** + * Appllication Toogle section part for application page. + */ +final class ApplicationToggle extends UiElementPart { + + /** Checkbox indicating whether an application node is present */ + private Button mCheckbox; + /** Listen to changes to the UI node for and updates the checkbox */ + private AppNodeUpdateListener mAppNodeUpdateListener; + /** Internal flag to know where we're programmatically modifying the checkbox and we want to + * avoid triggering the checkbox's callback. */ + public boolean mInternalModification; + private FormText mTooltipFormText; + + public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor, + UiElementNode applicationUiNode) { + super(body, toolkit, editor, applicationUiNode, + "Application Toggle", + null, /* description */ + Section.TWISTIE | Section.EXPANDED); + } + + @Override + public void dispose() { + super.dispose(); + if (getUiElementNode() != null && mAppNodeUpdateListener != null) { + getUiElementNode().removeUpdateListener(mAppNodeUpdateListener); + mAppNodeUpdateListener = null; + } + } + + /** + * Changes and refreshes the Application UI node handle by the this part. + */ + @Override + public void setUiElementNode(UiElementNode uiElementNode) { + super.setUiElementNode(uiElementNode); + + updateTooltip(); + + // Set the state of the checkbox + mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(), + UiUpdateState.CHILDREN_CHANGED); + } + + /** + * Create the controls to edit the attributes for the given ElementDescriptor. + *

          + * This MUST not be called by the constructor. Instead it must be called from + * initialize (i.e. right after the form part is added to the managed form.) + * + * @param managedForm The owner managed form + */ + @Override + protected void createFormControls(IManagedForm managedForm) { + FormToolkit toolkit = managedForm.getToolkit(); + Composite table = createTableLayout(toolkit, 1 /* numColumns */); + + mTooltipFormText = createFormText(table, toolkit, true, "

          ", + false /* setupLayoutData */); + updateTooltip(); + + mCheckbox = toolkit.createButton(table, + "Define an tag in the AndroidManifest.xml", + SWT.CHECK); + mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP)); + mCheckbox.setSelection(false); + mCheckbox.addSelectionListener(new CheckboxSelectionListener()); + + mAppNodeUpdateListener = new AppNodeUpdateListener(); + getUiElementNode().addUpdateListener(mAppNodeUpdateListener); + + // Initialize the state of the checkbox + mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(), + UiUpdateState.CHILDREN_CHANGED); + + // Tell the section that the layout has changed. + layoutChanged(); + } + + /** + * Updates the application tooltip in the form text. + * If there is no tooltip, the form text is hidden. + */ + private void updateTooltip() { + boolean isVisible = false; + + String tooltip = getUiElementNode().getDescriptor().getTooltip(); + if (tooltip != null) { + tooltip = DescriptorsUtils.formatFormText(tooltip, + getUiElementNode().getDescriptor(), + Sdk.getCurrent().getDocumentationBaseUrl()); + + mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */); + mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo()); + mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener()); + isVisible = true; + } + + mTooltipFormText.setVisible(isVisible); + } + + /** + * This listener synchronizes the XML application node when the checkbox + * is changed by the user. + */ + private class CheckboxSelectionListener extends SelectionAdapter { + private Node mUndoXmlNode; + private Node mUndoXmlParent; + private Node mUndoXmlNextNode; + private Node mUndoXmlNextElement; + private Document mUndoXmlDocument; + + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + if (!mInternalModification && getUiElementNode() != null) { + getUiElementNode().getEditor().wrapUndoRecording( + mCheckbox.getSelection() + ? "Create or restore Application node" + : "Remove Application node", + new Runnable() { + public void run() { + getUiElementNode().getEditor().editXmlModel(new Runnable() { + public void run() { + if (mCheckbox.getSelection()) { + // The user wants an node. + // Either restore a previous one + // or create a full new one. + boolean create = true; + if (mUndoXmlNode != null) { + create = !restoreApplicationNode(); + } + if (create) { + getUiElementNode().createXmlNode(); + } + } else { + // Users no longer wants the node. + removeApplicationNode(); + } + } + }); + } + }); + } + } + + /** + * Restore a previously "saved" application node. + * + * @return True if the node could be restored, false otherwise. + */ + private boolean restoreApplicationNode() { + if (mUndoXmlDocument == null || mUndoXmlNode == null) { + return false; + } + + // Validate node references... + mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent); + mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode); + mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement); + + if (mUndoXmlParent == null){ + // If the parent node doesn't exist, try to find a new manifest node. + // If it doesn't exist, create it. + mUndoXmlParent = getUiElementNode().getUiParent().prepareCommit(); + mUndoXmlNextNode = null; + mUndoXmlNextElement = null; + } + + boolean success = false; + if (mUndoXmlParent != null) { + // If the parent is still around, reuse the same node. + + // Ideally we want to insert the node before what used to be its next sibling. + // If that's not possible, we try to insert it before its next sibling element. + // If that's not possible either, it will be inserted at the end of the parent's. + Node next = mUndoXmlNextNode; + if (next == null) { + next = mUndoXmlNextElement; + } + mUndoXmlParent.insertBefore(mUndoXmlNode, next); + if (next == null) { + Text sep = mUndoXmlDocument.createTextNode("\n"); //$NON-NLS-1$ + mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag + } + success = true; + } + + // Remove internal references to avoid using them twice + mUndoXmlParent = null; + mUndoXmlNextNode = null; + mUndoXmlNextElement = null; + mUndoXmlNode = null; + mUndoXmlDocument = null; + return success; + } + + /** + * Validates that the given xml_node is still either the root node or one of its + * direct descendants. + * + * @param root_node The root of the node hierarchy to examine. + * @param xml_node The XML node to find. + * @return Returns xml_node if it is, otherwise returns null. + */ + private Node validateNode(Node root_node, Node xml_node) { + if (root_node == xml_node) { + return xml_node; + } else { + for (Node node = root_node.getFirstChild(); node != null; + node = node.getNextSibling()) { + if (root_node == xml_node || validateNode(node, xml_node) != null) { + return xml_node; + } + } + } + return null; + } + + /** + * Removes the node from the hierarchy. + * Before doing that, we try to remember where it was so that we can put it back + * in the same place. + */ + private void removeApplicationNode() { + // Make sure the node actually exists... + Node xml_node = getUiElementNode().getXmlNode(); + if (xml_node == null) { + return; + } + + // Save its parent, next sibling and next element + mUndoXmlDocument = xml_node.getOwnerDocument(); + mUndoXmlParent = xml_node.getParentNode(); + mUndoXmlNextNode = xml_node.getNextSibling(); + mUndoXmlNextElement = mUndoXmlNextNode; + while (mUndoXmlNextElement != null && + mUndoXmlNextElement.getNodeType() != Node.ELEMENT_NODE) { + mUndoXmlNextElement = mUndoXmlNextElement.getNextSibling(); + } + + // Actually remove the node from the hierarchy and keep it here. + // The returned node looses its parents/siblings pointers. + mUndoXmlNode = getUiElementNode().deleteXmlNode(); + } + } + + /** + * This listener synchronizes the UI (i.e. the checkbox) with the + * actual presence of the application XML node. + */ + private class AppNodeUpdateListener implements IUiUpdateListener { + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + // The UiElementNode for the application XML node always exists, even + // if there is no corresponding XML node in the XML file. + // + // To update the checkbox to reflect the actual state, we just need + // to check if the XML node is null. + try { + mInternalModification = true; + boolean exists = ui_node.getXmlNode() != null; + if (mCheckbox.getSelection() != exists) { + mCheckbox.setSelection(exists); + } + } finally { + mInternalModification = false; + } + + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java new file mode 100644 index 0000000..86d0dd1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Page for instrumentation settings, part of the AndroidManifest form editor. + */ +public final class InstrumentationPage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "instrumentation_page"; //$NON-NLS-1$ + + /** Container editor */ + ManifestEditor mEditor; + + private UiTreeBlock mTreeBlock; + + public InstrumentationPage(ManifestEditor editor) { + super(editor, PAGE_ID, "Instrumentation"); // tab's label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Manifest Instrumentation"); + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode manifest = mEditor.getUiRootNode(); + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + + ElementDescriptor[] descriptorFilters = null; + if (manifestDescriptor != null) { + descriptorFilters = new ElementDescriptor[] { + manifestDescriptor.getInstrumentationElement(), + }; + } + + mTreeBlock = new UiTreeBlock(mEditor, manifest, + true /* autoCreateRoot */, + descriptorFilters, + "Instrumentation", + "List of instrumentations defined in the manifest"); + mTreeBlock.createContent(managedForm); + } + + /** + * Changes and refreshes the Application UI node handled by the sub parts. + */ + public void refreshUiNode() { + if (mTreeBlock != null) { + UiElementNode manifest = mEditor.getUiRootNode(); + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + + mTreeBlock.changeRootAndDescriptors(manifest, + new ElementDescriptor[] { + manifestDescriptor.getInstrumentationElement() + }, + true /* refresh */); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java new file mode 100644 index 0000000..66af84c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.common.project.ExportHelper; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.forms.events.HyperlinkAdapter; +import org.eclipse.ui.forms.events.HyperlinkEvent; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.part.FileEditorInput; + +/** + * Export section part for overview page. + */ +final class OverviewExportPart extends ManifestSectionPart { + + private final OverviewPage mOverviewPage; + + public OverviewExportPart(OverviewPage overviewPage, Composite body, FormToolkit toolkit, ManifestEditor editor) { + super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */); + mOverviewPage = overviewPage; + Section section = getSection(); + section.setText("Exporting"); + section.setDescription("To export the application for distribution, you have the following options:"); + + Composite table = createTableLayout(toolkit, 2 /* numColumns */); + + StringBuffer buf = new StringBuffer(); + buf.append("
        • "); //$NON-NLS-1$ + buf.append("Use the Export Wizard"); + buf.append(""); //$NON-NLS-1$ + buf.append(" to export and sign an APK"); + buf.append("
        • "); //$NON-NLS-1$ + buf.append("
        • "); //$NON-NLS-1$ + buf.append("Export an unsigned APK"); + buf.append(""); //$NON-NLS-1$ + buf.append(" and sign it manually"); + buf.append("
        • "); //$NON-NLS-1$ + + FormText text = createFormText(table, toolkit, true, buf.toString(), + false /* setupLayoutData */); + text.addHyperlinkListener(new HyperlinkAdapter() { + @Override + public void linkActivated(HyperlinkEvent e) { + // get the project from the editor + IEditorInput input = mOverviewPage.mEditor.getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + IFile file = fileInput.getFile(); + IProject project = file.getProject(); + + if ("manual".equals(e.data)) { //$NON-NLS-1$ + // now we can export an unsigned apk for the project. + ExportHelper.exportProject(project); + } else { + // call the export wizard + ExportHelper.startExportWizard(project); + } + } + } + }); + + layoutChanged(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java new file mode 100644 index 0000000..026b760 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.UiElementPart; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.w3c.dom.Node; + +/** + * Generic info section part for overview page + */ +final class OverviewInfoPart extends UiElementPart { + + private IManagedForm mManagedForm; + + public OverviewInfoPart(Composite body, FormToolkit toolkit, ManifestEditor editor) { + super(body, toolkit, editor, + null, // uiElementNode + "General Information", // section title + "Defines general information about the AndroidManifest.xml", // section description + Section.TWISTIE | Section.EXPANDED); + } + + /** + * Retrieves the UiElementNode that this part will edit. The node must exist + * and can't be null, by design, because it's a mandatory node. + */ + private static UiElementNode getManifestUiNode(ManifestEditor editor) { + AndroidManifestDescriptors manifestDescriptors = editor.getManifestDescriptors(); + if (manifestDescriptors != null) { + ElementDescriptor desc = manifestDescriptors.getManifestElement(); + if (editor.getUiRootNode().getDescriptor() == desc) { + return editor.getUiRootNode(); + } else { + return editor.getUiRootNode().findUiChildNode(desc.getXmlName()); + } + } + + // No manifest descriptor: we have a dummy UiRootNode, so we return that. + // The editor will be reloaded once we have the proper descriptors anyway. + return editor.getUiRootNode(); + } + + /** + * Retrieves the uses-sdk UI node. Since this is a mandatory node, it *always* + * exists, even if there is no matching XML node. + */ + private UiElementNode getUsesSdkUiNode(ManifestEditor editor) { + AndroidManifestDescriptors manifestDescriptors = editor.getManifestDescriptors(); + if (manifestDescriptors != null) { + ElementDescriptor desc = manifestDescriptors.getUsesSdkElement(); + return editor.getUiRootNode().findUiChildNode(desc.getXmlName()); + } + + // No manifest descriptor: we have a dummy UiRootNode, so we return that. + // The editor will be reloaded once we have the proper descriptors anyway. + return editor.getUiRootNode(); + } + + /** + * Overridden in order to capture the current managed form. + * + * {@inheritDoc} + */ + @Override + protected void createFormControls(final IManagedForm managedForm) { + mManagedForm = managedForm; + super.createFormControls(managedForm); + } + + /** + * Removes any existing Attribute UI widgets and recreate them if the SDK has changed. + *

          + * This is called by {@link OverviewPage#refreshUiApplicationNode()} when the + * SDK has changed. + */ + public void onSdkChanged() { + createUiAttributes(mManagedForm); + } + + /** + * Overridden to add the description and the ui attributes of both the + * manifest and uses-sdk UI nodes. + *

          + * {@inheritDoc} + */ + @Override + protected void fillTable(Composite table, IManagedForm managedForm) { + int n = 0; + + UiElementNode uiNode = getManifestUiNode(getEditor()); + n += insertUiAttributes(uiNode, table, managedForm); + + uiNode = getUsesSdkUiNode(getEditor()); + n += insertUiAttributes(uiNode, table, managedForm); + + if (n == 0) { + createLabel(table, managedForm.getToolkit(), + "No attributes to display, waiting for SDK to finish loading...", + null /* tooltip */ ); + } + + layoutChanged(); + } + + /** + * Overridden to tests whether either the manifest or uses-sdk nodes parts are dirty. + *

          + * {@inheritDoc} + * + * @return true if the part is dirty, false + * otherwise. + */ + @Override + public boolean isDirty() { + boolean dirty = super.isDirty(); + + if (!dirty) { + UiElementNode uiNode = getManifestUiNode(getEditor()); + if (uiNode != null) { + for (UiAttributeNode ui_attr : uiNode.getUiAttributes()) { + if (ui_attr.isDirty()) { + markDirty(); + dirty = true; + break; + } + } + } + } + + if (!dirty) { + UiElementNode uiNode = getUsesSdkUiNode(getEditor()); + if (uiNode != null) { + for (UiAttributeNode ui_attr : uiNode.getUiAttributes()) { + if (ui_attr.isDirty()) { + markDirty(); + dirty = true; + break; + } + } + } + } + + return dirty; + } + + /** + * Overridden to save both the manifest or uses-sdk nodes. + *

          + * {@inheritDoc} + */ + @Override + public void commit(boolean onSave) { + final UiElementNode manifestUiNode = getManifestUiNode(getEditor()); + final UiElementNode usesSdkUiNode = getUsesSdkUiNode(getEditor()); + + getEditor().editXmlModel(new Runnable() { + public void run() { + if (manifestUiNode != null && manifestUiNode.isDirty()) { + for (UiAttributeNode ui_attr : manifestUiNode.getUiAttributes()) { + ui_attr.commit(); + } + } + + if (usesSdkUiNode != null && usesSdkUiNode.isDirty()) { + for (UiAttributeNode ui_attr : usesSdkUiNode.getUiAttributes()) { + ui_attr.commit(); + } + + if (!usesSdkUiNode.isDirty()) { + // Remove the XML element if it is empty. + // Rather than rely on the internal UI state, actually check that the + // XML element has no attributes and no child element so that we don't + // trash some user-generated content. + Node element = usesSdkUiNode.prepareCommit(); + if (element != null && + !element.hasAttributes() && + !element.hasChildNodes()) { + // Important note: we MUST NOT use usesSdkUiNode.deleteXmlNode() + // here, as it would clear the UiAttribute list and thus break the + // link between the controls and the ui attribute field. + // Instead what we want is simply to remove the XML node and let the + // UiElementNode node know. + Node parent = element.getParentNode(); + if (parent != null) { + parent.removeChild(element); + usesSdkUiNode.loadFromXmlNode(null /*xml_node*/); + } + } + } + } + } + }); + + // We need to call super's commit after we synchronized the nodes to make sure we + // reset the dirty flag after all the side effects from committing have occurred. + super.commit(onSave); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java new file mode 100644 index 0000000..d637a8f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +/** + * Links section part for overview page. + */ +final class OverviewLinksPart extends ManifestSectionPart { + + private final ManifestEditor mEditor; + private FormText mFormText; + + public OverviewLinksPart(Composite body, FormToolkit toolkit, ManifestEditor editor) { + super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */); + mEditor = editor; + Section section = getSection(); + section.setText("Links"); + section.setDescription("The content of the Android Manifest is made up of three sections. You can also edit the XML directly."); + + Composite table = createTableLayout(toolkit, 2 /* numColumns */); + + StringBuffer buf = new StringBuffer(); + buf.append(String.format("

        • ", // $NON-NLS-1$ + ApplicationPage.PAGE_ID)); + buf.append("Application"); + buf.append(""); //$NON-NLS-1$ + buf.append(": Activities, intent filters, providers, services and receivers."); + buf.append("
        • "); //$NON-NLS-1$ + + buf.append(String.format("
        • ", // $NON-NLS-1$ + PermissionPage.PAGE_ID)); + buf.append("Permission"); + buf.append(""); //$NON-NLS-1$ + buf.append(": Permissions defined and permissions used."); + buf.append("
        • "); //$NON-NLS-1$ + + buf.append(String.format("
        • ", // $NON-NLS-1$ + InstrumentationPage.PAGE_ID)); + buf.append("Instrumentation"); + buf.append(""); //$NON-NLS-1$ + buf.append(": Instrumentation defined."); + buf.append("
        • "); //$NON-NLS-1$ + + buf.append(String.format("
        • ", // $NON-NLS-1$ + ManifestEditor.TEXT_EDITOR_ID)); + buf.append("XML Source"); + buf.append(""); //$NON-NLS-1$ + buf.append(": Directly edit the AndroidManifest.xml file."); + buf.append("
        • "); //$NON-NLS-1$ + + buf.append("
        • "); // $NON-NLS-1$ + buf.append("Documentation: Documentation from the Android SDK for AndroidManifest.xml."); // $NON-NLS-1$ + buf.append("
        • "); //$NON-NLS-1$ + buf.append("
          "); //$NON-NLS-1$ + + mFormText = createFormText(table, toolkit, true, buf.toString(), + false /* setupLayoutData */); + + AndroidManifestDescriptors manifestDescriptor = editor.getManifestDescriptors(); + + Image androidLogo = AdtPlugin.getAndroidLogo(); + mFormText.setImage("android_img", androidLogo); //$NON-NLS-1$ + + if (manifestDescriptor != null) { + mFormText.setImage("app_img", getIcon(manifestDescriptor.getApplicationElement())); //$NON-NLS-1$ + mFormText.setImage("perm_img", getIcon(manifestDescriptor.getPermissionElement())); //$NON-NLS-1$ + mFormText.setImage("inst_img", getIcon(manifestDescriptor.getInstrumentationElement())); //$NON-NLS-1$ + } else { + mFormText.setImage("app_img", androidLogo); //$NON-NLS-1$ + mFormText.setImage("perm_img", androidLogo); //$NON-NLS-1$ + mFormText.setImage("inst_img", androidLogo); //$NON-NLS-1$ + } + mFormText.addHyperlinkListener(editor.createHyperlinkListener()); + } + + /** + * Update the UI with information from the new descriptors. + *

          At this point, this only refreshes the icons. + *

          + * This is called by {@link OverviewPage#refreshUiApplicationNode()} when the + * SDK has changed. + */ + public void onSdkChanged() { + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + if (manifestDescriptor != null) { + mFormText.setImage("app_img", getIcon(manifestDescriptor.getApplicationElement())); //$NON-NLS-1$ + mFormText.setImage("perm_img", getIcon(manifestDescriptor.getPermissionElement())); //$NON-NLS-1$ + mFormText.setImage("inst_img", getIcon(manifestDescriptor.getInstrumentationElement())); //$NON-NLS-1$ + } + } + + private Image getIcon(ElementDescriptor desc) { + if (desc != null && desc.getIcon() != null) { + return desc.getIcon(); + } + + return AdtPlugin.getAndroidLogo(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java new file mode 100644 index 0000000..62954bd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ColumnLayout; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; + + +/** + * Page for overview settings, part of the AndroidManifest form editor. + *

          + * Useful reference: + * + * http://www.eclipse.org/articles/Article-Forms/article.html + */ +public final class OverviewPage extends FormPage { + + /** Page id used for switching tabs programmatically */ + final static String PAGE_ID = "overview_page"; //$NON-NLS-1$ + + /** Container editor */ + ManifestEditor mEditor; + /** Overview part (attributes for manifest) */ + private OverviewInfoPart mOverviewPart; + /** Overview link part */ + private OverviewLinksPart mOverviewLinkPart; + + public OverviewPage(ManifestEditor editor) { + super(editor, PAGE_ID, "Overview"); // tab's label, user visible, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Manifest Overview"); + form.setImage(AdtPlugin.getAndroidLogo()); + + Composite body = form.getBody(); + FormToolkit toolkit = managedForm.getToolkit(); + ColumnLayout cl = new ColumnLayout(); + cl.minNumColumns = cl.maxNumColumns = 1; + body.setLayout(cl); + mOverviewPart = new OverviewInfoPart(body, toolkit, mEditor); + managedForm.addPart(mOverviewPart); + managedForm.addPart(new OverviewExportPart(this, body, toolkit, mEditor)); + mOverviewLinkPart = new OverviewLinksPart(body, toolkit, mEditor); + managedForm.addPart(mOverviewLinkPart); + } + + /** + * Changes and refreshes the Application UI node handle by the sub parts. + */ + public void refreshUiApplicationNode() { + if (mOverviewPart != null) { + mOverviewPart.onSdkChanged(); + } + + if (mOverviewLinkPart != null) { + mOverviewLinkPart.onSdkChanged(); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java new file mode 100644 index 0000000..41ba22e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2007 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.editors.manifest.pages; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Page for permissions settings, part of the AndroidManifest form editor. + *

          + * Useful reference: + * + * http://www.eclipse.org/articles/Article-Forms/article.html + */ +public final class PermissionPage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "permission_page"; //$NON-NLS-1$ + + /** Container editor */ + ManifestEditor mEditor; + + private UiTreeBlock mTreeBlock; + + public PermissionPage(ManifestEditor editor) { + super(editor, PAGE_ID, "Permissions"); // tab label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Manifest Permissions"); + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode manifest = mEditor.getUiRootNode(); + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + + ElementDescriptor[] descriptorFilters = null; + if (manifestDescriptor != null) { + descriptorFilters = new ElementDescriptor[] { + manifestDescriptor.getPermissionElement(), + manifestDescriptor.getUsesPermissionElement(), + manifestDescriptor.getPermissionGroupElement(), + manifestDescriptor.getPermissionTreeElement() + }; + } + mTreeBlock = new UiTreeBlock(mEditor, manifest, + true /* autoCreateRoot */, + descriptorFilters, + "Permissions", + "List of permissions defined and used by the manifest"); + mTreeBlock.createContent(managedForm); + } + + /** + * Changes and refreshes the Application UI node handled by the sub parts. + */ + public void refreshUiNode() { + if (mTreeBlock != null) { + UiElementNode manifest = mEditor.getUiRootNode(); + AndroidManifestDescriptors manifestDescriptor = mEditor.getManifestDescriptors(); + + mTreeBlock.changeRootAndDescriptors(manifest, + new ElementDescriptor[] { + manifestDescriptor.getPermissionElement(), + manifestDescriptor.getUsesPermissionElement(), + manifestDescriptor.getPermissionGroupElement(), + manifestDescriptor.getPermissionTreeElement() + }, + true /* refresh */); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java new file mode 100644 index 0000000..bf76d53 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java @@ -0,0 +1,33 @@ +/* + * 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.editors.menu; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidContentAssist; + +/** + * Content Assist Processor for /res/menu XML files + */ +class MenuContentAssist extends AndroidContentAssist { + + /** + * Constructor for LayoutContentAssist + */ + public MenuContentAssist() { + super(AndroidTargetData.DESCRIPTOR_MENU); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuEditor.java new file mode 100644 index 0000000..cff1746 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuEditor.java @@ -0,0 +1,184 @@ +/* + * 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.editors.menu; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidXPathFactory; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; + +/** + * Multi-page form editor for /res/menu XML files. + */ +public class MenuEditor extends AndroidEditor { + + public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".menu.MenuEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiRootNode; + + /** + * Creates the form editor for resources XML files. + */ + public MenuEditor() { + super(); + } + + /** + * Returns the root node of the UI element hierarchy, which here is + * the "menu" node. + */ + @Override + public UiElementNode getUiRootNode() { + return mUiRootNode; + } + + // ---- Base Class Overrides ---- + + /** + * Returns whether the "save as" operation is supported by this editor. + *

          + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Create the various form pages. + */ + @Override + protected void createFormPages() { + try { + addPage(new MenuTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ + } + + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", file.getName())); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // init the ui root on demand + initUiRootNode(false /*force*/); + + mUiRootNode.setXmlDocument(xml_doc); + if (xml_doc != null) { + ElementDescriptor root_desc = mUiRootNode.getDescriptor(); + try { + XPath xpath = AndroidXPathFactory.newXPath(); + Node node = (Node) xpath.evaluate("/" + root_desc.getXmlName(), //$NON-NLS-1$ + xml_doc, + XPathConstants.NODE); + if (node == null && root_desc.isMandatory()) { + // Create the root element if it doesn't exist yet (for empty new documents) + node = mUiRootNode.createXmlNode(); + } + + // Refresh the manifest UI node and all its descendants + mUiRootNode.loadFromXmlNode(node); + + // TODO ? startMonitoringMarkers(); + } catch (XPathExpressionException e) { + AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ + root_desc.getXmlName()); + } + } + + super.xmlModelChanged(xml_doc); + } + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiRootNode is recreated even if it already exists. + */ + @Override + protected void initUiRootNode(boolean force) { + // The root UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + Document doc = null; + if (mUiRootNode != null) { + doc = mUiRootNode.getXmlDocument(); + } + + // get the target data from the opened file (and its project) + AndroidTargetData data = getTargetData(); + + ElementDescriptor desc; + if (data == null) { + desc = new ElementDescriptor("temp", null /*children*/); + } else { + desc = data.getMenuDescriptors().getDescriptor(); + } + + mUiRootNode = desc.createUiNode(); + mUiRootNode.setEditor(this); + + onDescriptorsChanged(doc); + } + } + + // ---- Local Methods ---- + + /** + * Reloads the UI manifest node from the XML, and calls the pages to update. + */ + private void onDescriptorsChanged(Document document) { + if (document != null) { + mUiRootNode.loadFromXmlNode(document); + } else { + mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlNode()); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java new file mode 100644 index 0000000..a5e3b09 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * 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.editors.menu; + + +import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in MenuContentAssist. + */ +public class MenuSourceViewerConfig extends AndroidSourceViewerConfig { + + public MenuSourceViewerConfig() { + super(new MenuContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java new file mode 100644 index 0000000..edbfa5e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java @@ -0,0 +1,62 @@ +/* + * 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.editors.menu; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Page for the menu form editor. + */ +public final class MenuTreePage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "layout_tree_page"; //$NON-NLS-1$ + + /** Container editor */ + MenuEditor mEditor; + + public MenuTreePage(MenuEditor editor) { + super(editor, PAGE_ID, "Layout"); // tab's label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Menu"); + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode rootNode = mEditor.getUiRootNode(); + UiTreeBlock block = new UiTreeBlock(mEditor, rootNode, + true /* autoCreateRoot */, + null /* no element filters */, + "Menu Elements", + "List of all menu elements in this XML file."); + block.createContent(managedForm); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java new file mode 100644 index 0000000..34c7bb2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java @@ -0,0 +1,196 @@ +/* + * 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.editors.menu.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; + +import java.util.ArrayList; +import java.util.Map; + + +/** + * Complete description of the menu structure. + */ +public final class MenuDescriptors implements IDescriptorProvider { + + public static final String MENU_ROOT_ELEMENT = "menu"; //$NON-NLS-1$ + + /** The root element descriptor. */ + private ElementDescriptor mDescriptor = null; + + /** @return the root descriptor. */ + public ElementDescriptor getDescriptor() { + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mDescriptor.getChildren(); + } + + /** + * Updates the document descriptor. + *

          + * It first computes the new children of the descriptor and then updates them + * all at once. + * + * @param styleMap The map style => attributes from the attrs.xml file + */ + public synchronized void updateDescriptors(Map styleMap) { + + // There are 3 elements: menu, item and group. + // The root element MUST be a menu. + // A top menu can contain items or group: + // - top groups can contain top items + // - top items can contain sub-menus + // A sub menu can contains sub items or sub groups: + // - sub groups can contain sub items + // - sub items cannot contain anything + + if (mDescriptor == null) { + mDescriptor = createElement(styleMap, + MENU_ROOT_ELEMENT, // xmlName + "Menu", // uiName, + null, // TODO SDK URL + null, // extraAttribute + null, // childrenElements, + true /* mandatory */); + } + + // -- sub menu can have sub_items, sub_groups but not sub_menus + + ElementDescriptor sub_item = createElement(styleMap, + "item", // xmlName //$NON-NLS-1$ + "Item", // uiName, + null, // TODO SDK URL + null, // extraAttribute + null, // childrenElements, + false /* mandatory */); + + ElementDescriptor sub_group = createElement(styleMap, + "group", // xmlName //$NON-NLS-1$ + "Group", // uiName, + null, // TODO SDK URL + null, // extraAttribute + new ElementDescriptor[] { sub_item }, // childrenElements, + false /* mandatory */); + + ElementDescriptor sub_menu = createElement(styleMap, + MENU_ROOT_ELEMENT, // xmlName //$NON-NLS-1$ + "Sub-Menu", // uiName, + null, // TODO SDK URL + null, // extraAttribute + new ElementDescriptor[] { sub_item, sub_group }, // childrenElements, + true /* mandatory */); + + // -- top menu can have all top groups and top items (which can have sub menus) + + ElementDescriptor top_item = createElement(styleMap, + "item", // xmlName //$NON-NLS-1$ + "Item", // uiName, + null, // TODO SDK URL + null, // extraAttribute + new ElementDescriptor[] { sub_menu }, // childrenElements, + false /* mandatory */); + + ElementDescriptor top_group = createElement(styleMap, + "group", // xmlName //$NON-NLS-1$ + "Group", // uiName, + null, // TODO SDK URL + null, // extraAttribute + new ElementDescriptor[] { top_item }, // childrenElements, + false /* mandatory */); + + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor("android", //$NON-NLS-1$ + AndroidConstants.NS_RESOURCES); + + updateElement(mDescriptor, styleMap, "Menu", xmlns); //$NON-NLS-1$ + mDescriptor.setChildren(new ElementDescriptor[] { top_item, top_group }); + } + + /** + * Returns a new ElementDescriptor constructed from the information given here + * and the javadoc & attributes extracted from the style map if any. + */ + private ElementDescriptor createElement( + Map styleMap, + String xmlName, String uiName, String sdkUrl, + AttributeDescriptor extraAttribute, + ElementDescriptor[] childrenElements, boolean mandatory) { + + ElementDescriptor element = new ElementDescriptor(xmlName, uiName, null, sdkUrl, + null, childrenElements, mandatory); + + return updateElement(element, styleMap, + getStyleName(xmlName), + extraAttribute); + } + + /** + * Updates an ElementDescriptor with the javadoc & attributes extracted from the style + * map if any. + */ + private ElementDescriptor updateElement(ElementDescriptor element, + Map styleMap, + String styleName, + AttributeDescriptor extraAttribute) { + ArrayList descs = new ArrayList(); + + DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; + if (style != null) { + DescriptorsUtils.appendAttributes(descs, + null, // elementName + AndroidConstants.NS_RESOURCES, + style.getAttributes(), + null, // requiredAttributes + null); // overrides + element.setTooltip(style.getJavaDoc()); + } + + if (extraAttribute != null) { + descs.add(extraAttribute); + } + + element.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); + return element; + } + + /** + * Returns the style name (i.e. the name found in attrs.xml) + * for a given XML element name. + *

          + * The rule is that all elements have for style name: + * - their xml name capitalized + * - a "Menu" prefix, except for

          itself which is just "Menu". + */ + private String getStyleName(String xmlName) { + String styleName = DescriptorsUtils.capitalize(xmlName); + + // This is NOT the UI Name but the expected internal style name + final String MENU_STYLE_BASE_NAME = "Menu"; //$NON-NLS-1$ + + if (!styleName.equals(MENU_STYLE_BASE_NAME)) { + styleName = MENU_STYLE_BASE_NAME + styleName; + } + return styleName; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java new file mode 100644 index 0000000..c9c8e17 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007 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.editors.resources; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidContentAssist; + +/** + * Content Assist Processor for /res/values and /res/drawable XML files + */ +class ResourcesContentAssist extends AndroidContentAssist { + + /** + * Constructor for ResourcesContentAssist + */ + public ResourcesContentAssist() { + super(AndroidTargetData.DESCRIPTOR_RESOURCES); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java new file mode 100644 index 0000000..46a9112 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 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.editors.resources; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidXPathFactory; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; + +/** + * Multi-page form editor for /res/values and /res/drawable XML files. + */ +public class ResourcesEditor extends AndroidEditor { + + public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".resources.ResourcesEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiElementNode mUiResourcesNode; + + + /** + * Creates the form editor for resources XML files. + */ + public ResourcesEditor() { + super(); + } + + /** + * Returns the root node of the UI element hierarchy, which + * here is the "resources" node. + */ + @Override + public UiElementNode getUiRootNode() { + return mUiResourcesNode; + } + + // ---- Base Class Overrides ---- + + /** + * Returns whether the "save as" operation is supported by this editor. + *

          + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Create the various form pages. + */ + @Override + protected void createFormPages() { + try { + addPage(new ResourcesTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(IStatus.ERROR, "Error creating nested page"); //$NON-NLS-1$ + AdtPlugin.getDefault().getLog().log(e.getStatus()); + } + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", + file.getName())); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // init the ui root on demand + initUiRootNode(false /*force*/); + + mUiResourcesNode.setXmlDocument(xml_doc); + if (xml_doc != null) { + ElementDescriptor resources_desc = + ResourcesDescriptors.getInstance().getElementDescriptor(); + try { + XPath xpath = AndroidXPathFactory.newXPath(); + Node node = (Node) xpath.evaluate("/" + resources_desc.getXmlName(), //$NON-NLS-1$ + xml_doc, + XPathConstants.NODE); + assert node != null && node.getNodeName().equals(resources_desc.getXmlName()); + + // Refresh the manifest UI node and all its descendants + mUiResourcesNode.loadFromXmlNode(node); + } catch (XPathExpressionException e) { + AdtPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ + resources_desc.getXmlName()); + } + } + + super.xmlModelChanged(xml_doc); + } + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiRootNode is recreated even if it already exists. + */ + @Override + protected void initUiRootNode(boolean force) { + // The manifest UI node is always created, even if there's no corresponding XML node. + if (mUiResourcesNode == null || force) { + ElementDescriptor resources_desc = + ResourcesDescriptors.getInstance().getElementDescriptor(); + mUiResourcesNode = resources_desc.createUiNode(); + mUiResourcesNode.setEditor(this); + + onDescriptorsChanged(); + } + } + + // ---- Local Methods ---- + + private void onDescriptorsChanged() { + // nothing to be done, as the descriptor are static for now. + // FIXME Update when the descriptors are not static + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java new file mode 100644 index 0000000..1804312 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2007 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.editors.resources; + + +import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in ResourcesContentAssist. + */ +public class ResourcesSourceViewerConfig extends AndroidSourceViewerConfig { + + public ResourcesSourceViewerConfig() { + super(new ResourcesContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java new file mode 100644 index 0000000..5c1b0e1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007 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.editors.resources; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.resources.IFile; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.part.FileEditorInput; + +/** + * Page for instrumentation settings, part of the AndroidManifest form editor. + */ +public final class ResourcesTreePage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "res_tree_page"; //$NON-NLS-1$ + + /** Container editor */ + ResourcesEditor mEditor; + + public ResourcesTreePage(ResourcesEditor editor) { + super(editor, PAGE_ID, "Resources"); // tab's label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + + String configText = null; + IEditorInput input = mEditor.getEditorInput(); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput)input; + IFile iFile = fileInput.getFile(); + + ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile); + if (resFolder != null) { + configText = resFolder.getConfiguration().toDisplayString(); + } + } + + if (configText != null) { + form.setText(String.format("Android Resources (%1$s)", configText)); + } else { + form.setText("Android Resources"); + } + + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode resources = mEditor.getUiRootNode(); + UiTreeBlock block = new UiTreeBlock(mEditor, resources, + true /* autoCreateRoot */, + null /* no element filters */, + "Resources Elements", + "List of all resources elements in this XML file."); + block.createContent(managedForm); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java new file mode 100644 index 0000000..1d01260 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java @@ -0,0 +1,144 @@ +/* + * 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Mobile Country Code. + */ +public final class CountryCodeQualifier extends ResourceQualifier { + /** Default pixel density value. This means the property is not set. */ + private final static int DEFAULT_CODE = -1; + + private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$ + + private int mCode = DEFAULT_CODE; + + public static final String NAME = "Mobile Country Code"; + + /** + * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, + * null is returned. + * @param segment the folder segment from which to create a qualifier. + * @return a new {@link CountryCodeQualifier} object or null + */ + public static CountryCodeQualifier getQualifier(String segment) { + Matcher m = sCountryCodePattern.matcher(segment); + if (m.matches()) { + String v = m.group(1); + + int code = -1; + try { + code = Integer.parseInt(v); + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid number. + return null; + } + + CountryCodeQualifier qualifier = new CountryCodeQualifier(); + qualifier.mCode = code; + return qualifier; + } + + return null; + } + + /** + * Returns the folder name segment for the given value. This is equivalent to calling + * {@link #toString()} on a {@link CountryCodeQualifier} object. + * @param value the value of the qualifier, as returned by {@link #getCode()}. + */ + public static String getFolderSegment(int code) { + if (code != DEFAULT_CODE && code >= 100 && code <=999) { // code is 3 digit.) { + return String.format("mcc%1$d", code); //$NON-NLS-1$ + } + + return ""; //$NON-NLS-1$ + } + + public int getCode() { + return mCode; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Country Code"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("mcc"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mCode != DEFAULT_CODE; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + CountryCodeQualifier qualifier = getQualifier(value); + if (qualifier != null) { + config.setCountryCodeQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof CountryCodeQualifier) { + return mCode == ((CountryCodeQualifier)qualifier).mCode; + } + + return false; + } + + @Override + public int hashCode() { + return mCode; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + return getFolderSegment(mCode); + } + + @Override + public String getStringValue() { + if (mCode != DEFAULT_CODE) { + return String.format("MCC %1$d", mCode); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java new file mode 100644 index 0000000..3c3e11f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; + + +/** + * Represents the configuration for Resource Folders. All the properties have a default + * value which means that the property is not set. + */ +public final class FolderConfiguration implements Comparable { + public final static String QUALIFIER_SEP = "-"; //$NON-NLS-1$ + + private final ResourceQualifier[] mQualifiers = new ResourceQualifier[INDEX_COUNT]; + + private final static int INDEX_COUNTRY_CODE = 0; + private final static int INDEX_NETWORK_CODE = 1; + private final static int INDEX_LANGUAGE = 2; + private final static int INDEX_REGION = 3; + private final static int INDEX_SCREEN_ORIENTATION = 4; + private final static int INDEX_PIXEL_DENSITY = 5; + private final static int INDEX_TOUCH_TYPE = 6; + private final static int INDEX_KEYBOARD_STATE = 7; + private final static int INDEX_TEXT_INPUT_METHOD = 8; + private final static int INDEX_NAVIGATION_METHOD = 9; + private final static int INDEX_SCREEN_DIMENSION = 10; + private final static int INDEX_COUNT = 11; + + /** + * Sets the config from the qualifiers of a given config. + * @param config + */ + public void set(FolderConfiguration config) { + for (int i = 0 ; i < INDEX_COUNT ; i++) { + mQualifiers[i] = config.mQualifiers[i]; + } + } + + /** + * Removes the qualifiers from the receiver if they are present (and valid) + * in the given configuration. + */ + public void substract(FolderConfiguration config) { + for (int i = 0 ; i < INDEX_COUNT ; i++) { + if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) { + mQualifiers[i] = null; + } + } + } + + /** + * Returns the first invalid qualifier, or null if they are all valid (or if none + * exists). + */ + public ResourceQualifier getInvalidQualifier() { + for (int i = 0 ; i < INDEX_COUNT ; i++) { + if (mQualifiers[i] != null && mQualifiers[i].isValid() == false) { + return mQualifiers[i]; + } + } + + // all allocated qualifiers are valid, we return null. + return null; + } + + /** + * Returns whether the Region qualifier is valid. Region qualifier can only be present if a + * Language qualifier is present as well. + * @return true if the Region qualifier is valid. + */ + public boolean checkRegion() { + if (mQualifiers[INDEX_LANGUAGE] == null && mQualifiers[INDEX_REGION] != null) { + return false; + } + + return true; + } + + /** + * Adds a qualifier to the {@link FolderConfiguration} + * @param qualifier the {@link ResourceQualifier} to add. + */ + public void addQualifier(ResourceQualifier qualifier) { + if (qualifier instanceof CountryCodeQualifier) { + mQualifiers[INDEX_COUNTRY_CODE] = qualifier; + } else if (qualifier instanceof NetworkCodeQualifier) { + mQualifiers[INDEX_NETWORK_CODE] = qualifier; + } else if (qualifier instanceof LanguageQualifier) { + mQualifiers[INDEX_LANGUAGE] = qualifier; + } else if (qualifier instanceof RegionQualifier) { + mQualifiers[INDEX_REGION] = qualifier; + } else if (qualifier instanceof ScreenOrientationQualifier) { + mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier; + } else if (qualifier instanceof PixelDensityQualifier) { + mQualifiers[INDEX_PIXEL_DENSITY] = qualifier; + } else if (qualifier instanceof TouchScreenQualifier) { + mQualifiers[INDEX_TOUCH_TYPE] = qualifier; + } else if (qualifier instanceof KeyboardStateQualifier) { + mQualifiers[INDEX_KEYBOARD_STATE] = qualifier; + } else if (qualifier instanceof TextInputMethodQualifier) { + mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier; + } else if (qualifier instanceof NavigationMethodQualifier) { + mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier; + } else if (qualifier instanceof ScreenDimensionQualifier) { + mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier; + } + } + + /** + * Removes a given qualifier from the {@link FolderConfiguration}. + * @param qualifier the {@link ResourceQualifier} to remove. + */ + public void removeQualifier(ResourceQualifier qualifier) { + for (int i = 0 ; i < INDEX_COUNT ; i++) { + if (mQualifiers[i] == qualifier) { + mQualifiers[i] = null; + return; + } + } + } + + public void setCountryCodeQualifier(CountryCodeQualifier qualifier) { + mQualifiers[INDEX_COUNTRY_CODE] = qualifier; + } + + public CountryCodeQualifier getCountryCodeQualifier() { + return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE]; + } + + public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) { + mQualifiers[INDEX_NETWORK_CODE] = qualifier; + } + + public NetworkCodeQualifier getNetworkCodeQualifier() { + return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE]; + } + + public void setLanguageQualifier(LanguageQualifier qualifier) { + mQualifiers[INDEX_LANGUAGE] = qualifier; + } + + public LanguageQualifier getLanguageQualifier() { + return (LanguageQualifier)mQualifiers[INDEX_LANGUAGE]; + } + + public void setRegionQualifier(RegionQualifier qualifier) { + mQualifiers[INDEX_REGION] = qualifier; + } + + public RegionQualifier getRegionQualifier() { + return (RegionQualifier)mQualifiers[INDEX_REGION]; + } + + public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) { + mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier; + } + + public ScreenOrientationQualifier getScreenOrientationQualifier() { + return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION]; + } + + public void setPixelDensityQualifier(PixelDensityQualifier qualifier) { + mQualifiers[INDEX_PIXEL_DENSITY] = qualifier; + } + + public PixelDensityQualifier getPixelDensityQualifier() { + return (PixelDensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY]; + } + + public void setTouchTypeQualifier(TouchScreenQualifier qualifier) { + mQualifiers[INDEX_TOUCH_TYPE] = qualifier; + } + + public TouchScreenQualifier getTouchTypeQualifier() { + return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE]; + } + + public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) { + mQualifiers[INDEX_KEYBOARD_STATE] = qualifier; + } + + public KeyboardStateQualifier getKeyboardStateQualifier() { + return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE]; + } + + public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) { + mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier; + } + + public TextInputMethodQualifier getTextInputMethodQualifier() { + return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD]; + } + + public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) { + mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier; + } + + public NavigationMethodQualifier getNavigationMethodQualifier() { + return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD]; + } + + public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) { + mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier; + } + + public ScreenDimensionQualifier getScreenDimensionQualifier() { + return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION]; + } + + /** + * Returns whether an object is equals to the receiver. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if (obj instanceof FolderConfiguration) { + FolderConfiguration fc = (FolderConfiguration)obj; + for (int i = 0 ; i < INDEX_COUNT ; i++) { + ResourceQualifier qualifier = mQualifiers[i]; + ResourceQualifier fcQualifier = fc.mQualifiers[i]; + if (qualifier != null) { + if (qualifier.equals(fcQualifier) == false) { + return false; + } + } else if (fcQualifier != null) { + return false; + } + } + + return true; + } + + return false; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + /** + * Returns whether the Configuration has only default values. + */ + public boolean isDefault() { + for (ResourceQualifier irq : mQualifiers) { + if (irq != null) { + return false; + } + } + + return true; + } + + /** + * Returns the name of a folder with the configuration. + */ + public String getFolderName(ResourceFolderType folder) { + StringBuilder result = new StringBuilder(folder.getName()); + + for (ResourceQualifier qualifier : mQualifiers) { + if (qualifier != null) { + result.append(QUALIFIER_SEP); + result.append(qualifier.toString()); + } + } + + return result.toString(); + } + + /** + * Returns a string valid for usage in a folder name, or null if the configuration + * is default. + */ + @Override + public String toString() { + StringBuilder result = null; + + for (ResourceQualifier irq : mQualifiers) { + if (irq != null) { + if (result == null) { + result = new StringBuilder(); + } else { + result.append(QUALIFIER_SEP); + } + result.append(irq.toString()); + } + } + + if (result != null) { + return result.toString(); + } else { + return null; + } + } + + /** + * Returns a string valid for display purpose. + */ + public String toDisplayString() { + if (isDefault()) { + return "default"; + } + + StringBuilder result = null; + int index = 0; + ResourceQualifier qualifier = null; + + // pre- language/region qualifiers + while (index < INDEX_LANGUAGE) { + qualifier = mQualifiers[index++]; + if (qualifier != null) { + if (result == null) { + result = new StringBuilder(); + } else { + result.append(", "); //$NON-NLS-1$ + } + result.append(qualifier.getStringValue()); + + } + } + + // process the language/region qualifier in a custom way, if there are both non null. + if (mQualifiers[INDEX_LANGUAGE] != null && mQualifiers[INDEX_REGION] != null) { + String language = mQualifiers[INDEX_LANGUAGE].getStringValue(); + String region = mQualifiers[INDEX_REGION].getStringValue(); + + if (result == null) { + result = new StringBuilder(); + } else { + result.append(", "); //$NON-NLS-1$ + } + result.append(String.format("%s_%s", language, region)); //$NON-NLS-1$ + + index += 2; + } + + // post language/region qualifiers. + while (index < INDEX_COUNT) { + qualifier = mQualifiers[index++]; + if (qualifier != null) { + if (result == null) { + result = new StringBuilder(); + } else { + result.append(", "); //$NON-NLS-1$ + } + result.append(qualifier.getStringValue()); + + } + } + + return result.toString(); + } + + public int compareTo(FolderConfiguration folderConfig) { + // default are always at the top. + if (isDefault()) { + if (folderConfig.isDefault()) { + return 0; + } + return -1; + } + + // now we compare the qualifiers + for (int i = 0 ; i < INDEX_COUNT; i++) { + ResourceQualifier qualifier1 = mQualifiers[i]; + ResourceQualifier qualifier2 = folderConfig.mQualifiers[i]; + + if (qualifier1 == null) { + if (qualifier2 == null) { + continue; + } else { + return -1; + } + } else { + if (qualifier2 == null) { + return 1; + } else { + int result = qualifier1.compareTo(qualifier2); + + if (result == 0) { + continue; + } + + return result; + } + } + } + + // if we arrive here, all the qualifier matches + return 0; + } + + /** + * Returns whether the configuration match the given reference config. + *

          A match means that: + *

            + *
          • This config does not use any qualifier not used by the reference config
          • + *
          • The qualifier used by this config have the same values as the qualifiers of + * the reference config.
          • + *
          + * @param referenceConfig The reference configuration to test against. + * @return the number of matching qualifiers or -1 if the configurations are not compatible. + */ + public int match(FolderConfiguration referenceConfig) { + int matchCount = 0; + + for (int i = 0 ; i < INDEX_COUNT ; i++) { + ResourceQualifier testQualifier = mQualifiers[i]; + ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i]; + + // we only care if testQualifier is non null. If it's null, it's a match but + // without increasing the matchCount. + if (testQualifier != null) { + if (referenceQualifier == null) { + return -1; + } else if (testQualifier.equals(referenceQualifier) == false) { + return -1; + } + + // the qualifier match, increment the count + matchCount++; + } + } + return matchCount; + } + + /** + * Returns the index of the first non null {@link ResourceQualifier} starting at index + * startIndex + * @param startIndex + * @return -1 if no qualifier was found. + */ + public int getHighestPriorityQualifier(int startIndex) { + for (int i = startIndex ; i < INDEX_COUNT ; i++) { + if (mQualifiers[i] != null) { + return i; + } + } + + return -1; + } + + /** + * Create default qualifiers. + */ + public void createDefault() { + mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier(); + mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier(); + mQualifiers[INDEX_LANGUAGE] = new LanguageQualifier(); + mQualifiers[INDEX_REGION] = new RegionQualifier(); + mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier(); + mQualifiers[INDEX_PIXEL_DENSITY] = new PixelDensityQualifier(); + mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier(); + mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier(); + mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier(); + mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier(); + mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier(); + } + + /** + * Returns an array of all the non null qualifiers. + */ + public ResourceQualifier[] getQualifiers() { + int count = 0; + for (int i = 0 ; i < INDEX_COUNT ; i++) { + if (mQualifiers[i] != null) { + count++; + } + } + + ResourceQualifier[] array = new ResourceQualifier[count]; + int index = 0; + for (int i = 0 ; i < INDEX_COUNT ; i++) { + if (mQualifiers[i] != null) { + array[index++] = mQualifiers[i]; + } + } + + return array; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java new file mode 100644 index 0000000..ad232ed --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java @@ -0,0 +1,180 @@ +/* + * 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + + + +/** + * Resource Qualifier for keyboard state. + */ +public final class KeyboardStateQualifier extends ResourceQualifier { + + public static final String NAME = "Keyboard State"; + + private KeyboardState mValue = null; + + /** + * Screen Orientation enum. + */ + public static enum KeyboardState { + EXPOSED("keysexposed", "Exposed"), //$NON-NLS-1$ + HIDDEN("keyshidden", "Hidden"); //$NON-NLS-1$ + + private String mValue; + private String mDisplayValue; + + private KeyboardState(String value, String displayValue) { + mValue = value; + mDisplayValue = displayValue; + } + + /** + * Returns the enum for matching the provided qualifier value. + * @param value The qualifier value. + * @return the enum for the qualifier value or null if no matching was found. + */ + static KeyboardState getEnum(String value) { + for (KeyboardState orient : values()) { + if (orient.mValue.equals(value)) { + return orient; + } + } + + return null; + } + + public String getValue() { + return mValue; + } + + public String getDisplayValue() { + return mDisplayValue; + } + + public static int getIndex(KeyboardState value) { + int i = 0; + for (KeyboardState input : values()) { + if (value == input) { + return i; + } + + i++; + } + + return -1; + } + + public static KeyboardState getByIndex(int index) { + int i = 0; + for (KeyboardState value : values()) { + if (i == index) { + return value; + } + i++; + } + return null; + } + } + + public KeyboardStateQualifier() { + // pass + } + + public KeyboardStateQualifier(KeyboardState value) { + mValue = value; + } + + public KeyboardState getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Keyboard"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("keyboard"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + KeyboardState orientation = KeyboardState.getEnum(value); + if (orientation != null) { + KeyboardStateQualifier qualifier = new KeyboardStateQualifier(); + qualifier.mValue = orientation; + config.setKeyboardStateQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof KeyboardStateQualifier) { + return mValue == ((KeyboardStateQualifier)qualifier).mValue; + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return mValue.getValue(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue.getDisplayValue(); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java new file mode 100644 index 0000000..99c3a43 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Language. + */ +public final class LanguageQualifier extends ResourceQualifier { + private final static Pattern sLanguagePattern = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$ + + public static final String NAME = "Language"; + + private String mValue; + + /** + * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, + * null is returned. + * @param segment the folder segment from which to create a qualifier. + * @return a new {@link LanguageQualifier} object or null + */ + public static LanguageQualifier getQualifier(String segment) { + if (sLanguagePattern.matcher(segment).matches()) { + LanguageQualifier qualifier = new LanguageQualifier(); + qualifier.mValue = segment; + + return qualifier; + } + return null; + } + + /** + * Returns the folder name segment for the given value. This is equivalent to calling + * {@link #toString()} on a {@link LanguageQualifier} object. + * @param value the value of the qualifier, as returned by {@link #getValue()}. + */ + public static String getFolderSegment(String value) { + String segment = value.toLowerCase(); + if (sLanguagePattern.matcher(segment).matches()) { + return segment; + } + + return null; + } + + public String getValue() { + if (mValue != null) { + return mValue; + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return NAME; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("language"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + LanguageQualifier qualifier = getQualifier(value); + if (qualifier != null) { + config.setLanguageQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof LanguageQualifier) { + if (mValue == null) { + return ((LanguageQualifier)qualifier).mValue == null; + } + return mValue.equals(((LanguageQualifier)qualifier).mValue); + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return getFolderSegment(mValue); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue; + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java new file mode 100644 index 0000000..1a2cf53 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + + + +/** + * Resource Qualifier for Navigation Method. + */ +public final class NavigationMethodQualifier extends ResourceQualifier { + + public static final String NAME = "Navigation Method"; + + private NavigationMethod mValue; + + /** + * Navigation Method enum. + */ + public static enum NavigationMethod { + DPAD("dpad", "D-pad"), //$NON-NLS-1$ + TRACKBALL("trackball", "Trackball"), //$NON-NLS-1$ + WHEEL("wheel", "Wheel"), //$NON-NLS-1$ + NONAV("nonav", "No Navigation"); //$NON-NLS-1$ + + private String mValue; + private String mDisplay; + + private NavigationMethod(String value, String display) { + mValue = value; + mDisplay = display; + } + + /** + * Returns the enum for matching the provided qualifier value. + * @param value The qualifier value. + * @return the enum for the qualifier value or null if no matching was found. + */ + static NavigationMethod getEnum(String value) { + for (NavigationMethod orient : values()) { + if (orient.mValue.equals(value)) { + return orient; + } + } + + return null; + } + + public String getValue() { + return mValue; + } + + public String getDisplayValue() { + return mDisplay; + } + + public static int getIndex(NavigationMethod value) { + int i = 0; + for (NavigationMethod nav : values()) { + if (nav == value) { + return i; + } + + i++; + } + + return -1; + } + + public static NavigationMethod getByIndex(int index) { + int i = 0; + for (NavigationMethod value : values()) { + if (i == index) { + return value; + } + i++; + } + return null; + } + } + + public NavigationMethodQualifier() { + // pass + } + + public NavigationMethodQualifier(NavigationMethod value) { + mValue = value; + } + + public NavigationMethod getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Navigation"; + } + + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("navpad"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + NavigationMethod method = NavigationMethod.getEnum(value); + if (method != null) { + NavigationMethodQualifier qualifier = new NavigationMethodQualifier(); + qualifier.mValue = method; + config.setNavigationMethodQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof NavigationMethodQualifier) { + return mValue == ((NavigationMethodQualifier)qualifier).mValue; + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return mValue.getValue(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue.getDisplayValue(); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java new file mode 100644 index 0000000..ce527a4 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java @@ -0,0 +1,156 @@ +/* + * 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Mobile Network Code Pixel Density. + */ +public final class NetworkCodeQualifier extends ResourceQualifier { + /** Default pixel density value. This means the property is not set. */ + private final static int DEFAULT_CODE = -1; + + private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$ + + private int mCode = DEFAULT_CODE; + + public final static String NAME = "Mobile Network Code"; + + /** + * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, + * null is returned. + * @param segment the folder segment from which to create a qualifier. + * @return a new {@link CountryCodeQualifier} object or null + */ + public static NetworkCodeQualifier getQualifier(String segment) { + Matcher m = sNetworkCodePattern.matcher(segment); + if (m.matches()) { + String v = m.group(1); + + int code = -1; + try { + code = Integer.parseInt(v); + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid number. + return null; + } + + NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); + qualifier.mCode = code; + return qualifier; + } + + return null; + } + + /** + * Returns the folder name segment for the given value. This is equivalent to calling + * {@link #toString()} on a {@link NetworkCodeQualifier} object. + * @param value the value of the qualifier, as returned by {@link #getCode()}. + */ + public static String getFolderSegment(int code) { + if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit. + return String.format("mnc%1$d", code); //$NON-NLS-1$ + } + + return ""; //$NON-NLS-1$ + } + + public int getCode() { + return mCode; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Network Code"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("mnc"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mCode != DEFAULT_CODE; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + Matcher m = sNetworkCodePattern.matcher(value); + if (m.matches()) { + String v = m.group(1); + + int code = -1; + try { + code = Integer.parseInt(v); + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid number. + return false; + } + + NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); + qualifier.mCode = code; + config.setNetworkCodeQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof NetworkCodeQualifier) { + return mCode == ((NetworkCodeQualifier)qualifier).mCode; + } + + return false; + } + + @Override + public int hashCode() { + return mCode; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + return getFolderSegment(mCode); + } + + @Override + public String getStringValue() { + if (mCode != DEFAULT_CODE) { + return String.format("MNC %1$d", mCode); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java new file mode 100644 index 0000000..0fd05bf --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Screen Pixel Density. + */ +public final class PixelDensityQualifier extends ResourceQualifier { + /** Default pixel density value. This means the property is not set. */ + private final static int DEFAULT_DENSITY = -1; + + private final static Pattern sPixelDensityPattern = Pattern.compile("^(\\d+)dpi$");//$NON-NLS-1$ + + public static final String NAME = "Pixel Density"; + + private int mValue = DEFAULT_DENSITY; + + /** + * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, + * null is returned. + * @param segment the folder segment from which to create a qualifier. + * @return a new {@link CountryCodeQualifier} object or null + */ + public static PixelDensityQualifier getQualifier(String folderSegment) { + Matcher m = sPixelDensityPattern.matcher(folderSegment); + if (m.matches()) { + String v = m.group(1); + + int density = -1; + try { + density = Integer.parseInt(v); + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid number. + return null; + } + + PixelDensityQualifier qualifier = new PixelDensityQualifier(); + qualifier.mValue = density; + + return qualifier; + } + return null; + } + + /** + * Returns the folder name segment for the given value. This is equivalent to calling + * {@link #toString()} on a {@link NetworkCodeQualifier} object. + * @param value the value of the qualifier, as returned by {@link #getCode()}. + */ + public static String getFolderSegment(int value) { + if (value != DEFAULT_DENSITY) { + return String.format("%1$ddpi", value); //$NON-NLS-1$ + } + + return ""; //$NON-NLS-1$ + } + + public int getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return NAME; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("dpi"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != DEFAULT_DENSITY; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + PixelDensityQualifier qualifier = getQualifier(value); + if (qualifier != null) { + config.setPixelDensityQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof PixelDensityQualifier) { + return mValue == ((PixelDensityQualifier)qualifier).mValue; + } + + return false; + } + + @Override + public int hashCode() { + return mValue; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + return getFolderSegment(mValue); + } + + @Override + public String getStringValue() { + if (mValue != DEFAULT_DENSITY) { + return String.format("%1$d dpi", mValue); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java new file mode 100644 index 0000000..dc4d5fa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Region. + */ +public final class RegionQualifier extends ResourceQualifier { + private final static Pattern sRegionPattern = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$ + + public static final String NAME = "Region"; + + private String mValue; + + /** + * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, + * null is returned. + * @param segment the folder segment from which to create a qualifier. + * @return a new {@link RegionQualifier} object or null + */ + public static RegionQualifier getQualifier(String segment) { + Matcher m = sRegionPattern.matcher(segment); + if (m.matches()) { + RegionQualifier qualifier = new RegionQualifier(); + qualifier.mValue = m.group(1); + + return qualifier; + } + return null; + } + + /** + * Returns the folder name segment for the given value. This is equivalent to calling + * {@link #toString()} on a {@link RegionQualifier} object. + * @param value the value of the qualifier, as returned by {@link #getValue()}. + */ + public static String getFolderSegment(String value) { + if (value != null) { + String segment = "r" + value.toUpperCase(); //$NON-NLS-1$ + if (sRegionPattern.matcher(segment).matches()) { + return segment; + } + } + + return ""; //$NON-NLS-1$ + } + + public String getValue() { + if (mValue != null) { + return mValue; + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return NAME; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("region"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + RegionQualifier qualifier = getQualifier(value); + if (qualifier != null) { + config.setRegionQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof RegionQualifier) { + if (mValue == null) { + return ((RegionQualifier)qualifier).mValue == null; + } + return mValue.equals(((RegionQualifier)qualifier).mValue); + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + return getFolderSegment(mValue); + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue; + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java new file mode 100644 index 0000000..0257afa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import org.eclipse.swt.graphics.Image; + +/** + * Base class for resource qualifiers. + *

          The resource qualifier classes are designed as immutable. + */ +public abstract class ResourceQualifier implements Comparable { + + /** + * Returns the human readable name of the qualifier. + */ + public abstract String getName(); + + /** + * Returns a shorter human readable name for the qualifier. + * @see #getName() + */ + public abstract String getShortName(); + + /** + * Returns the icon for the qualifier. + */ + public abstract Image getIcon(); + + /** + * Returns whether the qualifier has a valid filter value. + */ + public abstract boolean isValid(); + + /** + * Check if the value is valid for this qualifier, and if so sets the value + * into a Folder Configuration. + * @param value The value to check and set. Must not be null. + * @param config The folder configuration to receive the value. Must not be null. + * @return true if the value was valid and was set. + */ + public abstract boolean checkAndSet(String value, FolderConfiguration config); + + /** + * Returns a string formated to be used in a folder name. + *

          This is declared as abstract to force children classes to implement it. + */ + @Override + public abstract String toString(); + + /** + * Returns a string formatted for display purpose. + */ + public abstract String getStringValue(); + + /** + * Returns true if both objects are equal. + *

          This is declared as abstract to force children classes to implement it. + */ + @Override + public abstract boolean equals(Object object); + + /** + * Returns a hash code value for the object. + *

          This is declared as abstract to force children classes to implement it. + */ + @Override + public abstract int hashCode(); + + public final int compareTo(ResourceQualifier o) { + return toString().compareTo(o.toString()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java new file mode 100644 index 0000000..a2cc789 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resource Qualifier for Screen Dimension. + */ +public final class ScreenDimensionQualifier extends ResourceQualifier { + /** Default screen size value. This means the property is not set */ + final static int DEFAULT_SIZE = -1; + + private final static Pattern sDimensionPattern = Pattern.compile( + "^(\\d+)x(\\d+)$"); //$NON-NLS-1$ + + public static final String NAME = "Screen Dimension"; + + /** Screen size 1 value. This is not size X or Y because the folder name always + * contains the biggest size first. So if the qualifier is 400x200, size 1 will always be + * 400 but that'll be X in landscape and Y in portrait. + * Default value is DEFAULT_SIZE */ + private int mValue1 = DEFAULT_SIZE; + + /** Screen size 2 value. This is not size X or Y because the folder name always + * contains the biggest size first. So if the qualifier is 400x200, size 2 will always be + * 200 but that'll be Y in landscape and X in portrait. + * Default value is DEFAULT_SIZE */ + private int mValue2 = DEFAULT_SIZE; + + public int getValue1() { + return mValue1; + } + + public int getValue2() { + return mValue2; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Dimension"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("dimension"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue1 != DEFAULT_SIZE && mValue2 != DEFAULT_SIZE; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + Matcher m = sDimensionPattern.matcher(value); + if (m.matches()) { + String d1 = m.group(1); + String d2 = m.group(2); + + ScreenDimensionQualifier qualifier = getQualifier(d1, d2); + if (qualifier != null) { + config.setScreenDimensionQualifier(qualifier); + return true; + } + } + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof ScreenDimensionQualifier) { + ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier; + return (mValue1 == q.mValue1 && mValue2 == q.mValue2); + } + + return false; + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public static ScreenDimensionQualifier getQualifier(String size1, String size2) { + try { + int s1 = Integer.parseInt(size1); + int s2 = Integer.parseInt(size2); + + ScreenDimensionQualifier qualifier = new ScreenDimensionQualifier(); + + if (s1 > s2) { + qualifier.mValue1 = s1; + qualifier.mValue2 = s2; + } else { + qualifier.mValue1 = s2; + qualifier.mValue2 = s1; + } + + return qualifier; + } catch (NumberFormatException e) { + // looks like the string we extracted wasn't a valid number. + } + + return null; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + return String.format("%1$dx%2$d", mValue1, mValue2); //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue1 != -1 && mValue2 != -1) { + return String.format("%1$dx%2$d", mValue1, mValue2); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java new file mode 100644 index 0000000..e30930f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + +/** + * Resource Qualifier for Screen Orientation. + */ +public final class ScreenOrientationQualifier extends ResourceQualifier { + + public static final String NAME = "Screen Orientation"; + + private ScreenOrientation mValue = null; + + /** + * Screen Orientation enum. + */ + public static enum ScreenOrientation { + PORTRAIT("port", "Portrait"), //$NON-NLS-1$ + LANDSCAPE("land", "Landscape"), //$NON-NLS-1$ + SQUARE("square", "Square"); //$NON-NLS-1$ + + private String mValue; + private String mDisplayValue; + + private ScreenOrientation(String value, String displayValue) { + mValue = value; + mDisplayValue = displayValue; + } + + /** + * Returns the enum for matching the provided qualifier value. + * @param value The qualifier value. + * @return the enum for the qualifier value or null if no matching was found. + */ + static ScreenOrientation getEnum(String value) { + for (ScreenOrientation orient : values()) { + if (orient.mValue.equals(value)) { + return orient; + } + } + + return null; + } + + public String getValue() { + return mValue; + } + + public String getDisplayValue() { + return mDisplayValue; + } + + public static int getIndex(ScreenOrientation orientation) { + int i = 0; + for (ScreenOrientation orient : values()) { + if (orient == orientation) { + return i; + } + + i++; + } + + return -1; + } + + public static ScreenOrientation getByIndex(int index) { + int i = 0; + for (ScreenOrientation orient : values()) { + if (i == index) { + return orient; + } + i++; + } + + return null; + } + } + + public ScreenOrientationQualifier() { + } + + public ScreenOrientationQualifier(ScreenOrientation value) { + mValue = value; + } + + public ScreenOrientation getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Orientation"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("orientation"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + ScreenOrientation orientation = ScreenOrientation.getEnum(value); + if (orientation != null) { + ScreenOrientationQualifier qualifier = new ScreenOrientationQualifier(orientation); + config.setScreenOrientationQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof ScreenOrientationQualifier) { + return mValue == ((ScreenOrientationQualifier)qualifier).mValue; + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return mValue.getValue(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue.getDisplayValue(); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java new file mode 100644 index 0000000..de40138 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + + + + +/** + * Resource Qualifier for Text Input Method. + */ +public final class TextInputMethodQualifier extends ResourceQualifier { + + public static final String NAME = "Text Input Method"; + + private TextInputMethod mValue; + + /** + * Screen Orientation enum. + */ + public static enum TextInputMethod { + NOKEY("nokeys", "No Keys"), //$NON-NLS-1$ + QWERTY("qwerty", "Qwerty"), //$NON-NLS-1$ + TWELVEKEYS("12key", "12 Key"); //$NON-NLS-1$ + + private String mValue; + private String mDisplayValue; + + private TextInputMethod(String value, String displayValue) { + mValue = value; + mDisplayValue = displayValue; + } + + /** + * Returns the enum for matching the provided qualifier value. + * @param value The qualifier value. + * @return the enum for the qualifier value or null if no matching was found. + */ + static TextInputMethod getEnum(String value) { + for (TextInputMethod orient : values()) { + if (orient.mValue.equals(value)) { + return orient; + } + } + + return null; + } + + public String getValue() { + return mValue; + } + + public String getDisplayValue() { + return mDisplayValue; + } + + public static int getIndex(TextInputMethod value) { + int i = 0; + for (TextInputMethod input : values()) { + if (value == input) { + return i; + } + + i++; + } + + return -1; + } + + public static TextInputMethod getByIndex(int index) { + int i = 0; + for (TextInputMethod value : values()) { + if (i == index) { + return value; + } + i++; + } + return null; + } + } + + public TextInputMethodQualifier() { + // pass + } + + public TextInputMethodQualifier(TextInputMethod value) { + mValue = value; + } + + public TextInputMethod getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return "Text Input"; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("text_input"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + TextInputMethod method = TextInputMethod.getEnum(value); + if (method != null) { + TextInputMethodQualifier qualifier = new TextInputMethodQualifier(); + qualifier.mValue = method; + config.setTextInputMethodQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof TextInputMethodQualifier) { + return mValue == ((TextInputMethodQualifier)qualifier).mValue; + } + + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return mValue.getValue(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue.getDisplayValue(); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java new file mode 100644 index 0000000..2390e2c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2007 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.editors.resources.configurations; + +import com.android.ide.eclipse.editors.IconFactory; + +import org.eclipse.swt.graphics.Image; + + +/** + * Resource Qualifier for Touch Screen type. + */ +public final class TouchScreenQualifier extends ResourceQualifier { + + public static final String NAME = "Touch Screen"; + + private TouchScreenType mValue; + + /** + * Screen Orientation enum. + */ + public static enum TouchScreenType { + NOTOUCH("notouch", "No Touch"), //$NON-NLS-1$ + STYLUS("stylus", "Stylus"), //$NON-NLS-1$ + FINGER("finger", "Finger"); //$NON-NLS-1$ + + private String mValue; + private String mDisplayValue; + + private TouchScreenType(String value, String displayValue) { + mValue = value; + mDisplayValue = displayValue; + } + + /** + * Returns the enum for matching the provided qualifier value. + * @param value The qualifier value. + * @return the enum for the qualifier value or null if no matching was found. + */ + static TouchScreenType getEnum(String value) { + for (TouchScreenType orient : values()) { + if (orient.mValue.equals(value)) { + return orient; + } + } + + return null; + } + + public String getValue() { + return mValue; + } + + public String getDisplayValue() { + return mDisplayValue; + } + + public static int getIndex(TouchScreenType touch) { + int i = 0; + for (TouchScreenType t : values()) { + if (t == touch) { + return i; + } + + i++; + } + + return -1; + } + + public static TouchScreenType getByIndex(int index) { + int i = 0; + for (TouchScreenType value : values()) { + if (i == index) { + return value; + } + i++; + } + + return null; + } + } + + public TouchScreenQualifier() { + // pass + } + + public TouchScreenQualifier(TouchScreenType touchValue) { + mValue = touchValue; + } + + public TouchScreenType getValue() { + return mValue; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return NAME; + } + + @Override + public Image getIcon() { + return IconFactory.getInstance().getIcon("touch"); //$NON-NLS-1$ + } + + @Override + public boolean isValid() { + return mValue != null; + } + + @Override + public boolean checkAndSet(String value, FolderConfiguration config) { + TouchScreenType type = TouchScreenType.getEnum(value); + if (type != null) { + TouchScreenQualifier qualifier = new TouchScreenQualifier(); + qualifier.mValue = type; + config.setTouchTypeQualifier(qualifier); + return true; + } + + return false; + } + + @Override + public boolean equals(Object qualifier) { + if (qualifier instanceof TouchScreenQualifier) { + return mValue == ((TouchScreenQualifier)qualifier).mValue; + } + return false; + } + + @Override + public int hashCode() { + if (mValue != null) { + return mValue.hashCode(); + } + + return 0; + } + + /** + * Returns the string used to represent this qualifier in the folder name. + */ + @Override + public String toString() { + if (mValue != null) { + return mValue.getValue(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + public String getStringValue() { + if (mValue != null) { + return mValue.getDisplayValue(); + } + + return ""; //$NON-NLS-1$ + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java new file mode 100644 index 0000000..92288ba --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2007 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.editors.resources.descriptors; + +import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; +import com.android.ide.eclipse.editors.resources.uimodel.UiColorValueNode; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; + +/** + * Describes a Color XML element value displayed by an {@link UiColorValueNode}. + */ +public final class ColorValueDescriptor extends TextValueDescriptor { + + public ColorValueDescriptor(String uiName, String tooltip) { + super(uiName, tooltip); + } + + /** + * @return A new {@link UiResourceAttributeNode} linked to this theme descriptor. + */ + @Override + public UiAttributeNode createUiNode(UiElementNode uiParent) { + return new UiColorValueNode(this, uiParent); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java new file mode 100644 index 0000000..bf83d52 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java @@ -0,0 +1,55 @@ +/* + * 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.editors.resources.descriptors; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.resources.uimodel.UiItemElementNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +/** + * {@link ItemElementDescriptor} is a special version of {@link ElementDescriptor} that + * uses a specialized {@link UiItemElementNode} for display. + */ +public class ItemElementDescriptor extends ElementDescriptor { + + /** + * Constructs a new {@link ItemElementDescriptor} based on its XML name, UI name, + * tooltip, SDK url, attributes list, children list and mandatory. + * + * @param xml_name The XML element node name. Case sensitive. + * @param ui_name The XML element name for the user interface, typically capitalized. + * @param tooltip An optional tooltip. Can be null or empty. + * @param sdk_url An optional SKD URL. Can be null or empty. + * @param attributes The list of allowed attributes. Can be null or empty. + * @param children The list of allowed children. Can be null or empty. + * @param mandatory Whether this node must always exist (even for empty models). A mandatory + * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory + * UI node MUST have an XML node attached and it will cease to exist when the XML node + * ceases to exist. + */ + public ItemElementDescriptor(String xml_name, String ui_name, + String tooltip, String sdk_url, AttributeDescriptor[] attributes, + ElementDescriptor[] children, boolean mandatory) { + super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); + } + + @Override + public UiElementNode createUiNode() { + return new UiItemElementNode(this); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java new file mode 100644 index 0000000..1075897 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2007 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.editors.resources.descriptors; + +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.FlagAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; + + +/** + * Complete description of the structure for resources XML files (under res/values/) + */ +public class ResourcesDescriptors implements IDescriptorProvider { + + // Public attributes names, attributes descriptors and elements descriptors + + public static final String ROOT_ELEMENT = "resources"; //$NON-NLS-1$ + + public static final String NAME_ATTR = "name"; //$NON-NLS-1$ + public static final String TYPE_ATTR = "type"; //$NON-NLS-1$ + + private static final ResourcesDescriptors sThis = new ResourcesDescriptors(); + + /** The {@link ElementDescriptor} for the root Resources element. */ + public final ElementDescriptor mResourcesElement; + + public static ResourcesDescriptors getInstance() { + return sThis; + } + + /* + * @see com.android.ide.eclipse.editors.descriptors.IDescriptorProvider#getRootElementDescriptors() + */ + public ElementDescriptor[] getRootElementDescriptors() { + return new ElementDescriptor[] { mResourcesElement }; + } + + public ElementDescriptor getDescriptor() { + return mResourcesElement; + } + + public ElementDescriptor getElementDescriptor() { + return mResourcesElement; + } + + private ResourcesDescriptors() { + + // Common attributes used in many placed + + // Elements + + ElementDescriptor color_element = new ElementDescriptor( + "color", //$NON-NLS-1$ + "Color", + "A @color@ value specifies an RGB value with an alpha channel, which can be used in various places such as specifying a solid color for a Drawable or the color to use for text. It always begins with a # character and then is followed by the alpha-red-green-blue information in one of the following formats: #RGB, #ARGB, #RRGGBB or #AARRGGBB.", + "http://code.google.com/android/reference/available-resources.html#colorvals", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this color."), + new ColorValueDescriptor( + "Value*", + "A mandatory color value.") + }, + null, // no child nodes + false /* not mandatory */); + + ElementDescriptor string_element = new ElementDescriptor( + "string", //$NON-NLS-1$ + "String", + "@Strings@, with optional simple formatting, can be stored and retrieved as resources. You can add formatting to your string by using three standard HTML tags: b, i, and u. If you use an apostrophe or a quote in your string, you must either escape it or enclose the whole string in the other kind of enclosing quotes.", + "http://code.google.com/android/reference/available-resources.html#stringresources", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this string."), + new TextValueDescriptor( + "Value*", + "A mandatory string value.") + }, + null, // no child nodes + false /* not mandatory */); + + ElementDescriptor item_element = new ItemElementDescriptor( + "item", //$NON-NLS-1$ + "Item", + null, // TODO find javadoc + null, // TODO find link to javadoc + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this resource."), + new ListAttributeDescriptor(TYPE_ATTR, + "Type*", + null /* nsUri */, + "The mandatory type of this resource.", + ResourceType.getNames() + ), + new FlagAttributeDescriptor("format", + "Format", + null /* nsUri */, + "The optional format of this resource.", + new String[] { + "boolean", //$NON-NLS-1$ + "color", //$NON-NLS-1$ + "dimension", //$NON-NLS-1$ + "float", //$NON-NLS-1$ + "fraction", //$NON-NLS-1$ + "integer", //$NON-NLS-1$ + "reference", //$NON-NLS-1$ + "string" //$NON-NLS-1$ + }), + new TextValueDescriptor( + "Value", + "A standard string, hex color value, or reference to any other resource type.") + }, + null, // no child nodes + false /* not mandatory */); + + ElementDescriptor drawable_element = new ElementDescriptor( + "drawable", //$NON-NLS-1$ + "Drawable", + "A @drawable@ defines a rectangle of color. Android accepts color values written in various web-style formats -- a hexadecimal constant in any of the following forms: #RGB, #ARGB, #RRGGBB, #AARRGGBB. Zero in the alpha channel means transparent. The default value is opaque.", + "http://code.google.com/android/reference/available-resources.html#colordrawableresources", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this drawable."), + new TextValueDescriptor( + "Value*", + "A mandatory color value in the form #RGB, #ARGB, #RRGGBB or #AARRGGBB.") + }, + null, // no child nodes + false /* not mandatory */); + + ElementDescriptor dimen_element = new ElementDescriptor( + "dimen", //$NON-NLS-1$ + "Dimension", + "You can create common dimensions to use for various screen elements by defining @dimension@ values in XML. A dimension resource is a number followed by a unit of measurement. Supported units are px (pixels), in (inches), mm (millimeters), pt (points at 72 DPI), dp (density-independent pixels) and sp (scale-independent pixels)", + "http://code.google.com/android/reference/available-resources.html#dimension", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this dimension."), + new TextValueDescriptor( + "Value*", + "A mandatory dimension value is a number followed by a unit of measurement. For example: 10px, 2in, 5sp.") + }, + null, // no child nodes + false /* not mandatory */); + + ElementDescriptor style_element = new ElementDescriptor( + "style", //$NON-NLS-1$ + "Style/Theme", + "Both @styles and themes@ are defined in a style block containing one or more string or numerical values (typically color values), or references to other resources (drawables and so on).", + "http://code.google.com/android/reference/available-resources.html#stylesandthemes", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this theme."), + new TextAttributeDescriptor("parent", // $NON-NLS-1$ + "Parent", + null /* nsUri */, + "An optional parent theme. All values from the specified theme will be inherited into this theme. Any values with identical names that you specify will override inherited values."), + }, + new ElementDescriptor[] { + new ElementDescriptor( + "item", //$NON-NLS-1$ + "Item", + "A value to use in this @theme@. It can be a standard string, a hex color value, or a reference to any other resource type.", + "http://code.google.com/android/reference/available-resources.html#stylesandthemes", //$NON-NLS-1$ + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this item."), + new TextValueDescriptor( + "Value*", + "A mandatory standard string, hex color value, or reference to any other resource type.") + }, + null, // no child nodes + false /* not mandatory */) + }, + false /* not mandatory */); + + ElementDescriptor string_array_element = new ElementDescriptor( + "string-array", //$NON-NLS-1$ + "String Array", + "An array of strings. Strings are added as underlying item elements to the array.", + null, // tooltips + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this string array."), + }, + new ElementDescriptor[] { + new ElementDescriptor( + "item", //$NON-NLS-1$ + "Item", + "A string value to use in this string array.", + null, // tooltip + new AttributeDescriptor[] { + new TextValueDescriptor( + "Value*", + "A mandatory string.") + }, + null, // no child nodes + false /* not mandatory */) + }, + false /* not mandatory */); + + ElementDescriptor integer_array_element = new ElementDescriptor( + "integer-array", //$NON-NLS-1$ + "Integer Array", + "An array of integers. Integers are added as underlying item elements to the array.", + null, // tooltips + new AttributeDescriptor[] { + new TextAttributeDescriptor(NAME_ATTR, + "Name*", + null /* nsUri */, + "The mandatory name used in referring to this integer array."), + }, + new ElementDescriptor[] { + new ElementDescriptor( + "item", //$NON-NLS-1$ + "Item", + "An integer value to use in this integer array.", + null, // tooltip + new AttributeDescriptor[] { + new TextValueDescriptor( + "Value*", + "A mandatory integer.") + }, + null, // no child nodes + false /* not mandatory */) + }, + false /* not mandatory */); + + mResourcesElement = new ElementDescriptor( + ROOT_ELEMENT, + "Resources", + null, + "http://code.google.com/android/reference/available-resources.html", //$NON-NLS-1$ + null, // no attributes + new ElementDescriptor[] { + string_element, + color_element, + dimen_element, + drawable_element, + style_element, + item_element, + string_array_element, + integer_array_element, + }, + true /* mandatory */); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java new file mode 100644 index 0000000..845db32 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2007 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.editors.resources.explorer; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.resources.manager.ProjectResourceItem; +import com.android.ide.eclipse.editors.resources.manager.ProjectResources; +import com.android.ide.eclipse.editors.resources.manager.ResourceFile; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener; +import com.android.ide.eclipse.editors.wizards.ResourceContentProvider; +import com.android.ide.eclipse.editors.wizards.ResourceLabelProvider; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.SWTException; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.ISelectionListener; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.part.ViewPart; + +import java.util.Iterator; + +/** + * Resource Explorer View. + *

          + * This contains a basic Tree view, and uses a TreeViewer to handle the data. + *

          + * The view listener to change in selection in the workbench, and update to show the resource + * of the project of the current selected item (either item in the package explorer, or of the + * current editor). + * + * @see ResourceContentProvider + */ +public class ResourceExplorerView extends ViewPart implements ISelectionListener, + IResourceEventListener { + + // Note: keep using the obsolete AndroidConstants.EDITORS_NAMESPACE (which used + // to be the Editors Plugin ID) to keep existing preferences functional. + private final static String PREFS_COLUMN_RES = + AndroidConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col1"; //$NON-NLS-1$ + private final static String PREFS_COLUMN_2 = + AndroidConstants.EDITORS_NAMESPACE + "ResourceExplorer.Col2"; //$NON-NLS-1$ + + private Tree mTree; + private TreeViewer mTreeViewer; + + private IProject mCurrentProject; + + public ResourceExplorerView() { + } + + @Override + public void createPartControl(Composite parent) { + mTree = new Tree(parent, SWT.SINGLE | SWT.VIRTUAL); + mTree.setLayoutData(new GridData(GridData.FILL_BOTH)); + mTree.setHeaderVisible(true); + mTree.setLinesVisible(true); + + final IPreferenceStore store = AdtPlugin.getDefault().getPreferenceStore(); + + // create 2 columns. The main one with the resources, and an "info" column. + createTreeColumn(mTree, "Resources", SWT.LEFT, + "abcdefghijklmnopqrstuvwxz", -1, PREFS_COLUMN_RES, store); //$NON-NLS-1$ + createTreeColumn(mTree, "Info", SWT.LEFT, + "0123456789", -1, PREFS_COLUMN_2, store); //$NON-NLS-1$ + + // create the jface wrapper + mTreeViewer = new TreeViewer(mTree); + + mTreeViewer.setContentProvider(new ResourceContentProvider(true /* fullLevels */)); + mTreeViewer.setLabelProvider(new ResourceLabelProvider()); + + // listen to selection change in the workbench. + IWorkbenchPage page = getSite().getPage(); + + page.addSelectionListener(this); + + // init with current selection + selectionChanged(getSite().getPart(), page.getSelection()); + + // add support for double click. + mTreeViewer.addDoubleClickListener(new IDoubleClickListener() { + public void doubleClick(DoubleClickEvent event) { + ISelection sel = event.getSelection(); + + if (sel instanceof IStructuredSelection) { + IStructuredSelection selection = (IStructuredSelection) sel; + + if (selection.size() == 1) { + Object element = selection.getFirstElement(); + + // if it's a resourceFile, we directly open it. + if (element instanceof ResourceFile) { + try { + IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), + ((ResourceFile)element).getFile().getIFile()); + } catch (PartInitException e) { + } + } else if (element instanceof ProjectResourceItem) { + // if it's a ResourceItem, we open the first file, but only if + // there's no alternate files. + ProjectResourceItem item = (ProjectResourceItem)element; + + if (item.isEditableDirectly()) { + ResourceFile[] files = item.getSourceFileArray(); + if (files[0] != null) { + try { + IDE.openEditor( + getSite().getWorkbenchWindow().getActivePage(), + files[0].getFile().getIFile()); + } catch (PartInitException e) { + } + } + } + } + } + } + } + }); + + // set up the resource manager to send us resource change notification + AdtPlugin.getDefault().getResourceMonitor().addResourceEventListener(this); + } + + @Override + public void setFocus() { + mTree.setFocus(); + } + + /** + * Processes a new selection. + */ + public void selectionChanged(IWorkbenchPart part, ISelection selection) { + // first we test if the part is an editor. + if (part instanceof IEditorPart) { + // if it is, we check if it's a file editor. + IEditorInput input = ((IEditorPart)part).getEditorInput(); + + if (input instanceof IFileEditorInput) { + // from the file editor we can get the IFile object, and from it, the IProject. + IFile file = ((IFileEditorInput)input).getFile(); + + // get the file project + IProject project = file.getProject(); + + handleProjectSelection(project); + } + } else if (selection instanceof IStructuredSelection) { + // if it's not an editor, we look for structured selection. + for (Iterator it = ((IStructuredSelection) selection).iterator(); + it.hasNext();) { + Object element = it.next(); + IProject project = null; + + // if we are in the navigator or package explorer, the selection could contain a + // IResource object. + if (element instanceof IResource) { + project = ((IResource) element).getProject(); + } else if (element instanceof IJavaElement) { + // if we are in the package explorer on a java element, we handle that too. + IJavaElement javaElement = (IJavaElement)element; + IJavaProject javaProject = javaElement.getJavaProject(); + if (javaProject != null) { + project = javaProject.getProject(); + } + } else if (element instanceof IAdaptable) { + // finally we try to get a project object from IAdaptable. + project = (IProject) ((IAdaptable) element) + .getAdapter(IProject.class); + } + + // if we found a project, handle it, and return. + if (project != null) { + if (handleProjectSelection(project)) { + return; + } + } + } + } + } + + /** + * Handles a project selection. + * @param project the new selected project + * @return true if the project could be processed. + */ + private boolean handleProjectSelection(IProject project) { + try { + // if it's an android project, then we get its resources, and feed them + // to the tree viewer. + if (project.hasNature(AndroidConstants.NATURE)) { + if (mCurrentProject != project) { + ProjectResources projRes = ResourceManager.getInstance().getProjectResources( + project); + if (projRes != null) { + mTreeViewer.setInput(projRes); + mCurrentProject = project; + return true; + } + } + } + } catch (CoreException e) { + } + + return false; + } + + /** + * Create a TreeColumn with the specified parameters. If a + * PreferenceStore object and a preference entry name String + * object are provided then the column will listen to change in its width + * and update the preference store accordingly. + * + * @param parent The Table parent object + * @param header The header string + * @param style The column style + * @param sample_text A sample text to figure out column width if preference + * value is missing + * @param fixedSize a fixed size. If != -1 the column is non resizable + * @param pref_name The preference entry name for column width + * @param prefs The preference store + */ + public void createTreeColumn(Tree parent, String header, int style, + String sample_text, int fixedSize, final String pref_name, + final IPreferenceStore prefs) { + + // create the column + TreeColumn col = new TreeColumn(parent, style); + + if (fixedSize != -1) { + col.setWidth(fixedSize); + col.setResizable(false); + } else { + // if there is no pref store or the entry is missing, we use the sample + // text and pack the column. + // Otherwise we just read the width from the prefs and apply it. + if (prefs == null || prefs.contains(pref_name) == false) { + col.setText(sample_text); + col.pack(); + + // init the prefs store with the current value + if (prefs != null) { + prefs.setValue(pref_name, col.getWidth()); + } + } else { + col.setWidth(prefs.getInt(pref_name)); + } + + // if there is a pref store and a pref entry name, then we setup a + // listener to catch column resize to put the new width value into the store. + if (prefs != null && pref_name != null) { + col.addControlListener(new ControlListener() { + public void controlMoved(ControlEvent e) { + } + + public void controlResized(ControlEvent e) { + // get the new width + int w = ((TreeColumn)e.widget).getWidth(); + + // store in pref store + prefs.setValue(pref_name, w); + } + }); + } + } + + // set the header + col.setText(header); + } + + /** + * Processes a start in a resource event change. + */ + public void resourceChangeEventStart() { + // pass + } + + /** + * Processes the end of a resource change event. + */ + public void resourceChangeEventEnd() { + try { + mTree.getDisplay().asyncExec(new Runnable() { + public void run() { + if (mTree.isDisposed() == false) { + mTreeViewer.refresh(); + } + } + }); + } catch (SWTException e) { + // display is disposed. nothing to do. + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java new file mode 100644 index 0000000..455c825 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.AndroidManifestHelper; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + * A monitor for the compiled resources. This only monitors changes in the resources of type + * {@link ResourceType#ID}. + */ +public final class CompiledResourcesMonitor implements IFileListener, IProjectListener { + + private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor(); + + /** + * Sets up the monitoring system. + * @param monitor The main Resource Monitor. + */ + public static void setupMonitor(ResourceMonitor monitor) { + monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED); + monitor.addProjectListener(sThis); + } + + /** + * private constructor to prevent construction. + */ + private CompiledResourcesMonitor() { + } + + + /* (non-Javadoc) + * Sent when a file changed : if the file is the R class, then it is parsed again to update + * the internal data. + * + * @param file The file that changed. + * @param markerDeltas The marker deltas for the file. + * @param kind The change kind. This is equivalent to + * {@link IResourceDelta#accept(IResourceDeltaVisitor)} + * + * @see IFileListener#fileChanged + */ + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + if (file.getName().equals(AndroidConstants.FN_COMPILED_RESOURCE_CLASS)) { + loadAndParseRClass(file.getProject()); + } + } + + /** + * Processes project close event. + */ + public void projectClosed(IProject project) { + // the ProjectResources object will be removed by the ResourceManager. + } + + /** + * Processes project delete event. + */ + public void projectDeleted(IProject project) { + // the ProjectResources object will be removed by the ResourceManager. + } + + /** + * Processes project open event. + */ + public void projectOpened(IProject project) { + // when the project is opened, we get an ADDED event for each file, so we don't + // need to do anything here. + } + + /** + * Processes existing project at init time. + */ + public void projectOpenedWithWorkspace(IProject project) { + try { + // check this is an android project + if (project.hasNature(AndroidConstants.NATURE)) { + loadAndParseRClass(project); + } + } catch (CoreException e) { + // pass + } + } + + private void loadAndParseRClass(IProject project) { + try { + // first check there's a ProjectResources to store the content + ProjectResources projectResources = ResourceManager.getInstance().getProjectResources( + project); + + if (projectResources != null) { + // create the classname + String className = getRClassName(project); + + // create a temporary class loader to load it. + ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */, + project); + + try { + Class clazz = loader.loadClass(className); + + if (clazz != null) { + // create the maps to store the result of the parsing + Map> resourceValueMap = + new HashMap>(); + Map genericValueToNameMap = + new HashMap(); + Map styleableValueToNameMap = + new HashMap(); + + // parse the class + if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap, + resourceValueMap)) { + // now we associate the maps to the project. + projectResources.setCompiledResources(genericValueToNameMap, + styleableValueToNameMap, resourceValueMap); + } + } + } catch (Error e) { + // Log this error with the class name we're trying to load and abort. + AdtPlugin.log(e, "loadAndParseRClass failed to find class %1$s", className); //$NON-NLS-1$ + } + } + } catch (ClassNotFoundException e) { + // pass + } + } + + /** + * Parses a R class, and fills maps. + * @param rClass the class to parse + * @param genericValueToNameMap + * @param styleableValueToNameMap + * @param resourceValueMap + * @return True if we managed to parse the R class. + */ + private boolean parseClass(Class rClass, Map genericValueToNameMap, + Map styleableValueToNameMap, Map> resourceValueMap) { + try { + for (Class inner : rClass.getDeclaredClasses()) { + String resType = inner.getSimpleName(); + + Map fullMap = new HashMap(); + resourceValueMap.put(resType, fullMap); + + for (Field f : inner.getDeclaredFields()) { + // only process static final fields. + int modifiers = f.getModifiers(); + if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { + Class type = f.getType(); + if (type.isArray() && type.getComponentType() == int.class) { + // if the object is an int[] we put it in the styleable map + styleableValueToNameMap.put(new IntArrayWrapper((int[]) f.get(null)), + f.getName()); + } else if (type == int.class) { + Integer value = (Integer) f.get(null); + genericValueToNameMap.put(value, new String[] { f.getName(), resType }); + fullMap.put(f.getName(), value); + } else { + assert false; + } + } + } + } + + return true; + } catch (IllegalArgumentException e) { + } catch (IllegalAccessException e) { + } + return false; + } + + private String getRClassName(IProject project) { + // create the classname + AndroidManifestHelper manifest = new AndroidManifestHelper(project); + String javaPackage = manifest.getPackageName(); + + return javaPackage + ".R"; //$NON-NLS-1$ + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java new file mode 100644 index 0000000..57c17fc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +/** + * Represents a resource item that can exist in multiple "alternate" versions. + */ +public class ConfigurableResourceItem extends ProjectResourceItem { + + /** + * Constructs a new Resource Item. + * @param name the name of the resource as it appears in the XML and R.java files. + */ + public ConfigurableResourceItem(String name) { + super(name); + } + + /** + * Returns if the resource item has at least one non-default configuration. + */ + public boolean hasAlternates() { + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault() == false) { + return true; + } + } + + return false; + } + + /** + * Returns whether the resource has a default version, with no qualifier. + */ + public boolean hasDefault() { + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault()) { + return true; + } + } + + // We only want to return false if there's no default and more than 0 items. + return (mFiles.size() == 0); + } + + /** + * Returns the number of alternate versions of this resource. + */ + public int getAlternateCount() { + int count = 0; + for (ResourceFile file : mFiles) { + if (file.getFolder().getConfiguration().isDefault() == false) { + count++; + } + } + + return count; + } + + /* + * (non-Javadoc) + * Returns whether the item can be edited directly (ie it does not have alternate versions). + */ + @Override + public boolean isEditableDirectly() { + return hasAlternates() == false; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java new file mode 100644 index 0000000..a9f80bd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceType; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Set; + +/** + * This class gives access to the bi directional relationship between {@link ResourceType} and + * {@link ResourceFolderType}. + */ +public final class FolderTypeRelationship { + + private final static HashMap mTypeToFolderMap = + new HashMap(); + + private final static HashMap mFolderToTypeMap = + new HashMap(); + + // generate the relationships. + static { + HashMap> typeToFolderMap = + new HashMap>(); + + HashMap> folderToTypeMap = + new HashMap>(); + + add(ResourceType.ANIM, ResourceFolderType.ANIM, typeToFolderMap, folderToTypeMap); + add(ResourceType.ARRAY, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.COLOR, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.COLOR, ResourceFolderType.COLOR, typeToFolderMap, folderToTypeMap); + add(ResourceType.DIMEN, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.DRAWABLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.DRAWABLE, ResourceFolderType.DRAWABLE, typeToFolderMap, folderToTypeMap); + add(ResourceType.ID, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.LAYOUT, ResourceFolderType.LAYOUT, typeToFolderMap, folderToTypeMap); + add(ResourceType.MENU, ResourceFolderType.MENU, typeToFolderMap, folderToTypeMap); + add(ResourceType.RAW, ResourceFolderType.RAW, typeToFolderMap, folderToTypeMap); + add(ResourceType.STRING, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.STYLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); + add(ResourceType.XML, ResourceFolderType.XML, typeToFolderMap, folderToTypeMap); + + optimize(typeToFolderMap, folderToTypeMap); + } + + /** + * Returns a list of {@link ResourceType}s that can be generated from files inside a folder + * of the specified type. + * @param folderType The folder type. + * @return an array of {@link ResourceType} + */ + public static ResourceType[] getRelatedResourceTypes(ResourceFolderType folderType) { + ResourceType[] array = mFolderToTypeMap.get(folderType); + if (array != null) { + return array; + } + return new ResourceType[0]; + } + + /** + * Returns a list of {@link ResourceFolderType} that can contain files generating resources + * of the specified type. + * @param resType the type of resource. + * @return an array of {@link ResourceFolderType} + */ + public static ResourceFolderType[] getRelatedFolders(ResourceType resType) { + ResourceFolderType[] array = mTypeToFolderMap.get(resType); + if (array != null) { + return array; + } + return new ResourceFolderType[0]; + } + + /** + * Returns true if the {@link ResourceType} and the {@link ResourceFolderType} values match. + * @param resType the resource type. + * @param folderType the folder type. + * @return true if files inside the folder of the specified {@link ResourceFolderType} + * could generate a resource of the specified {@link ResourceType} + */ + public static boolean match(ResourceType resType, ResourceFolderType folderType) { + ResourceFolderType[] array = mTypeToFolderMap.get(resType); + + if (array != null && array.length > 0) { + for (ResourceFolderType fType : array) { + if (fType == folderType) { + return true; + } + } + } + + return false; + } + + /** + * Adds a {@link ResourceType} - {@link ResourceFolderType} relationship. this indicates that + * a file in the folder can generate a resource of the specified type. + * @param type The resourceType + * @param folder The {@link ResourceFolderType} + * @param folderToTypeMap + * @param typeToFolderMap + */ + private static void add(ResourceType type, ResourceFolderType folder, + HashMap> typeToFolderMap, + HashMap> folderToTypeMap) { + // first we add the folder to the list associated with the type. + List folderList = typeToFolderMap.get(type); + if (folderList == null) { + folderList = new ArrayList(); + typeToFolderMap.put(type, folderList); + } + if (folderList.indexOf(folder) == -1) { + folderList.add(folder); + } + + // now we add the type to the list associated with the folder. + List typeList = folderToTypeMap.get(folder); + if (typeList == null) { + typeList = new ArrayList(); + folderToTypeMap.put(folder, typeList); + } + if (typeList.indexOf(type) == -1) { + typeList.add(type); + } + } + + /** + * Optimize the map to contains array instead of lists (since the api returns arrays) + * @param typeToFolderMap + * @param folderToTypeMap + */ + private static void optimize(HashMap> typeToFolderMap, + HashMap> folderToTypeMap) { + Set types = typeToFolderMap.keySet(); + for (ResourceType type : types) { + List list = typeToFolderMap.get(type); + mTypeToFolderMap.put(type, list.toArray(new ResourceFolderType[list.size()])); + } + + Set folders = folderToTypeMap.keySet(); + for (ResourceFolderType folder : folders) { + List list = folderToTypeMap.get(folder); + mFolderToTypeMap.put(folder, list.toArray(new ResourceType[list.size()])); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java new file mode 100644 index 0000000..552aec9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java @@ -0,0 +1,54 @@ +/* + * 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.IIdResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; + +/** + * Represents a resource item of type {@link ResourceType#ID} + */ +public class IdResourceItem extends ProjectResourceItem implements IIdResourceItem { + + private final boolean mIsDeclaredInline; + + /** + * Constructs a new ResourceItem. + * @param name the name of the resource as it appears in the XML and R.java files. + * @param isDeclaredInline Whether this id was declared inline. + */ + IdResourceItem(String name, boolean isDeclaredInline) { + super(name); + mIsDeclaredInline = isDeclaredInline; + } + + /* + * (non-Javadoc) + * Returns whether the ID resource has been declared inline inside another resource XML file. + */ + public boolean isDeclaredInline() { + return mIsDeclaredInline; + } + + /* (non-Javadoc) + * Returns whether the item can be edited (ie, the id was not declared inline). + */ + @Override + public boolean isEditableDirectly() { + return !mIsDeclaredInline; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java new file mode 100644 index 0000000..25eb112 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java @@ -0,0 +1,50 @@ +/* + * 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.editors.resources.manager; + +import java.util.Arrays; + + +/** + * Wrapper around a int[] to provide hashCode/equals support. + */ +public final class IntArrayWrapper { + + private int[] mData; + + public IntArrayWrapper(int[] data) { + mData = data; + } + + public void set(int[] data) { + mData = data; + } + + @Override + public int hashCode() { + return Arrays.hashCode(mData); + } + + @Override + public boolean equals(Object obj) { + if (getClass().equals(obj.getClass())) { + return Arrays.equals(mData, ((IntArrayWrapper)obj).mData); + } + + return super.equals(obj); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java new file mode 100644 index 0000000..72438a6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.utils.ResourceValue; +import com.android.layoutlib.utils.ValueResourceParser; +import com.android.layoutlib.utils.ValueResourceParser.IValueResourceRepository; + +import org.eclipse.core.runtime.CoreException; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Set; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +/** + * Represents a resource file able to declare multiple resources, which could be of + * different {@link ResourceType}. + *

          + * This is typically an XML file inside res/values. + */ +public final class MultiResourceFile extends ResourceFile implements IValueResourceRepository { + + private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); + + private final HashMap> mResourceItems = + new HashMap>(); + + public MultiResourceFile(IAbstractFile file, ResourceFolder folder) { + super(file, folder); + } + + @Override + public ResourceType[] getResourceTypes() { + update(); + + Set keys = mResourceItems.keySet(); + + return keys.toArray(new ResourceType[keys.size()]); + } + + @Override + public boolean hasResources(ResourceType type) { + update(); + + HashMap list = mResourceItems.get(type); + return (list != null && list.size() > 0); + } + + @Override + public Collection getResources(ResourceType type, + ProjectResources projectResources) { + update(); + + HashMap list = mResourceItems.get(type); + + ArrayList items = new ArrayList(); + + if (list != null) { + Collection values = list.values(); + for (ResourceValue res : values) { + ProjectResourceItem item = projectResources.findResourceItem(type, res.getName()); + + if (item == null) { + if (type == ResourceType.ID) { + item = new IdResourceItem(res.getName(), false /* isDeclaredInline */); + } else { + item = new ConfigurableResourceItem(res.getName()); + } + items.add(item); + } + + item.add(this); + } + } + + return items; + } + + /** + * Updates the Resource items if necessary. + */ + private void update() { + if (isTouched() == true) { + // reset current content. + mResourceItems.clear(); + + // need to parse the file and find the content. + parseFile(); + + resetTouch(); + } + } + + /** + * Parses the file and creates a list of {@link ResourceType}. + */ + private void parseFile() { + try { + SAXParser parser = sParserFactory.newSAXParser(); + parser.parse(getFile().getContents(), new ValueResourceParser(this, isFramework())); + } catch (ParserConfigurationException e) { + } catch (SAXException e) { + } catch (IOException e) { + } catch (CoreException e) { + } + } + + /** + * Adds a resource item to the list + * @param resType The type of the resource + * @param name The name of the resource. + */ + public void addResourceValue(String resType, ResourceValue value) { + ResourceType type = ResourceType.getEnum(resType); + if (type != null) { + HashMap list = mResourceItems.get(type); + + // if the list does not exist, create it. + if (list == null) { + list = new HashMap(); + mResourceItems.put(type, list); + } else { + // look for a possible value already existing. + ResourceValue oldValue = list.get(value.getName()); + + if (oldValue != null) { + oldValue.replaceWith(value); + return; + } + } + + // empty list or no match found? add the given resource + list.put(value.getName(), value); + } + } + + @Override + public IResourceValue getValue(ResourceType type, String name) { + update(); + + // get the list for the given type + HashMap list = mResourceItems.get(type); + + if (list != null) { + return list.get(name); + } + + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java new file mode 100644 index 0000000..183af27 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java @@ -0,0 +1,254 @@ +/* + * 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.editors.resources.manager; + +import com.android.ide.eclipse.common.AndroidConstants; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; + +/** + * ClassLoader able to load class from output of an Eclipse project. + */ +public final class ProjectClassLoader extends ClassLoader { + + private final IJavaProject mJavaProject; + private URLClassLoader mJarClassLoader; + private boolean mInsideJarClassLoader = false; + + public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) { + super(parentClassLoader); + mJavaProject = JavaCore.create(project); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + try { + // get the project output folder. + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IPath outputLocation = mJavaProject.getOutputLocation(); + IResource outRes = root.findMember(outputLocation); + if (outRes == null) { + throw new ClassNotFoundException(name); + } + + File outFolder = new File(outRes.getLocation().toOSString()); + + // get the class name segments + String[] segments = name.split("\\."); //$NON-NLS-1$ + + File classFile = getFile(outFolder, segments, 0); + if (classFile == null) { + if (mInsideJarClassLoader == false) { + // if no file matching the class name was found, look in the 3rd party jars + return loadClassFromJar(name); + } else { + throw new ClassNotFoundException(name); + } + } + + // load the content of the file and create the class. + FileInputStream fis = new FileInputStream(classFile); + byte[] data = new byte[(int)classFile.length()]; + int read = 0; + try { + read = fis.read(data); + } catch (IOException e) { + data = null; + } + fis.close(); + + if (data != null) { + Class clazz = defineClass(null, data, 0, read); + if (clazz != null) { + return clazz; + } + } + } catch (Exception e) { + throw new ClassNotFoundException(e.getMessage()); + } + + throw new ClassNotFoundException(name); + } + + /** + * Returns the File matching the a certain path from a root {@link File}. + *

          The methods checks that the file ends in .class even though the last segment + * does not. + * @param parent the root of the file. + * @param segments the segments containing the path of the file + * @param index the offset at which to start looking into segments. + * @return + * @throws FileNotFoundException + */ + private File getFile(File parent, String[] segments, int index) + throws FileNotFoundException { + // reached the end with no match? + if (index == segments.length) { + throw new FileNotFoundException(); + } + + String toMatch = segments[index]; + File[] files = parent.listFiles(); + + // we're at the last segments. we look for a matching .class + if (index == segments.length - 1) { + toMatch = toMatch + ".class"; + + if (files != null) { + for (File file : files) { + if (file.isFile() && file.getName().equals(toMatch)) { + return file; + } + } + } + + // no match? abort. + throw new FileNotFoundException(); + } + + String innerClassName = null; + + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + if (toMatch.equals(file.getName())) { + return getFile(file, segments, index+1); + } + } else if (file.getName().startsWith(toMatch)) { + if (innerClassName == null) { + StringBuilder sb = new StringBuilder(segments[index]); + for (int i = index + 1 ; i < segments.length ; i++) { + sb.append('$'); + sb.append(segments[i]); + } + sb.append(".class"); + + innerClassName = sb.toString(); + } + + if (file.getName().equals(innerClassName)) { + return file; + } + } + } + } + + return null; + } + + /** + * Loads a class from the 3rd party jar present in the project + * @param name + * @return + * @throws ClassNotFoundException + */ + private Class loadClassFromJar(String name) throws ClassNotFoundException { + if (mJarClassLoader == null) { + // get the OS path to all the external jars + URL[] jars = getExternalJars(); + + mJarClassLoader = new URLClassLoader(jars, this /* parent */); + } + + try { + // because a class loader always look in its parent loader first, we need to know + // that we are querying the jar classloader. This will let us know to not query + // it again for classes we don't find, or this would create an infinite loop. + mInsideJarClassLoader = true; + return mJarClassLoader.loadClass(name); + } finally { + mInsideJarClassLoader = false; + } + } + + /** + * Returns an array of external jar files used by the project. + * @return an array of OS-specific absolute file paths + */ + private final URL[] getExternalJars() { + // get a java project from it + IJavaProject javaProject = JavaCore.create(mJavaProject.getProject()); + + IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); + + ArrayList oslibraryList = new ArrayList(); + IClasspathEntry[] classpaths = javaProject.readRawClasspath(); + if (classpaths != null) { + for (IClasspathEntry e : classpaths) { + if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY || + e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + // if this is a classpath variable reference, we resolve it. + if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { + e = JavaCore.getResolvedClasspathEntry(e); + } + + // get the IPath + IPath path = e.getPath(); + + // check the name ends with .jar + if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { + boolean local = false; + IResource resource = wsRoot.findMember(path); + if (resource != null && resource.exists() && + resource.getType() == IResource.FILE) { + local = true; + try { + oslibraryList.add( + new File(resource.getLocation().toOSString()).toURL()); + } catch (MalformedURLException mue) { + // pass + } + } + + if (local == false) { + // if the jar path doesn't match a workspace resource, + // then we get an OSString and check if this links to a valid file. + String osFullPath = path.toOSString(); + + File f = new File(osFullPath); + if (f.exists()) { + try { + oslibraryList.add(f.toURL()); + } catch (MalformedURLException mue) { + // pass + } + } + } + } + } + } + } + + return oslibraryList.toArray(new URL[oslibraryList.size()]); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java new file mode 100644 index 0000000..ba770b2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java @@ -0,0 +1,91 @@ +package com.android.ide.eclipse.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Base class for Resource Item coming from an Android Project. + */ +public abstract class ProjectResourceItem extends ResourceItem { + + private final static Comparator sComparator = new Comparator() { + public int compare(ResourceFile file1, ResourceFile file2) { + // get both FolderConfiguration and compare them + FolderConfiguration fc1 = file1.getFolder().getConfiguration(); + FolderConfiguration fc2 = file2.getFolder().getConfiguration(); + + return fc1.compareTo(fc2); + } + }; + + /** + * List of files generating this ResourceItem. + */ + protected final ArrayList mFiles = new ArrayList(); + + /** + * Constructs a new ResourceItem. + * @param name the name of the resource as it appears in the XML and R.java files. + */ + public ProjectResourceItem(String name) { + super(name); + } + + /** + * Returns whether the resource item is editable directly. + *

          + * This is typically the case for resources that don't have alternate versions, or resources + * of type {@link ResourceType#ID} that aren't declared inline. + */ + public abstract boolean isEditableDirectly(); + + /** + * Adds a new version of this resource item, by adding its {@link ResourceFile}. + * @param file the {@link ResourceFile} object. + */ + protected void add(ResourceFile file) { + mFiles.add(file); + } + + /** + * Reset the item by emptying its version list. + */ + protected void reset() { + mFiles.clear(); + } + + /** + * Returns the sorted list of {@link ResourceItem} objects for this resource item. + */ + public ResourceFile[] getSourceFileArray() { + ArrayList list = new ArrayList(); + list.addAll(mFiles); + + Collections.sort(list, sComparator); + + return list.toArray(new ResourceFile[list.size()]); + } + + /** + * Returns the list of {@link ResourceItem} objects for this resource item. + */ + public List getSourceFileList() { + return Collections.unmodifiableList(mFiles); + } + + + /** + * Replaces the content of the receiver with the ResourceItem received as parameter. + * @param item + */ + protected void replaceWith(ProjectResourceItem item) { + mFiles.clear(); + mFiles.addAll(item.mFiles); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java new file mode 100644 index 0000000..b0881fa --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java @@ -0,0 +1,810 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; +import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.utils.ResourceValue; + +import org.eclipse.core.resources.IFolder; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Represents the resources of a project. This is a file view of the resources, with handling + * for the alternate resource types. For a compiled view use CompiledResources. + */ +public class ProjectResources implements IResourceRepository { + private final HashMap> mFolderMap = + new HashMap>(); + + private final HashMap> mResourceMap = + new HashMap>(); + + /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */ + private Map> mResourceValueMap; + /** Map of (id, [name, resType]) for all resources coming from R.java */ + private Map mResIdValueToNameMap; + /** Map of (int[], name) for styleable resources coming from R.java */ + private Map mStyleableValueToNameMap; + + /** Cached list of {@link IdResourceItem}. This is mix of IdResourceItem created by + * {@link MultiResourceFile} for ids coming from XML files under res/values and + * {@link IdResourceItem} created manually, from the list coming from R.java */ + private final ArrayList mIdResourceList = new ArrayList(); + + private final boolean mIsFrameworkRepository; + + private final IntArrayWrapper mWrapper = new IntArrayWrapper(null); + + public ProjectResources(boolean isFrameworkRepository) { + mIsFrameworkRepository = isFrameworkRepository; + } + + public boolean isSystemRepository() { + return mIsFrameworkRepository; + } + + /** + * Adds a Folder Configuration to the project. + * @param type The resource type. + * @param config The resource configuration. + * @param folder The workspace folder object. + * @return the {@link ResourceFolder} object associated to this folder. + */ + protected ResourceFolder add(ResourceFolderType type, FolderConfiguration config, + IAbstractFolder folder) { + // get the list for the resource type + List list = mFolderMap.get(type); + + if (list == null) { + list = new ArrayList(); + + ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); + list.add(cf); + + mFolderMap.put(type, list); + + return cf; + } + + // look for an already existing folder configuration. + for (ResourceFolder cFolder : list) { + if (cFolder.mConfiguration.equals(config)) { + // config already exist. Nothing to be done really, besides making sure + // the IFolder object is up to date. + cFolder.mFolder = folder; + return cFolder; + } + } + + // If we arrive here, this means we didn't find a matching configuration. + // So we add one. + ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); + list.add(cf); + + return cf; + } + + /** + * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}. + * @param type The type of the folder + * @param folder the IFolder object. + */ + protected void removeFolder(ResourceFolderType type, IFolder folder) { + // get the list of folders for the resource type. + List list = mFolderMap.get(type); + + if (list != null) { + int count = list.size(); + for (int i = 0 ; i < count ; i++) { + ResourceFolder resFolder = list.get(i); + if (resFolder.getFolder().getIFolder().equals(folder)) { + // we found the matching ResourceFolder. we need to remove it. + list.remove(i); + + // we now need to invalidate this resource type. + // The easiest way is to touch one of the other folders of the same type. + if (list.size() > 0) { + list.get(0).touch(); + } else { + // if the list is now empty, and we have a single ResouceType out of this + // ResourceFolderType, then we are done. + // However, if another ResourceFolderType can generate similar ResourceType + // than this, we need to update those ResourceTypes as well. + // For instance, if the last "drawable-*" folder is deleted, we need to + // refresh the ResourceItem associated with ResourceType.DRAWABLE. + // Those can be found in ResourceFolderType.DRAWABLE but also in + // ResourceFolderType.VALUES. + // If we don't find a single folder to touch, then it's fine, as the top + // level items (the list of generated resource types) is not cached + // (for now) + + // get the lists of ResourceTypes generated by this ResourceFolderType + ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( + type); + + // for each of those, make sure to find one folder to touch so that the + // list of ResourceItem associated with the type is rebuilt. + for (ResourceType resType : resTypes) { + // get the list of folder that can generate this type + ResourceFolderType[] folderTypes = + FolderTypeRelationship.getRelatedFolders(resType); + + // we only need to touch one folder in any of those (since it's one + // folder per type, not per folder type). + for (ResourceFolderType folderType : folderTypes) { + List resFolders = mFolderMap.get(folderType); + + if (resFolders != null && resFolders.size() > 0) { + resFolders.get(0).touch(); + break; + } + } + } + } + + // we're done updating/touching, we can stop + break; + } + } + } + } + + + /** + * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}. + * @param type The {@link ResourceFolderType} + */ + public List getFolders(ResourceFolderType type) { + return mFolderMap.get(type); + } + + /* (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getAvailableResourceTypes() + */ + public ResourceType[] getAvailableResourceTypes() { + ArrayList list = new ArrayList(); + + // For each key, we check if there's a single ResourceType match. + // If not, we look for the actual content to give us the resource type. + + for (ResourceFolderType folderType : mFolderMap.keySet()) { + ResourceType types[] = FolderTypeRelationship.getRelatedResourceTypes(folderType); + if (types.length == 1) { + // before we add it we check if it's not already present, since a ResourceType + // could be created from multiple folders, even for the folders that only create + // one type of resource (drawable for instance, can be created from drawable/ and + // values/) + if (list.indexOf(types[0]) == -1) { + list.add(types[0]); + } + } else { + // there isn't a single resource type out of this folder, so we look for all + // content. + List folders = mFolderMap.get(folderType); + if (folders != null) { + for (ResourceFolder folder : folders) { + Collection folderContent = folder.getResourceTypes(); + + // then we add them, but only if they aren't already in the list. + for (ResourceType folderResType : folderContent) { + if (list.indexOf(folderResType) == -1) { + list.add(folderResType); + } + } + } + } + } + } + + // in case ResourceType.ID haven't been added yet because there's no id defined + // in XML, we check on the list of compiled id resources. + if (list.indexOf(ResourceType.ID) == -1 && mResourceValueMap != null) { + Map map = mResourceValueMap.get(ResourceType.ID.getName()); + if (map != null && map.size() > 0) { + list.add(ResourceType.ID); + } + } + + // at this point the list is full of ResourceType defined in the files. + // We need to sort it. + Collections.sort(list); + + return list.toArray(new ResourceType[list.size()]); + } + + /* (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getResources(com.android.ide.eclipse.common.resources.ResourceType) + */ + public ProjectResourceItem[] getResources(ResourceType type) { + checkAndUpdate(type); + + if (type == ResourceType.ID) { + synchronized (mIdResourceList) { + return mIdResourceList.toArray(new ProjectResourceItem[mIdResourceList.size()]); + } + } + + List items = mResourceMap.get(type); + + return items.toArray(new ProjectResourceItem[items.size()]); + } + + /* (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.IResourceRepository#hasResources(com.android.ide.eclipse.common.resources.ResourceType) + */ + public boolean hasResources(ResourceType type) { + checkAndUpdate(type); + + if (type == ResourceType.ID) { + synchronized (mIdResourceList) { + return mIdResourceList.size() > 0; + } + } + + List items = mResourceMap.get(type); + return (items != null && items.size() > 0); + } + + /** + * Returns the {@link ResourceFolder} associated with a {@link IFolder}. + * @param folder The {@link IFolder} object. + * @return the {@link ResourceFolder} or null if it was not found. + */ + public ResourceFolder getResourceFolder(IFolder folder) { + for (List list : mFolderMap.values()) { + for (ResourceFolder resFolder : list) { + if (resFolder.getFolder().getIFolder().equals(folder)) { + return resFolder; + } + } + } + + return null; + } + + /** + * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and + * configuration. + *

          This only works with files generating one resource named after the file (for instance, + * layouts, bitmap based drawable, xml, anims). + * @return the matching file or null if no match was found. + */ + public ResourceFile getMatchingFile(String name, ResourceFolderType type, + FolderConfiguration config) { + // get the folders for the given type + List folders = mFolderMap.get(type); + + // look for folders containing a file with the given name. + ArrayList matchingFolders = new ArrayList(); + + // remove the folders that do not have a file with the given name, or if their config + // is incompatible. + for (int i = 0 ; i < folders.size(); i++) { + ResourceFolder folder = folders.get(i); + + if (folder.hasFile(name) == true) { + matchingFolders.add(folder); + } + } + + // from those, get the folder with a config matching the given reference configuration. + Resource match = findMatchingConfiguredResource(matchingFolders, config); + + // do we have a matching folder? + if (match instanceof ResourceFolder) { + // get the ResourceFile from the filename + return ((ResourceFolder)match).getFile(name); + } + + return null; + } + + /** + * Returns the resources values matching a given {@link FolderConfiguration}. + * @param referenceConfig the configuration that each value must match. + */ + public Map> getConfiguredResources( + FolderConfiguration referenceConfig) { + + Map> map = + new HashMap>(); + + // special case for Id since there's a mix of compiled id (declared inline) and id declared + // in the XML files. + if (mIdResourceList.size() > 0) { + Map idMap = new HashMap(); + String idType = ResourceType.ID.getName(); + for (IdResourceItem id : mIdResourceList) { + // FIXME: cache the ResourceValue! + idMap.put(id.getName(), new ResourceValue(idType, id.getName(), + mIsFrameworkRepository)); + } + + map.put(ResourceType.ID.getName(), idMap); + } + + Set keys = mResourceMap.keySet(); + for (ResourceType key : keys) { + // we don't process ID resources since we already did it above. + if (key != ResourceType.ID) { + map.put(key.getName(), getConfiguredResource(key, referenceConfig)); + } + } + + return map; + } + + /** + * Loads all the resources. Essentially this forces to load the values from the + * {@link ResourceFile} objects to make sure they are up to date and loaded + * in {@link #mResourceMap}. + */ + public void loadAll() { + // gets all the resource types available. + ResourceType[] types = getAvailableResourceTypes(); + + // loop on them and load them + for (ResourceType type: types) { + checkAndUpdate(type); + } + } + + /** + * Resolves a compiled resource id into the resource name and type + * @param id + * @return an array of 2 strings { name, type } or null if the id could not be resolved + */ + public String[] resolveResourceValue(int id) { + if (mResIdValueToNameMap != null) { + return mResIdValueToNameMap.get(id); + } + + return null; + } + + /** + * Resolves a compiled resource id of type int[] into the resource name. + * @param id + * @return + */ + public String resolveResourceValue(int[] id) { + if (mStyleableValueToNameMap != null) { + mWrapper.set(id); + return mStyleableValueToNameMap.get(mWrapper); + } + + return null; + } + + /** + * Returns the value of a resource by its type and name. + * @param type + * @param name + * @return + */ + public Integer getResourceValue(String type, String name) { + if (mResourceValueMap != null) { + Map map = mResourceValueMap.get(type); + if (map != null) { + return map.get(name); + } + } + + return null; + } + + /** + * Returns the list of languages used in the resources. + */ + public Set getLanguages() { + Set set = new HashSet(); + + Collection> folderList = mFolderMap.values(); + for (List folderSubList : folderList) { + for (ResourceFolder folder : folderSubList) { + FolderConfiguration config = folder.getConfiguration(); + LanguageQualifier lang = config.getLanguageQualifier(); + if (lang != null) { + set.add(lang.getStringValue()); + } + } + } + + return set; + } + + /** + * Returns the list of regions used in the resources with the given language. + * @param currentLanguage the current language the region must be associated with + * @return + */ + public Set getRegions(String currentLanguage) { + Set set = new HashSet(); + + Collection> folderList = mFolderMap.values(); + for (List folderSubList : folderList) { + for (ResourceFolder folder : folderSubList) { + FolderConfiguration config = folder.getConfiguration(); + + // get the language + LanguageQualifier lang = config.getLanguageQualifier(); + if (lang != null && lang.getStringValue().equals(currentLanguage)) { + RegionQualifier region = config.getRegionQualifier(); + if (region != null) { + set.add(region.getStringValue()); + } + } + } + } + + return set; + } + + /** + * Returns a map of (resource name, resource value) for the given {@link ResourceType}. + *

          The values returned are taken from the resource files best matching a given + * {@link FolderConfiguration}. + * @param type the type of the resources. + * @param referenceConfig the configuration to best match. + */ + private Map getConfiguredResource(ResourceType type, + FolderConfiguration referenceConfig) { + // get the resource item for the given type + List items = mResourceMap.get(type); + + // create the map + HashMap map = new HashMap(); + + for (ProjectResourceItem item : items) { + // get the source files generating this resource + List list = item.getSourceFileList(); + + // look for the best match for the given configuration + Resource match = findMatchingConfiguredResource(list, referenceConfig); + + if (match instanceof ResourceFile) { + ResourceFile matchResFile = (ResourceFile)match; + + // get the value of this configured resource. + IResourceValue value = matchResFile.getValue(type, item.getName()); + + if (value != null) { + map.put(item.getName(), value); + } + } + } + + return map; + } + + /** + * Returns the best matching {@link Resource}. + * @param resources the list of {@link Resource} to choose from. + * @param referenceConfig the {@link FolderConfiguration} to match. + */ + private Resource findMatchingConfiguredResource(List resources, + FolderConfiguration referenceConfig) { + // look for resources with the maximum number of qualifier match. + int currentMax = -1; + ArrayList matchingResources = new ArrayList(); + for (int i = 0 ; i < resources.size(); i++) { + Resource res = resources.get(i); + + int count = res.getConfiguration().match(referenceConfig); + if (count > currentMax) { + matchingResources.clear(); + matchingResources.add(res); + currentMax = count; + } else if (count != -1 && count == currentMax) { + matchingResources.add(res); + } + } + + // if we have more than one match, we look for the match with the qualifiers with the + // highest priority. + Resource resMatch = null; + if (matchingResources.size() == 1) { + resMatch = matchingResources.get(0); + } else if (matchingResources.size() > 1) { + // More than one resource with the same number of qualifier match. + // We loop, looking for the resource with the highest priority qualifiers. + ArrayList tmpResources = new ArrayList(); + int startIndex = 0; + while (matchingResources.size() > 1) { + int highest = -1; + for (int i = 0 ; i < matchingResources.size() ; i++) { + Resource folder = matchingResources.get(i); + + // get highest priority qualifiers. + int m = folder.getConfiguration().getHighestPriorityQualifier(startIndex); + + // add to the list if highest. + if (m != -1) { + if (highest == -1 || m == highest) { + tmpResources.add(folder); + highest = m; + } else if (m < highest) { // highest priority == lowest index. + tmpResources.clear(); + tmpResources.add(folder); + } + } + } + + // at this point, we have a list with 1+ resources that all have the same highest + // priority qualifiers. Go through the list again looking for the next highest + // priority qualifier. + startIndex = highest + 1; + + // this should not happen, but it's better to check. + if (matchingResources.size() == tmpResources.size() && highest == -1) { + // this means all the resources match with the same qualifiers + // (highest == -1 means we reached the end of the qualifier list) + // In this case, we arbitrarily take the first resource. + matchingResources.clear(); + matchingResources.add(tmpResources.get(0)); + } else { + matchingResources.clear(); + matchingResources.addAll(tmpResources); + } + tmpResources.clear(); + } + + // we should have only one match here. + resMatch = matchingResources.get(0); + } + + return resMatch; + } + + /** + * Checks if the list of {@link ResourceItem}s for the specified {@link ResourceType} needs + * to be updated. + * @param type the Resource Type. + */ + private void checkAndUpdate(ResourceType type) { + // get the list of folder that can output this type + ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); + + for (ResourceFolderType folderType : folderTypes) { + List folders = mFolderMap.get(folderType); + + if (folders != null) { + for (ResourceFolder folder : folders) { + if (folder.isTouched()) { + // if this folder is touched we need to update all the types that can + // be generated from a file in this folder. + // This will include 'type' obviously. + ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( + folderType); + for (ResourceType resType : resTypes) { + update(resType); + } + return; + } + } + } + } + } + + /** + * Updates the list of {@link ResourceItem} objects associated with a {@link ResourceType}. + * This will reset the touch status of all the folders that can generate this resource type. + * @param type the Resource Type. + */ + private void update(ResourceType type) { + // get the cache list, and lets make a backup + List items = mResourceMap.get(type); + List backup = new ArrayList(); + + if (items == null) { + items = new ArrayList(); + mResourceMap.put(type, items); + } else { + // backup the list + backup.addAll(items); + + // we reset the list itself. + items.clear(); + } + + // get the list of folder that can output this type + ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); + + for (ResourceFolderType folderType : folderTypes) { + List folders = mFolderMap.get(folderType); + + if (folders != null) { + for (ResourceFolder folder : folders) { + items.addAll(folder.getResources(type, this)); + folder.resetTouch(); + } + } + } + + // now items contains the new list. We "merge" it with the backup list. + // Basically, we need to keep the old instances of ResourceItem (where applicable), + // but replace them by the content of the new items. + // This will let the resource explorer keep the expanded state of the nodes whose data + // is a ResourceItem object. + if (backup.size() > 0) { + // this is not going to change as we're only replacing instances. + int count = items.size(); + + for (int i = 0 ; i < count;) { + // get the "new" item + ProjectResourceItem item = items.get(i); + + // look for a similar item in the old list. + ProjectResourceItem foundOldItem = null; + for (ProjectResourceItem oldItem : backup) { + if (oldItem.getName().equals(item.getName())) { + foundOldItem = oldItem; + break; + } + } + + if (foundOldItem != null) { + // erase the data of the old item with the data from the new one. + foundOldItem.replaceWith(item); + + // remove the old and new item from their respective lists + items.remove(i); + backup.remove(foundOldItem); + + // add the old item to the new list + items.add(foundOldItem); + } else { + // this is a new item, we skip to the next object + i++; + } + } + } + + // if this is the ResourceType.ID, we create the actual list, from this list and + // the compiled resource list. + if (type == ResourceType.ID) { + mergeIdResources(); + } else { + // else this is the list that will actually be displayed, so we sort it. + Collections.sort(items); + } + } + + /** + * Looks up an existing {@link ProjectResourceItem} by {@link ResourceType} and name. + * @param type the Resource Type. + * @param name the Resource name. + * @return the existing ResourceItem or null if no match was found. + */ + protected ProjectResourceItem findResourceItem(ResourceType type, String name) { + List list = mResourceMap.get(type); + + for (ProjectResourceItem item : list) { + if (name.equals(item.getName())) { + return item; + } + } + + return null; + } + + /** + * Sets compiled resource information. + * @param resIdValueToNameMap a map of compiled resource id to resource name. + * The map is acquired by the {@link ProjectResources} object. + * @param styleableValueMap + * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}. + * The list is acquired by the {@link ProjectResources} object. + */ + void setCompiledResources(Map resIdValueToNameMap, + Map styleableValueMap, + Map> resourceValueMap) { + mResourceValueMap = resourceValueMap; + mResIdValueToNameMap = resIdValueToNameMap; + mStyleableValueToNameMap = styleableValueMap; + mergeIdResources(); + } + + /** + * Merges the list of ID resource coming from R.java and the list of ID resources + * coming from XML declaration into the cached list {@link #mIdResourceList}. + */ + void mergeIdResources() { + // get the list of IDs coming from XML declaration. Those ids are present in + // mCompiledIdResources already, so we'll need to use those instead of creating + // new IdResourceItem + List xmlIdResources = mResourceMap.get(ResourceType.ID); + + synchronized (mIdResourceList) { + // copy the currently cached items. + ArrayList oldItems = new ArrayList(); + oldItems.addAll(mIdResourceList); + + // empty the current list + mIdResourceList.clear(); + + // get the list of compile id resources. + Map idMap = null; + if (mResourceValueMap != null) { + idMap = mResourceValueMap.get(ResourceType.ID.getName()); + } + + if (idMap == null) { + if (xmlIdResources != null) { + for (ProjectResourceItem resourceItem : xmlIdResources) { + // check the actual class just for safety. + if (resourceItem instanceof IdResourceItem) { + mIdResourceList.add((IdResourceItem)resourceItem); + } + } + } + } else { + // loop on the full list of id, and look for a match in the old list, + // in the list coming from XML (in case a new XML item was created.) + + Set idSet = idMap.keySet(); + + idLoop: for (String idResource : idSet) { + // first look in the XML list in case an id went from inline to XML declared. + if (xmlIdResources != null) { + for (ProjectResourceItem resourceItem : xmlIdResources) { + if (resourceItem instanceof IdResourceItem && + resourceItem.getName().equals(idResource)) { + mIdResourceList.add((IdResourceItem)resourceItem); + continue idLoop; + } + } + } + + // if we haven't found it, look in the old items. + int count = oldItems.size(); + for (int i = 0 ; i < count ; i++) { + IdResourceItem resourceItem = oldItems.get(i); + if (resourceItem.getName().equals(idResource)) { + oldItems.remove(i); + mIdResourceList.add(resourceItem); + continue idLoop; + } + } + + // if we haven't found it, it looks like it's a new id that was + // declared inline. + mIdResourceList.add(new IdResourceItem(idResource, + true /* isDeclaredInline */)); + } + } + + // now we sort the list + Collections.sort(mIdResourceList); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/Resource.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/Resource.java new file mode 100644 index 0000000..dd8d080 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/Resource.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; + +/** + * Base class for file system resource items (Folders, Files). + */ +public abstract class Resource { + private boolean mTouched = true; + + /** + * Returns the {@link FolderConfiguration} for this object. + */ + public abstract FolderConfiguration getConfiguration(); + + /** + * Indicates that the underlying file was changed. + */ + public final void touch() { + mTouched = true; + } + + public final boolean isTouched() { + return mTouched; + } + + public final void resetTouch() { + mTouched = false; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java new file mode 100644 index 0000000..f927a9a --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; +import com.android.layoutlib.api.IResourceValue; + +import java.util.Collection; + +/** + * Represents a Resource file (a file under $Project/res/) + */ +public abstract class ResourceFile extends Resource { + + private final IAbstractFile mFile; + private final ResourceFolder mFolder; + + protected ResourceFile(IAbstractFile file, ResourceFolder folder) { + mFile = file; + mFolder = folder; + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() + */ + @Override + public FolderConfiguration getConfiguration() { + return mFolder.getConfiguration(); + } + + /** + * Returns the IFile associated with the ResourceFile. + */ + public final IAbstractFile getFile() { + return mFile; + } + + /** + * Returns the parent folder as a {@link ResourceFolder}. + */ + public final ResourceFolder getFolder() { + return mFolder; + } + + /** + * Returns whether the resource is a framework resource. + */ + public final boolean isFramework() { + return mFolder.isFramework(); + } + + /** + * Returns the list of {@link ResourceType} generated by the file. + */ + public abstract ResourceType[] getResourceTypes(); + + /** + * Returns whether the file generated a resource of a specific type. + * @param type The {@link ResourceType} + */ + public abstract boolean hasResources(ResourceType type); + + /** + * Get the list of {@link ProjectResourceItem} of a specific type generated by the file. + * This method must make sure not to create duplicate. + * @param type The type of {@link ProjectResourceItem} to return. + * @param projectResources The global Project Resource object, allowing the implementation to + * query for already existing {@link ProjectResourceItem} + * @return The list of new {@link ProjectResourceItem} + * @see ProjectResources#findResourceItem(ResourceType, String) + */ + public abstract Collection getResources(ResourceType type, + ProjectResources projectResources); + + /** + * Returns the value of a resource generated by this file by {@link ResourceType} and name. + *

          If no resource match, null is returned. + * @param type the type of the resource. + * @param name the name of the resource. + */ + public abstract IResourceValue getValue(ResourceType type, String name); +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java new file mode 100644 index 0000000..6db0d94 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Resource Folder class. Contains list of {@link ResourceFile}s, + * the {@link FolderConfiguration}, and a link to the workspace {@link IFolder} object. + */ +public final class ResourceFolder extends Resource { + ResourceFolderType mType; + FolderConfiguration mConfiguration; + IAbstractFolder mFolder; + ArrayList mFiles = null; + private final boolean mIsFramework; + + /** + * Creates a new {@link ResourceFolder} + * @param type The type of the folder + * @param config The configuration of the folder + * @param folder The associated {@link IAbstractFolder} object. + * @param isFrameworkRepository + */ + public ResourceFolder(ResourceFolderType type, FolderConfiguration config, + IAbstractFolder folder, boolean isFrameworkRepository) { + mType = type; + mConfiguration = config; + mFolder = folder; + mIsFramework = isFrameworkRepository; + } + + /** + * Adds a {@link ResourceFile} to the folder. + * @param file The {@link ResourceFile}. + */ + public void addFile(ResourceFile file) { + if (mFiles == null) { + mFiles = new ArrayList(); + } + + mFiles.add(file); + } + + /** + * Attempts to remove the {@link ResourceFile} associated with a specified {@link IFile}. + * @param file the IFile object. + */ + public void removeFile(IFile file) { + if (mFiles != null) { + int count = mFiles.size(); + for (int i = 0 ; i < count ; i++) { + ResourceFile resFile = mFiles.get(i); + if (resFile != null) { + IFile iFile = resFile.getFile().getIFile(); + if (iFile != null && iFile.equals(file)) { + mFiles.remove(i); + touch(); + } + } + } + } + } + + /** + * Returns the {@link IFolder} associated with this object. + */ + public IAbstractFolder getFolder() { + return mFolder; + } + + /** + * Returns the {@link ResourceFolderType} of this object. + */ + public ResourceFolderType getType() { + return mType; + } + + /** + * Returns whether the folder is a framework resource folder. + */ + public boolean isFramework() { + return mIsFramework; + } + + /** + * Returns the list of {@link ResourceType}s generated by the files inside this folder. + */ + public Collection getResourceTypes() { + ArrayList list = new ArrayList(); + + if (mFiles != null) { + for (ResourceFile file : mFiles) { + ResourceType[] types = file.getResourceTypes(); + + // loop through those and add them to the main list, + // if they are not already present + if (types != null) { + for (ResourceType resType : types) { + if (list.indexOf(resType) == -1) { + list.add(resType); + } + } + } + } + } + + return list; + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() + */ + @Override + public FolderConfiguration getConfiguration() { + return mConfiguration; + } + + /** + * Returns whether the folder contains a file with the given name. + * @param name the name of the file. + */ + public boolean hasFile(String name) { + return mFolder.hasFile(name); + } + + /** + * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object. + * @param file The {@link IFile} object. + * @return the {@link ResourceFile} or null if no match was found. + */ + public ResourceFile getFile(IAbstractFile file) { + if (mFiles != null) { + for (ResourceFile f : mFiles) { + if (f.getFile().equals(file)) { + return f; + } + } + } + return null; + } + + /** + * Returns the {@link ResourceFile} matching a {@link IFile} object. + * @param file The {@link IFile} object. + * @return the {@link ResourceFile} or null if no match was found. + */ + public ResourceFile getFile(IFile file) { + if (mFiles != null) { + for (ResourceFile f : mFiles) { + IFile iFile = f.getFile().getIFile(); + if (iFile != null && iFile.equals(file)) { + return f; + } + } + } + return null; + } + + + /** + * Returns the {@link ResourceFile} matching a given name. + * @param file The name of the file to return. + * @return the {@link ResourceFile} or null if no match was found. + */ + public ResourceFile getFile(String filename) { + if (mFiles != null) { + for (ResourceFile f : mFiles) { + if (f.getFile().getName().equals(filename)) { + return f; + } + } + } + return null; + } + + /** + * Returns whether a file in the folder is generating a resource of a specified type. + * @param type The {@link ResourceType} being looked up. + */ + public boolean hasResources(ResourceType type) { + // Check if the folder type is able to generate resource of the type that was asked. + // this is a first check to avoid going through the files. + ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); + + boolean valid = false; + for (ResourceFolderType rft : folderTypes) { + if (rft == mType) { + valid = true; + break; + } + } + + if (valid) { + if (mFiles != null) { + for (ResourceFile f : mFiles) { + if (f.hasResources(type)) { + return true; + } + } + } + } + return false; + } + + /** + * Get the list of {@link ResourceItem} of a specific type generated by all the files + * in the folder. + * This method must make sure not to create duplicates. + * @param type The type of {@link ResourceItem} to return. + * @param projectResources The global Project Resource object, allowing the implementation to + * query for already existing {@link ResourceItem} + * @return The list of new {@link ResourceItem} + * @see ProjectResources#findResourceItem(ResourceType, String) + */ + public Collection getResources(ResourceType type, + ProjectResources projectResources) { + Collection list = new ArrayList(); + if (mFiles != null) { + for (ResourceFile f : mFiles) { + list.addAll(f.getResources(type, projectResources)); + } + } + return list; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java new file mode 100644 index 0000000..bd93301 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; + +/** + * Enum representing a type of resource folder. + */ +public enum ResourceFolderType { + ANIM(AndroidConstants.FD_ANIM), + COLOR(AndroidConstants.FD_COLOR), + DRAWABLE(AndroidConstants.FD_DRAWABLE), + LAYOUT(AndroidConstants.FD_LAYOUT), + MENU(AndroidConstants.FD_MENU), + RAW(AndroidConstants.FD_RAW), + VALUES(AndroidConstants.FD_VALUES), + XML(AndroidConstants.FD_XML); + + private final String mName; + + ResourceFolderType(String name) { + mName = name; + } + + public String getName() { + return mName; + } + + /** + * Returns the enum by name. + * @param name The enum string value. + * @return the enum or null if not found. + */ + public static ResourceFolderType getTypeByName(String name) { + for (ResourceFolderType rType : values()) { + if (rType.mName.equals(name)) { + return rType; + } + } + return null; + } + + /** + * Returns the {@link ResourceFolderType} from the folder name + * @param folderName The name of the folder. This must be a valid folder name in the format + * resType[-resqualifiers[-resqualifiers[...]] + * @return the ResourceFolderType representing the type of the folder, or + * null if no matching type was found. + */ + public static ResourceFolderType getFolderType(String folderName) { + // split the name of the folder in segments. + String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); + + // get the enum for the resource type. + return getTypeByName(folderSegments[0]); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java new file mode 100644 index 0000000..9c5f0fc --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java @@ -0,0 +1,493 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFolderListener; +import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; +import com.android.ide.eclipse.editors.resources.manager.files.FileWrapper; +import com.android.ide.eclipse.editors.resources.manager.files.FolderWrapper; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; +import com.android.ide.eclipse.editors.resources.manager.files.IFileWrapper; +import com.android.ide.eclipse.editors.resources.manager.files.IFolderWrapper; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +public final class ResourceManager implements IProjectListener, IFolderListener, IFileListener { + + private final static ResourceManager sThis = new ResourceManager(); + + /** List of the qualifier object helping for the parsing of folder names */ + private final ResourceQualifier[] mQualifiers; + + /** + * Map associating project resource with project objects. + */ + private final HashMap mMap = + new HashMap(); + + /** + * Sets up the resource manager with the global resource monitor. + * @param monitor The global resource monitor + */ + public static void setup(ResourceMonitor monitor) { + monitor.addProjectListener(sThis); + int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED; + monitor.addFolderListener(sThis, mask); + monitor.addFileListener(sThis, mask); + + CompiledResourcesMonitor.setupMonitor(monitor); + } + + /** + * Returns the singleton instance. + */ + public static ResourceManager getInstance() { + return sThis; + } + + /** + * Returns the resources of a project. + * @param project The project + * @return a ProjectResources object or null if none was found. + */ + public ProjectResources getProjectResources(IProject project) { + return mMap.get(project); + } + + /** + * Processes folder event. + */ + public void folderChanged(IFolder folder, int kind) { + ProjectResources resources; + + final IProject project = folder.getProject(); + + try { + if (project.hasNature(AndroidConstants.NATURE) == false) { + return; + } + } catch (CoreException e) { + // can't get the project nature? return! + return; + } + + switch (kind) { + case IResourceDelta.ADDED: + // checks if the folder is under res. + IPath path = folder.getFullPath(); + + // the path will be project/res/ + if (path.segmentCount() == 3) { + if (isInResFolder(path)) { + // get the project and its resource object. + resources = mMap.get(project); + + // if it doesn't exist, we create it. + if (resources == null) { + resources = new ProjectResources(false /* isFrameworkRepository */); + mMap.put(project, resources); + } + + processFolder(new IFolderWrapper(folder), resources); + } + } + break; + case IResourceDelta.CHANGED: + resources = mMap.get(folder.getProject()); + if (resources != null) { + ResourceFolder resFolder = resources.getResourceFolder(folder); + if (resFolder != null) { + resFolder.touch(); + } + } + break; + case IResourceDelta.REMOVED: + resources = mMap.get(folder.getProject()); + if (resources != null) { + // lets get the folder type + ResourceFolderType type = ResourceFolderType.getFolderType(folder.getName()); + + resources.removeFolder(type, folder); + } + break; + } + } + + /* (non-Javadoc) + * Sent when a file changed. Depending on the file being changed, and the type of change (ADDED, + * REMOVED, CHANGED), the file change is processed to update the resource manager data. + * + * @param file The file that changed. + * @param markerDeltas The marker deltas for the file. + * @param kind The change kind. This is equivalent to + * {@link IResourceDelta#accept(IResourceDeltaVisitor)} + * + * @see IFileListener#fileChanged + */ + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { + ProjectResources resources; + + final IProject project = file.getProject(); + + try { + if (project.hasNature(AndroidConstants.NATURE) == false) { + return; + } + } catch (CoreException e) { + // can't get the project nature? return! + return; + } + + switch (kind) { + case IResourceDelta.ADDED: + // checks if the file is under res/something. + IPath path = file.getFullPath(); + + if (path.segmentCount() == 4) { + if (isInResFolder(path)) { + // get the project and its resources + resources = mMap.get(project); + + IContainer container = file.getParent(); + if (container instanceof IFolder && resources != null) { + + ResourceFolder folder = resources.getResourceFolder((IFolder)container); + + if (folder != null) { + processFile(new IFileWrapper(file), folder); + } + } + } + } + break; + case IResourceDelta.CHANGED: + // try to find a matching ResourceFile + resources = mMap.get(project); + if (resources != null) { + IContainer container = file.getParent(); + if (container instanceof IFolder) { + ResourceFolder resFolder = resources.getResourceFolder((IFolder)container); + + // we get the delete on the folder before the file, so it is possible + // the associated ResourceFolder doesn't exist anymore. + if (resFolder != null) { + // get the resourceFile, and touch it. + ResourceFile resFile = resFolder.getFile(file); + if (resFile != null) { + resFile.touch(); + } + } + } + } + break; + case IResourceDelta.REMOVED: + // try to find a matching ResourceFile + resources = mMap.get(project); + if (resources != null) { + IContainer container = file.getParent(); + if (container instanceof IFolder) { + ResourceFolder resFolder = resources.getResourceFolder((IFolder)container); + + // we get the delete on the folder before the file, so it is possible + // the associated ResourceFolder doesn't exist anymore. + if (resFolder != null) { + // remove the file + resFolder.removeFile(file); + } + } + } + break; + } + } + + public void projectClosed(IProject project) { + mMap.remove(project); + } + + public void projectDeleted(IProject project) { + mMap.remove(project); + } + + public void projectOpened(IProject project) { + createProject(project); + } + + public void projectOpenedWithWorkspace(IProject project) { + createProject(project); + } + + /** + * Returns the {@link ResourceFolder} for the given file or null if none exists. + */ + public ResourceFolder getResourceFolder(IFile file) { + IContainer container = file.getParent(); + if (container.getType() == IResource.FOLDER) { + IFolder parent = (IFolder)container; + IProject project = file.getProject(); + + ProjectResources resources = getProjectResources(project); + if (resources != null) { + return resources.getResourceFolder(parent); + } + } + + return null; + } + + /** + * Loads and returns the resources for a given {@link IAndroidTarget} + * @param osFilePath the path to the folder containing all the versions of the framework + * resources + */ + public ProjectResources loadFrameworkResources(IAndroidTarget androidTarget) { + String osResourcesPath = androidTarget.getPath(IAndroidTarget.RESOURCES); + + File frameworkRes = new File(osResourcesPath); + if (frameworkRes.isDirectory()) { + ProjectResources resources = new ProjectResources(true /* isFrameworkRepository */); + + try { + File[] files = frameworkRes.listFiles(); + for (File file : files) { + if (file.isDirectory()) { + ResourceFolder resFolder = processFolder(new FolderWrapper(file), + resources); + + if (resFolder != null) { + // now we process the content of the folder + File[] children = file.listFiles(); + + for (File childRes : children) { + if (childRes.isFile()) { + processFile(new FileWrapper(childRes), resFolder); + } + } + } + + } + } + + // now that we have loaded the files, we need to force load the resources from them + resources.loadAll(); + + return resources; + + } catch (IOException e) { + // since we test that folders are folders, and files are files, this shouldn't + // happen. We can ignore it. + } + } + + return null; + } + + /** + * Initial project parsing to gather resource info. + * @param project + */ + private void createProject(IProject project) { + if (project.isOpen()) { + try { + if (project.hasNature(AndroidConstants.NATURE) == false) { + return; + } + } catch (CoreException e1) { + // can't check the nature of the project? ignore it. + return; + } + + IFolder resourceFolder = project.getFolder(AndroidConstants.FD_RESOURCES); + + ProjectResources projectResources = mMap.get(project); + if (projectResources == null) { + projectResources = new ProjectResources(false /* isFrameworkRepository */); + mMap.put(project, projectResources); + } + + 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 = processFolder(new IFolderWrapper(folder), + projectResources); + + 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; + + processFile(new IFileWrapper(file), resFolder); + } + } + } + } + } + } 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. + } + } + } + } + + /** + * Creates a {@link FolderConfiguration} matching the folder segments. + * @param folderSegments The segments of the folder name. The first segments should contain + * the name of the folder + * @return a FolderConfiguration object, or null if the folder name isn't valid.. + */ + public FolderConfiguration getConfig(String[] folderSegments) { + FolderConfiguration config = new FolderConfiguration(); + + // we are going to loop through the segments, and match them with the first + // available qualifier. If the segment doesn't match we try with the next qualifier. + // Because the order of the qualifier is fixed, we do not reset the first qualifier + // after each sucessful segment. + // If we run out of qualifier before processing all the segments, we fail. + + int qualifierIndex = 0; + int qualifierCount = mQualifiers.length; + + for (int i = 1 ; i < folderSegments.length; i++) { + String seg = folderSegments[i]; + if (seg.length() > 0) { + while (qualifierIndex < qualifierCount && + mQualifiers[qualifierIndex].checkAndSet(seg, config) == false) { + qualifierIndex++; + } + + // if we reached the end of the qualifier we didn't find a matching qualifier. + if (qualifierIndex == qualifierCount) { + return null; + } + + } else { + return null; + } + } + + return config; + } + + /** + * Processes a folder and adds it to the list of the project resources. + * @param folder the folder to process + * @param project the folder's project. + * @return the ConfiguredFolder created from this folder, or null if the process failed. + */ + private ResourceFolder processFolder(IAbstractFolder folder, ProjectResources project) { + // split the name of the folder in segments. + String[] folderSegments = folder.getName().split(FolderConfiguration.QUALIFIER_SEP); + + // get the enum for the resource type. + ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]); + + if (type != null) { + // get the folder configuration. + FolderConfiguration config = getConfig(folderSegments); + + if (config != null) { + ResourceFolder configuredFolder = project.add(type, config, folder); + + return configuredFolder; + } + } + + return null; + } + + /** + * Processes a file and adds it to its parent folder resource. + * @param file + * @param folder + */ + private void processFile(IAbstractFile file, ResourceFolder folder) { + // get the type of the folder + ResourceFolderType type = folder.getType(); + + // look for this file if it's already been created + ResourceFile resFile = folder.getFile(file); + + if (resFile != null) { + // invalidate the file + resFile.touch(); + } else { + // create a ResourceFile for it. + + // check if that's a single or multi resource type folder. For now we define this by + // the number of possible resource type output by files in the folder. This does + // not make the difference between several resource types from a single file or + // the ability to have 2 files in the same folder generating 2 different types of + // resource. The former is handled by MultiResourceFile properly while we don't + // handle the latter. If we were to add this behavior we'd have to change this call. + ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(type); + + if (types.length == 1) { + resFile = new SingleResourceFile(file, folder); + } else { + resFile = new MultiResourceFile(file, folder); + } + + // add it to the folder + folder.addFile(resFile); + } + } + + /** + * Returns true if the path is under /project/res/ + * @param path a workspace relative path + * @return true if the path is under /project res/ + */ + private boolean isInResFolder(IPath path) { + return AndroidConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1)); + } + + /** + * Private constructor to enforce singleton design. + */ + ResourceManager() { + // get the default qualifiers. + FolderConfiguration defaultConfig = new FolderConfiguration(); + defaultConfig.createDefault(); + mQualifiers = defaultConfig.getQualifiers(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java new file mode 100644 index 0000000..dc0f505 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.project.BaseProjectHelper; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; + +import java.util.ArrayList; + +/** + * Resource Monitor for the whole editor plugin. Other, more simple, listeners can register to + * that one. + */ +public class ResourceMonitor implements IResourceChangeListener { + + private final static ResourceMonitor sThis = new ResourceMonitor(); + + /** + * Classes which implement this interface provide a method that deals + * with file change events. + */ + public interface IFileListener { + /** + * Sent when a file changed. + * @param file The file that changed. + * @param markerDeltas The marker deltas for the file. + * @param kind The change kind. This is equivalent to + * {@link IResourceDelta#accept(IResourceDeltaVisitor)} + */ + public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind); + } + + /** + * Classes which implements this interface provide methods dealing with project events. + */ + public interface IProjectListener { + /** + * Sent for each opened android project at the time the listener is put in place. + * @param project the opened project. + */ + public void projectOpenedWithWorkspace(IProject project); + /** + * Sent when a project is opened. + * @param project the project being opened. + */ + public void projectOpened(IProject project); + /** + * Sent when a project is closed. + * @param project the project being closed. + */ + public void projectClosed(IProject project); + /** + * Sent when a project is deleted. + * @param project the project about to be deleted. + */ + public void projectDeleted(IProject project); + } + + /** + * Classes which implement this interface provide a method that deals + * with folder change events + */ + public interface IFolderListener { + /** + * Sent when a folder changed. + * @param folder The file that was changed + * @param kind The change kind. This is equivalent to {@link IResourceDelta#getKind()} + */ + public void folderChanged(IFolder folder, int kind); + } + + /** + * Interface for a listener to be notified when resource change event starts and ends. + */ + public interface IResourceEventListener { + public void resourceChangeEventStart(); + public void resourceChangeEventEnd(); + } + + /** + * Base listener bundle to associate a listener to an event mask. + */ + private static class ListenerBundle { + /** Mask value to accept all events */ + public final static int MASK_NONE = -1; + + /** + * Event mask. Values accepted are IResourceDelta.### + * @see IResourceDelta#ADDED + * @see IResourceDelta#REMOVED + * @see IResourceDelta#CHANGED + * @see IResourceDelta#ADDED_PHANTOM + * @see IResourceDelta#REMOVED_PHANTOM + * */ + int kindMask; + } + + /** + * Listener bundle for file event. + */ + private static class FileListenerBundle extends ListenerBundle { + + /** The file listener */ + IFileListener listener; + } + + /** + * Listener bundle for folder event. + */ + private static class FolderListenerBundle extends ListenerBundle { + /** The file listener */ + IFolderListener listener; + } + + private final ArrayList mFileListeners = + new ArrayList(); + + private final ArrayList mFolderListeners = + new ArrayList(); + + private final ArrayList mProjectListeners = new ArrayList(); + + private final ArrayList mEventListeners = + new ArrayList(); + + private IWorkspace mWorkspace; + + /** + * Delta visitor for resource changes. + */ + private final class DeltaVisitor implements IResourceDeltaVisitor { + + public boolean visit(IResourceDelta delta) { + IResource r = delta.getResource(); + int type = r.getType(); + if (type == IResource.FILE) { + int kind = delta.getKind(); + // notify the listeners. + for (FileListenerBundle bundle : mFileListeners) { + if (bundle.kindMask == ListenerBundle.MASK_NONE + || (bundle.kindMask & kind) != 0) { + bundle.listener.fileChanged((IFile)r, delta.getMarkerDeltas(), kind); + } + } + return false; + } else if (type == IResource.FOLDER) { + int kind = delta.getKind(); + // notify the listeners. + for (FolderListenerBundle bundle : mFolderListeners) { + if (bundle.kindMask == ListenerBundle.MASK_NONE + || (bundle.kindMask & kind) != 0) { + bundle.listener.folderChanged((IFolder)r, kind); + } + } + return true; + } else if (type == IResource.PROJECT) { + int flags = delta.getFlags(); + + if (flags == IResourceDelta.OPEN) { + // the project is opening or closing. + IProject project = (IProject)r; + + // the OPEN flag represent a toggle in the open/close state of the + // project, but this is sent before the project actually toggles + // its state. + // This means that if the project is closing, isOpen() will return true. + boolean isClosing = project.isOpen(); + if (isClosing) { + // notify the listeners. + for (IProjectListener pl : mProjectListeners) { + pl.projectClosed(project); + } + } else { + // notify the listeners. + for (IProjectListener pl : mProjectListeners) { + pl.projectOpened(project); + } + } + } + } + + return true; + } + } + + public static ResourceMonitor getMonitor() { + return sThis; + } + + + /** + * Starts the resource monitoring. + * @param ws The current workspace. + * @return The monitor object. + */ + public static ResourceMonitor startMonitoring(IWorkspace ws) { + if (sThis != null) { + ws.addResourceChangeListener(sThis, + IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE); + sThis.mWorkspace = ws; + } + return sThis; + } + + /** + * Stops the resource monitoring. + * @param ws The current workspace. + */ + public static void stopMonitoring(IWorkspace ws) { + if (sThis != null) { + ws.removeResourceChangeListener(sThis); + + sThis.mFileListeners.clear(); + sThis.mProjectListeners.clear(); + } + } + + /** + * Adds a file listener. + * @param listener The listener to receive the events. + * @param kindMask The event mask to filter out specific events. + * {@link ListenerBundle#MASK_NONE} will forward all events. + */ + public synchronized void addFileListener(IFileListener listener, int kindMask) { + FileListenerBundle bundle = new FileListenerBundle(); + bundle.listener = listener; + bundle.kindMask = kindMask; + + mFileListeners.add(bundle); + } + + /** + * Removes an existing file listener. + * @param listener the listener to remove. + */ + public synchronized void removeFileListener(IFileListener listener) { + for (int i = 0 ; i < mFileListeners.size() ; i++) { + FileListenerBundle bundle = mFileListeners.get(i); + if (bundle.listener == listener) { + mFileListeners.remove(i); + return; + } + } + } + + /** + * Adds a folder listener. + * @param listener The listener to receive the events. + * @param kindMask The event mask to filter out specific events. + * {@link ListenerBundle#MASK_NONE} will forward all events. + */ + public synchronized void addFolderListener(IFolderListener listener, int kindMask) { + FolderListenerBundle bundle = new FolderListenerBundle(); + bundle.listener = listener; + bundle.kindMask = kindMask; + + mFolderListeners.add(bundle); + } + + /** + * Adds a project listener. + * @param listener The listener to receive the events. + */ + public synchronized void addProjectListener(IProjectListener listener) { + mProjectListeners.add(listener); + + // we need to look at the opened projects and give them to the listener. + + // get the list of opened android projects. + IWorkspaceRoot workspaceRoot = mWorkspace.getRoot(); + IJavaModel javaModel = JavaCore.create(workspaceRoot); + IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(javaModel); + + for (IJavaProject androidProject : androidProjects) { + listener.projectOpenedWithWorkspace(androidProject.getProject()); + } + } + + public synchronized void addResourceEventListener(IResourceEventListener listener) { + mEventListeners.add(listener); + } + + /** + * Processes the workspace resource change events. + */ + public void resourceChanged(IResourceChangeEvent event) { + // notify the event listeners of a start. + for (IResourceEventListener listener : mEventListeners) { + listener.resourceChangeEventStart(); + } + + if (event.getType() == IResourceChangeEvent.PRE_DELETE) { + // a project is being deleted. Lets get the project object and remove + // its compiled resource list. + IResource r = event.getResource(); + IProject project = r.getProject(); + + // notify the listeners. + for (IProjectListener pl : mProjectListeners) { + pl.projectDeleted(project); + } + } else { + // this a regular resource change. We get the delta and go through it with a visitor. + IResourceDelta delta = event.getDelta(); + + DeltaVisitor visitor = new DeltaVisitor(); + try { + delta.accept(visitor); + } catch (CoreException e) { + } + } + + // we're done, notify the event listeners. + for (IResourceEventListener listener : mEventListeners) { + listener.resourceChangeEventEnd(); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java new file mode 100644 index 0000000..1211236 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2007 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.editors.resources.manager; + +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; +import com.android.layoutlib.api.IResourceValue; +import com.android.layoutlib.utils.ResourceValue; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.SAXParserFactory; + +/** + * Represents a resource file describing a single resource. + *

          + * This is typically an XML file inside res/anim, res/layout, or res/menu or an image file + * under res/drawable. + */ +public class SingleResourceFile extends ResourceFile { + + private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); + static { + sParserFactory.setNamespaceAware(true); + } + + private final static Pattern sXmlPattern = Pattern.compile("^(.+)\\.xml", //$NON-NLS-1$ + Pattern.CASE_INSENSITIVE); + + private final static Pattern[] sDrawablePattern = new Pattern[] { + Pattern.compile("^(.+)\\.9\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ + Pattern.compile("^(.+)\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ + Pattern.compile("^(.+)\\.jpg", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ + Pattern.compile("^(.+)\\.gif", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ + }; + + private String mResourceName; + private ResourceType mType; + private IResourceValue mValue; + + public SingleResourceFile(IAbstractFile file, ResourceFolder folder) { + super(file, folder); + + // we need to infer the type of the resource from the folder type. + // This is easy since this is a single Resource file. + ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType()); + mType = types[0]; + + // compute the resource name + mResourceName = getResourceName(mType); + + mValue = new ResourceValue(mType.getName(), getResourceName(mType), file.getOsLocation(), + isFramework()); + } + + @Override + public ResourceType[] getResourceTypes() { + return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType()); + } + + @Override + public boolean hasResources(ResourceType type) { + return FolderTypeRelationship.match(type, getFolder().getType()); + } + + @Override + public Collection getResources(ResourceType type, + ProjectResources projectResources) { + + // looking for an existing ResourceItem with this name and type + ProjectResourceItem item = projectResources.findResourceItem(type, mResourceName); + + ArrayList items = new ArrayList(); + + if (item == null) { + item = new ConfigurableResourceItem(mResourceName); + items.add(item); + } + + // add this ResourceFile to the ResourceItem + item.add(this); + + return items; + } + + /* + * (non-Javadoc) + * @see com.android.ide.eclipse.editors.resources.manager.ResourceFile#getValue(com.android.ide.eclipse.common.resources.ResourceType, java.lang.String) + * + * This particular implementation does not care about the type or name since a + * SingleResourceFile represents a file generating only one resource. + * The value returned is the full absolute path of the file in OS form. + */ + @Override + public IResourceValue getValue(ResourceType type, String name) { + return mValue; + } + + /** + * Returns the name of the resources. + * @param type + * @return + */ + private String getResourceName(ResourceType type) { + // get the name from the filename. + String name = getFile().getName(); + + if (type == ResourceType.ANIM || type == ResourceType.LAYOUT || type == ResourceType.MENU || + type == ResourceType.COLOR || type == ResourceType.XML) { + Matcher m = sXmlPattern.matcher(name); + if (m.matches()) { + return m.group(1); + } + } else if (type == ResourceType.DRAWABLE) { + for (Pattern p : sDrawablePattern) { + Matcher m = p.matcher(name); + if (m.matches()) { + return m.group(1); + } + } + + // also try the Xml pattern for selector/shape based drawable. + Matcher m = sXmlPattern.matcher(name); + if (m.matches()) { + return m.group(1); + } + } + return name; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java new file mode 100644 index 0000000..0a14214 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java @@ -0,0 +1,87 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +/** + * An implementation of {@link IAbstractFile} on top of a {@link File} object. + * + */ +public class FileWrapper implements IAbstractFile { + + private File mFile; + + /** + * Constructs a {@link FileWrapper} object. If {@link File#isFile()} returns false + * then an {@link IOException} is thrown. + */ + public FileWrapper(File file) throws IOException { + if (file.isFile() == false) { + throw new IOException("FileWrapper must wrap a File object representing an existing file!"); //$NON-NLS-1$ + } + + mFile = file; + } + + public InputStream getContents() throws CoreException { + try { + return new FileInputStream(mFile); + } catch (FileNotFoundException e) { + // we'll return null below. + } + + return null; + } + + public IFile getIFile() { + return null; + } + + public String getOsLocation() { + return mFile.getAbsolutePath(); + } + + public String getName() { + return mFile.getName(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof FileWrapper) { + return mFile.equals(((FileWrapper)obj).mFile); + } + + if (obj instanceof File) { + return mFile.equals((File)obj); + } + + return super.equals(obj); + } + + @Override + public int hashCode() { + return mFile.hashCode(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java new file mode 100644 index 0000000..8afea33 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java @@ -0,0 +1,73 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFolder; + +import java.io.File; +import java.io.IOException; + +/** + * An implementation of {@link IAbstractFolder} on top of a {@link File} object. + */ +public class FolderWrapper implements IAbstractFolder { + + private File mFolder; + + /** + * Constructs a {@link FileWrapper} object. If {@link File#isDirectory()} returns + * false then an {@link IOException} is thrown. + */ + public FolderWrapper(File folder) throws IOException { + if (folder.isDirectory() == false) { + throw new IOException("FileWrapper must wrap a File object representing an existing folder!"); //$NON-NLS-1$ + } + + mFolder = folder; + } + + public boolean hasFile(String name) { + return false; + } + + public String getName() { + return mFolder.getName(); + } + + public IFolder getIFolder() { + return null; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof FolderWrapper) { + return mFolder.equals(((FolderWrapper)obj).mFolder); + } + + if (obj instanceof File) { + return mFolder.equals((File)obj); + } + + return super.equals(obj); + } + + @Override + public int hashCode() { + return mFolder.hashCode(); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java new file mode 100644 index 0000000..7e807f9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java @@ -0,0 +1,44 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +import java.io.InputStream; + +/** + * A file. + */ +public interface IAbstractFile extends IAbstractResource { + + /** + * Returns an {@link InputStream} object on the file content. + * @throws CoreException + */ + InputStream getContents() throws CoreException; + + /** + * Returns the OS path of the file location. + */ + String getOsLocation(); + + /** + * Returns the {@link IFile} object that the receiver could represent. Can be null + */ + IFile getIFile(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java new file mode 100644 index 0000000..b35283d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java @@ -0,0 +1,38 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFolder; + +/** + * A folder. + */ +public interface IAbstractFolder extends IAbstractResource { + + /** + * Returns true if the receiver contains a file with a given name + * @param name the name of the file. This is the name without the path leading to the + * parent folder. + */ + boolean hasFile(String name); + + /** + * Returns the {@link IFolder} object that the receiver could represent. + * Can be null + */ + IFolder getIFolder(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java new file mode 100644 index 0000000..daf243d --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java @@ -0,0 +1,34 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFile; + +import java.io.File; + +/** + * Base representation of a file system resource.

          + * This somewhat limited interface is designed to let classes use file-system resources, without + * having the manually handle {@link IFile} and/or {@link File} manually. + */ +public interface IAbstractResource { + + /** + * Returns the name of the resource. + */ + String getName(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java new file mode 100644 index 0000000..441c65b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java @@ -0,0 +1,68 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; + +import java.io.InputStream; + +/** + * An implementation of {@link IAbstractFile} on top of an {@link IFile} object. + */ +public class IFileWrapper implements IAbstractFile { + + private IFile mFile; + + public IFileWrapper(IFile file) { + mFile = file; + } + + public InputStream getContents() throws CoreException { + return mFile.getContents(); + } + + public String getOsLocation() { + return mFile.getLocation().toOSString(); + } + + public String getName() { + return mFile.getName(); + } + + public IFile getIFile() { + return mFile; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof IFileWrapper) { + return mFile.equals(((IFileWrapper)obj).mFile); + } + + if (obj instanceof IFile) { + return mFile.equals((IFile)obj); + } + + return super.equals(obj); + } + + @Override + public int hashCode() { + return mFile.hashCode(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java new file mode 100644 index 0000000..92b5c07 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java @@ -0,0 +1,74 @@ +/* + * 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.editors.resources.manager.files; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; + +/** + * An implementation of {@link IAbstractFolder} on top of an {@link IFolder} object. + */ +public class IFolderWrapper implements IAbstractFolder { + + private IFolder mFolder; + + public IFolderWrapper(IFolder folder) { + mFolder = folder; + } + + public String getName() { + return mFolder.getName(); + } + + public boolean hasFile(String name) { + try { + IResource[] files = mFolder.members(); + for (IResource file : files) { + if (name.equals(file.getName())) { + return true; + } + } + } catch (CoreException e) { + // we'll return false below. + } + + return false; + } + + public IFolder getIFolder() { + return mFolder; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof IFolderWrapper) { + return mFolder.equals(((IFolderWrapper)obj).mFolder); + } + + if (obj instanceof IFolder) { + return mFolder.equals((IFolder)obj); + } + + return super.equals(obj); + } + + @Override + public int hashCode() { + return mFolder.hashCode(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java new file mode 100644 index 0000000..29453e9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007 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.editors.resources.uimodel; + +import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.editors.uimodel.UiTextValueNode; + +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Text; + +import java.util.regex.Pattern; + +/** + * Displays and edits a color XML element value with a custom validator. + *

          + * See {@link UiAttributeNode} for more information. + */ +public class UiColorValueNode extends UiTextValueNode { + + /** Accepted RGBA formats are one of #RGB, #ARGB, #RRGGBB or #AARRGGBB. */ + private static final Pattern RGBA_REGEXP = Pattern.compile( + "#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})"); //$NON-NLS-1$ + + public UiColorValueNode(TextValueDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /* (non-java doc) + * + * Add a modify listener that will check colors have the proper format, + * that is one of #RGB, #ARGB, #RRGGBB or #AARRGGBB. + */ + @Override + protected void onAddValidators(final Text text) { + ModifyListener listener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + String color = text.getText(); + if (RGBA_REGEXP.matcher(color).matches()) { + getManagedForm().getMessageManager().removeMessage(text, text); + } else { + getManagedForm().getMessageManager().addMessage(text, + "Accepted color formats are one of #RGB, #ARGB, #RRGGBB or #AARRGGBB.", + null /* data */, IMessageProvider.ERROR, text); + } + } + }; + + text.addModifyListener(listener); + + // Make sure the validator removes its message(s) when the widget is disposed + text.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + getManagedForm().getMessageManager().removeMessage(text, text); + } + }); + + // Finally call the validator once to make sure the initial value is processed + listener.modifyText(null); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java new file mode 100644 index 0000000..89649f5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java @@ -0,0 +1,58 @@ +/* + * 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.editors.resources.uimodel; + +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.resources.descriptors.ItemElementDescriptor; +import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * {@link UiItemElementNode} is apecial version of {@link UiElementNode} that + * customizes the element display to include the item type attribute if present. + */ +public class UiItemElementNode extends UiElementNode { + + /** + * Creates a new {@link UiElementNode} described by a given {@link ItemElementDescriptor}. + * + * @param elementDescriptor The {@link ItemElementDescriptor} for the XML node. Cannot be null. + */ + public UiItemElementNode(ItemElementDescriptor elementDescriptor) { + super(elementDescriptor); + } + + @Override + public String getShortDescription() { + Node xmlNode = getXmlNode(); + if (xmlNode != null && xmlNode instanceof Element && xmlNode.hasAttributes()) { + + Element elem = (Element) xmlNode; + String type = elem.getAttribute(ResourcesDescriptors.TYPE_ATTR); + String name = elem.getAttribute(ResourcesDescriptors.NAME_ATTR); + if (type != null && name != null && type.length() > 0 && name.length() > 0) { + type = DescriptorsUtils.capitalize(type); + return String.format("%1$s (%2$s %3$s)", name, type, getDescriptor().getUiName()); + } + } + + return super.getShortDescription(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java new file mode 100644 index 0000000..5fb479f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java @@ -0,0 +1,458 @@ +/* + * 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.editors.ui; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.viewers.DialogCellEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +import java.text.MessageFormat; + +/** + * Custom DialogCellEditor, replacing the Label with an editable {@link Text} widget. + *

          Also set the button to {@link SWT#FLAT} to make sure it looks good on MacOS X. + *

          Most of the code comes from TextCellEditor. + */ +public abstract class EditableDialogCellEditor extends DialogCellEditor { + + private Text text; + + private ModifyListener modifyListener; + + /** + * State information for updating action enablement + */ + private boolean isSelection = false; + + private boolean isDeleteable = false; + + private boolean isSelectable = false; + + EditableDialogCellEditor(Composite parent) { + super(parent); + } + + /* + * Re-implement this method only to properly set the style in the button, or it won't look + * good in MacOS X + */ + @Override + protected Button createButton(Composite parent) { + Button result = new Button(parent, SWT.DOWN | SWT.FLAT); + result.setText("..."); //$NON-NLS-1$ + return result; + } + + + @Override + protected Control createContents(Composite cell) { + text = new Text(cell, SWT.SINGLE); + text.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + handleDefaultSelection(e); + } + }); + text.addKeyListener(new KeyAdapter() { + // hook key pressed - see PR 14201 + @Override + public void keyPressed(KeyEvent e) { + keyReleaseOccured(e); + + // as a result of processing the above call, clients may have + // disposed this cell editor + if ((getControl() == null) || getControl().isDisposed()) { + return; + } + checkSelection(); // see explanation below + checkDeleteable(); + checkSelectable(); + } + }); + text.addTraverseListener(new TraverseListener() { + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_ESCAPE + || e.detail == SWT.TRAVERSE_RETURN) { + e.doit = false; + } + } + }); + // We really want a selection listener but it is not supported so we + // use a key listener and a mouse listener to know when selection changes + // may have occurred + text.addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + checkSelection(); + checkDeleteable(); + checkSelectable(); + } + }); + text.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + EditableDialogCellEditor.this.focusLost(); + } + }); + text.setFont(cell.getFont()); + text.setBackground(cell.getBackground()); + text.setText("");//$NON-NLS-1$ + text.addModifyListener(getModifyListener()); + return text; + } + + /** + * Checks to see if the "deletable" state (can delete/ + * nothing to delete) has changed and if so fire an + * enablement changed notification. + */ + private void checkDeleteable() { + boolean oldIsDeleteable = isDeleteable; + isDeleteable = isDeleteEnabled(); + if (oldIsDeleteable != isDeleteable) { + fireEnablementChanged(DELETE); + } + } + + /** + * Checks to see if the "selectable" state (can select) + * has changed and if so fire an enablement changed notification. + */ + private void checkSelectable() { + boolean oldIsSelectable = isSelectable; + isSelectable = isSelectAllEnabled(); + if (oldIsSelectable != isSelectable) { + fireEnablementChanged(SELECT_ALL); + } + } + + /** + * Checks to see if the selection state (selection / + * no selection) has changed and if so fire an + * enablement changed notification. + */ + private void checkSelection() { + boolean oldIsSelection = isSelection; + isSelection = text.getSelectionCount() > 0; + if (oldIsSelection != isSelection) { + fireEnablementChanged(COPY); + fireEnablementChanged(CUT); + } + } + + /* (non-Javadoc) + * Method declared on CellEditor. + */ + @Override + protected void doSetFocus() { + if (text != null) { + text.selectAll(); + text.setFocus(); + checkSelection(); + checkDeleteable(); + checkSelectable(); + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.viewers.DialogCellEditor#updateContents(java.lang.Object) + */ + @Override + protected void updateContents(Object value) { + Assert.isTrue(text != null && (value == null || (value instanceof String))); + if (value != null) { + text.removeModifyListener(getModifyListener()); + text.setText((String) value); + text.addModifyListener(getModifyListener()); + } + } + + /** + * The TextCellEditor implementation of + * this CellEditor framework method returns + * the text string. + * + * @return the text string + */ + @Override + protected Object doGetValue() { + return text.getText(); + } + + + /** + * Processes a modify event that occurred in this text cell editor. + * This framework method performs validation and sets the error message + * accordingly, and then reports a change via fireEditorValueChanged. + * Subclasses should call this method at appropriate times. Subclasses + * may extend or reimplement. + * + * @param e the SWT modify event + */ + protected void editOccured(ModifyEvent e) { + String value = text.getText(); + if (value == null) { + value = "";//$NON-NLS-1$ + } + Object typedValue = value; + boolean oldValidState = isValueValid(); + boolean newValidState = isCorrect(typedValue); + + if (!newValidState) { + // try to insert the current value into the error message. + setErrorMessage(MessageFormat.format(getErrorMessage(), + new Object[] { value })); + } + valueChanged(oldValidState, newValidState); + } + + /** + * Return the modify listener. + */ + private ModifyListener getModifyListener() { + if (modifyListener == null) { + modifyListener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + editOccured(e); + } + }; + } + return modifyListener; + } + + /** + * Handles a default selection event from the text control by applying the editor + * value and deactivating this cell editor. + * + * @param event the selection event + * + * @since 3.0 + */ + protected void handleDefaultSelection(SelectionEvent event) { + // same with enter-key handling code in keyReleaseOccured(e); + fireApplyEditorValue(); + deactivate(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method returns true if + * the current selection is not empty. + */ + @Override + public boolean isCopyEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return text.getSelectionCount() > 0; + } + + /** + * The TextCellEditor implementation of this + * CellEditor method returns true if + * the current selection is not empty. + */ + @Override + public boolean isCutEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return text.getSelectionCount() > 0; + } + + /** + * The TextCellEditor implementation of this + * CellEditor method returns true + * if there is a selection or if the caret is not positioned + * at the end of the text. + */ + @Override + public boolean isDeleteEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return text.getSelectionCount() > 0 + || text.getCaretPosition() < text.getCharCount(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method always returns true. + */ + @Override + public boolean isPasteEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return true; + } + + /** + * Check if save all is enabled + * @return true if it is + */ + public boolean isSaveAllEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return true; + } + + /** + * Returns true if this cell editor is + * able to perform the select all action. + *

          + * This default implementation always returns + * false. + *

          + *

          + * Subclasses may override + *

          + * @return true if select all is possible, + * false otherwise + */ + @Override + public boolean isSelectAllEnabled() { + if (text == null || text.isDisposed()) { + return false; + } + return text.getCharCount() > 0; + } + + /** + * Processes a key release event that occurred in this cell editor. + *

          + * The TextCellEditor implementation of this framework method + * ignores when the RETURN key is pressed since this is handled in + * handleDefaultSelection. + * An exception is made for Ctrl+Enter for multi-line texts, since + * a default selection event is not sent in this case. + *

          + * + * @param keyEvent the key event + */ + @Override + protected void keyReleaseOccured(KeyEvent keyEvent) { + if (keyEvent.character == '\r') { // Return key + // Enter is handled in handleDefaultSelection. + // Do not apply the editor value in response to an Enter key event + // since this can be received from the IME when the intent is -not- + // to apply the value. + // See bug 39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control + // + // An exception is made for Ctrl+Enter for multi-line texts, since + // a default selection event is not sent in this case. + if (text != null && !text.isDisposed() + && (text.getStyle() & SWT.MULTI) != 0) { + if ((keyEvent.stateMask & SWT.CTRL) != 0) { + super.keyReleaseOccured(keyEvent); + } + } + return; + } + super.keyReleaseOccured(keyEvent); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method copies the + * current selection to the clipboard. + */ + @Override + public void performCopy() { + text.copy(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method cuts the + * current selection to the clipboard. + */ + @Override + public void performCut() { + text.cut(); + checkSelection(); + checkDeleteable(); + checkSelectable(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method deletes the + * current selection or, if there is no selection, + * the character next character from the current position. + */ + @Override + public void performDelete() { + if (text.getSelectionCount() > 0) { + // remove the contents of the current selection + text.insert(""); //$NON-NLS-1$ + } else { + // remove the next character + int pos = text.getCaretPosition(); + if (pos < text.getCharCount()) { + text.setSelection(pos, pos + 1); + text.insert(""); //$NON-NLS-1$ + } + } + checkSelection(); + checkDeleteable(); + checkSelectable(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method pastes the + * the clipboard contents over the current selection. + */ + @Override + public void performPaste() { + text.paste(); + checkSelection(); + checkDeleteable(); + checkSelectable(); + } + + /** + * The TextCellEditor implementation of this + * CellEditor method selects all of the + * current text. + */ + @Override + public void performSelectAll() { + text.selectAll(); + checkSelection(); + checkDeleteable(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java new file mode 100644 index 0000000..d095376 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java @@ -0,0 +1,47 @@ +package com.android.ide.eclipse.editors.ui; + +import org.eclipse.jface.resource.CompositeImageDescriptor; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.DecorationOverlayIcon; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +/** + * ImageDescriptor that adds a error marker. + * Based on {@link DecorationOverlayIcon} only available in Eclipse 3.3 + */ +public class ErrorImageComposite extends CompositeImageDescriptor { + + private Image mBaseImage; + private ImageDescriptor mErrorImageDescriptor; + private Point mSize; + + public ErrorImageComposite(Image baseImage) { + mBaseImage = baseImage; + mErrorImageDescriptor = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( + ISharedImages.IMG_OBJS_ERROR_TSK); + mSize = new Point(baseImage.getBounds().width, baseImage.getBounds().height); + } + + @Override + protected void drawCompositeImage(int width, int height) { + ImageData baseData = mBaseImage.getImageData(); + drawImage(baseData, 0, 0); + + ImageData overlayData = mErrorImageDescriptor.getImageData(); + if (overlayData.width == baseData.width && baseData.height == baseData.height) { + overlayData = overlayData.scaledTo(14, 14); + drawImage(overlayData, -3, mSize.y - overlayData.height + 3); + } else { + drawImage(overlayData, 0, mSize.y - overlayData.height); + } + } + + @Override + protected Point getSize() { + return mSize; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java new file mode 100644 index 0000000..ccae099 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java @@ -0,0 +1,58 @@ +/* + * 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.editors.ui; + +import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * DialogCellEditor able to receive a {@link UiFlagAttributeNode} in the {@link #setValue(Object)} + * method. + *

          The dialog box opened is the same as the one in the ui created by + * {@link UiFlagAttributeNode#createUiControl(Composite, org.eclipse.ui.forms.IManagedForm)} + */ +public class FlagValueCellEditor extends EditableDialogCellEditor { + + private UiFlagAttributeNode mUiFlagAttribute; + + public FlagValueCellEditor(Composite parent) { + super(parent); + } + + @Override + protected Object openDialogBox(Control cellEditorWindow) { + if (mUiFlagAttribute != null) { + String currentValue = (String)getValue(); + return mUiFlagAttribute.showDialog(cellEditorWindow.getShell(), currentValue); + } + + return null; + } + + @Override + protected void doSetValue(Object value) { + if (value instanceof UiFlagAttributeNode) { + mUiFlagAttribute = (UiFlagAttributeNode)value; + super.doSetValue(mUiFlagAttribute.getCurrentValue()); + return; + } + + super.doSetValue(value); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java new file mode 100644 index 0000000..304dd14 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java @@ -0,0 +1,76 @@ +/* + * 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.editors.ui; + +import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; + +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * ComboBoxCellEditor able to receive a {@link UiListAttributeNode} in the {@link #setValue(Object)} + * method, and returning a {@link String} in {@link #getValue()} instead of an {@link Integer}. + */ +public class ListValueCellEditor extends ComboBoxCellEditor { + private String[] mItems; + private CCombo mCombo; + + public ListValueCellEditor(Composite parent) { + super(parent, new String[0], SWT.DROP_DOWN); + } + + @Override + protected Control createControl(Composite parent) { + mCombo = (CCombo) super.createControl(parent); + return mCombo; + } + + @Override + protected void doSetValue(Object value) { + if (value instanceof UiListAttributeNode) { + UiListAttributeNode uiListAttribute = (UiListAttributeNode)value; + + // set the possible values in the combo + String[] items = uiListAttribute.getPossibleValues(); + mItems = new String[items.length]; + System.arraycopy(items, 0, mItems, 0, items.length); + setItems(mItems); + + // now edit the current value of the attribute + String attrValue = uiListAttribute.getCurrentValue(); + mCombo.setText(attrValue); + + return; + } + + // default behavior + super.doSetValue(value); + } + + @Override + protected Object doGetValue() { + String comboText = mCombo.getText(); + if (comboText == null) { + return ""; //$NON-NLS-1$ + } + return comboText; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java new file mode 100644 index 0000000..4fc0ab3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java @@ -0,0 +1,59 @@ +/* + * 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.editors.ui; + +import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * DialogCellEditor able to receive a {@link UiFlagAttributeNode} in the {@link #setValue(Object)} + * method. + *

          The dialog box opened is the same as the one in the ui created by + * {@link UiFlagAttributeNode#createUiControl(Composite, org.eclipse.ui.forms.IManagedForm)} + */ +public class ResourceValueCellEditor extends EditableDialogCellEditor { + + private UiResourceAttributeNode mUiResourceAttribute; + + public ResourceValueCellEditor(Composite parent) { + super(parent); + } + + @Override + protected Object openDialogBox(Control cellEditorWindow) { + if (mUiResourceAttribute != null) { + String currentValue = (String)getValue(); + return mUiResourceAttribute.showDialog(cellEditorWindow.getShell(), currentValue); + } + + return null; + } + + @Override + protected void doSetValue(Object value) { + if (value instanceof UiResourceAttributeNode) { + mUiResourceAttribute = (UiResourceAttributeNode)value; + super.doSetValue(mUiResourceAttribute.getCurrentValue()); + return; + } + + super.doSetValue(value); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/SectionHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/SectionHelper.java new file mode 100644 index 0000000..409e92f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/SectionHelper.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2007 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.editors.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.AndroidEditor; + +import org.eclipse.jface.text.DefaultInformationControl; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.SectionPart; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; + +import java.lang.reflect.Method; + +/** + * Helper for the AndroidManifest form editor. + * + * Helps create a new SectionPart with sensible default parameters, + * create default layout or add typical widgets. + * + * IMPORTANT: This is NOT a generic class. It makes a lot of assumptions on the + * UI as used by the form editor for the AndroidManifest. + * + * TODO: Consider moving to a common package. + */ +public final class SectionHelper { + + /** + * Utility class that derives from SectionPart, constructs the Section with + * sensible defaults (with a title and a description) and provide some shorthand + * methods for creating typically UI (label and text, form text.) + */ + static public class ManifestSectionPart extends SectionPart { + + /** + * Construct a SectionPart that uses a title bar and a description. + * It's up to the caller to call setText() and setDescription(). + *

          + * The section style includes a description and a title bar by default. + * + * @param body The parent (e.g. FormPage body) + * @param toolkit Form Toolkit + */ + public ManifestSectionPart(Composite body, FormToolkit toolkit) { + this(body, toolkit, 0, false); + } + + /** + * Construct a SectionPart that uses a title bar and a description. + * It's up to the caller to call setText() and setDescription(). + *

          + * The section style includes a description and a title bar by default. + * You can add extra styles, like Section.TWISTIE. + * + * @param body The parent (e.g. FormPage body). + * @param toolkit Form Toolkit. + * @param extra_style Extra styles (on top of description and title bar). + * @param use_description True if the Section.DESCRIPTION style should be added. + */ + public ManifestSectionPart(Composite body, FormToolkit toolkit, + int extra_style, boolean use_description) { + super(body, toolkit, extra_style | + Section.TITLE_BAR | + (use_description ? Section.DESCRIPTION : 0)); + } + + // Create non-static methods of helpers just for convenience + + /** + * Creates a new composite with a TableWrapLayout set with a given number of columns. + * + * If the parent composite is a Section, the new composite is set as a client. + * + * @param toolkit Form Toolkit + * @param numColumns Desired number of columns. + * @return The new composite. + */ + public Composite createTableLayout(FormToolkit toolkit, int numColumns) { + return SectionHelper.createTableLayout(getSection(), toolkit, numColumns); + } + + /** + * Creates a label widget. + * If the parent layout if a TableWrapLayout, maximize it to span over all columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param label The string for the label. + * @param tooltip An optional tooltip for the label and text. Can be null. + * @return The new created label + */ + public Label createLabel(Composite parent, FormToolkit toolkit, String label, + String tooltip) { + return SectionHelper.createLabel(parent, toolkit, label, tooltip); + } + + /** + * Creates two widgets: a label and a text field. + * + * This expects the parent composite to have a TableWrapLayout with 2 columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param label The string for the label. + * @param value The initial value of the text field. Can be null. + * @param tooltip An optional tooltip for the label and text. Can be null. + * @return The new created Text field (the label is not returned) + */ + public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label, + String value, String tooltip) { + return SectionHelper.createLabelAndText(parent, toolkit, label, value, tooltip); + } + + /** + * Creates a FormText widget. + * + * This expects the parent composite to have a TableWrapLayout with 2 columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param isHtml True if the form text will contain HTML that must be interpreted as + * rich text (i.e. parse tags & expand URLs). + * @param label The string for the label. + * @param setupLayoutData indicates whether the created form text receives a TableWrapData + * through the setLayoutData method. In some case, creating it will make the table parent + * huge, which we don't want. + * @return The new created FormText. + */ + public FormText createFormText(Composite parent, FormToolkit toolkit, boolean isHtml, + String label, boolean setupLayoutData) { + return SectionHelper.createFormText(parent, toolkit, isHtml, label, setupLayoutData); + } + + /** + * Forces the section to recompute its layout and redraw. + * This is needed after the content of the section has been changed at runtime. + */ + public void layoutChanged() { + Section section = getSection(); + + // Calls getSection().reflow(), which is the same that Section calls + // when the expandable state is changed and the height changes. + // Since this is protected, some reflection is needed to invoke it. + try { + Method reflow; + reflow = section.getClass().getDeclaredMethod("reflow", (Class[])null); + reflow.setAccessible(true); + reflow.invoke(section); + } catch (Exception e) { + AdtPlugin.log(e, "Error when invoking Section.reflow"); + } + + section.layout(true /* changed */, true /* all */); + } + } + + /** + * Creates a new composite with a TableWrapLayout set with a given number of columns. + * + * If the parent composite is a Section, the new composite is set as a client. + * + * @param composite The parent (e.g. a Section or SectionPart) + * @param toolkit Form Toolkit + * @param numColumns Desired number of columns. + * @return The new composite. + */ + static public Composite createTableLayout(Composite composite, FormToolkit toolkit, + int numColumns) { + Composite table = toolkit.createComposite(composite); + TableWrapLayout layout = new TableWrapLayout(); + layout.numColumns = numColumns; + table.setLayout(layout); + toolkit.paintBordersFor(table); + if (composite instanceof Section) { + ((Section) composite).setClient(table); + } + return table; + } + + /** + * Creates a new composite with a GridLayout set with a given number of columns. + * + * If the parent composite is a Section, the new composite is set as a client. + * + * @param composite The parent (e.g. a Section or SectionPart) + * @param toolkit Form Toolkit + * @param numColumns Desired number of columns. + * @return The new composite. + */ + static public Composite createGridLayout(Composite composite, FormToolkit toolkit, + int numColumns) { + Composite grid = toolkit.createComposite(composite); + GridLayout layout = new GridLayout(); + layout.numColumns = numColumns; + grid.setLayout(layout); + toolkit.paintBordersFor(grid); + if (composite instanceof Section) { + ((Section) composite).setClient(grid); + } + return grid; + } + + /** + * Creates two widgets: a label and a text field. + * + * This expects the parent composite to have a TableWrapLayout with 2 columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param label_text The string for the label. + * @param value The initial value of the text field. Can be null. + * @param tooltip An optional tooltip for the label and text. Can be null. + * @return The new created Text field (the label is not returned) + */ + static public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label_text, + String value, String tooltip) { + Label label = toolkit.createLabel(parent, label_text); + label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + Text text = toolkit.createText(parent, value); + text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); + + addControlTooltip(label, tooltip); + return text; + } + + /** + * Creates a label widget. + * If the parent layout if a TableWrapLayout, maximize it to span over all columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param label_text The string for the label. + * @param tooltip An optional tooltip for the label and text. Can be null. + * @return The new created label + */ + static public Label createLabel(Composite parent, FormToolkit toolkit, String label_text, + String tooltip) { + Label label = toolkit.createLabel(parent, label_text); + + TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); + if (parent.getLayout() instanceof TableWrapLayout) { + twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; + } + label.setLayoutData(twd); + + addControlTooltip(label, tooltip); + return label; + } + + /** + * Associates a tooltip with a control. + * + * This mirrors the behavior from org.eclipse.pde.internal.ui.editor.text.PDETextHover + * + * @param control The control to which associate the tooltip. + * @param tooltip The tooltip string. Can use \n for multi-lines. Will not display if null. + */ + static public void addControlTooltip(final Control control, String tooltip) { + if (control == null || tooltip == null || tooltip.length() == 0) { + return; + } + + // Some kinds of controls already properly implement tooltip display. + if (control instanceof Button) { + control.setToolTipText(tooltip); + return; + } + + control.setToolTipText(null); + + final DefaultInformationControl ic = new DefaultInformationControl(control.getShell()); + ic.setInformation(tooltip); + Point sz = ic.computeSizeHint(); + ic.setSize(sz.x, sz.y); + ic.setVisible(false); // initially hidden + + control.addMouseTrackListener(new MouseTrackListener() { + public void mouseEnter(MouseEvent e) { + } + + public void mouseExit(MouseEvent e) { + ic.setVisible(false); + } + + public void mouseHover(MouseEvent e) { + ic.setLocation(control.toDisplay(10, 25)); // same offset as in PDETextHover + ic.setVisible(true); + } + }); + } + + /** + * Creates a FormText widget. + * + * This expects the parent composite to have a TableWrapLayout with 2 columns. + * + * @param parent The parent (e.g. composite from CreateTableLayout()) + * @param toolkit Form Toolkit + * @param isHtml True if the form text will contain HTML that must be interpreted as + * rich text (i.e. parse tags & expand URLs). + * @param label The string for the label. + * @param setupLayoutData indicates whether the created form text receives a TableWrapData + * through the setLayoutData method. In some case, creating it will make the table parent + * huge, which we don't want. + * @return The new created FormText. + */ + static public FormText createFormText(Composite parent, FormToolkit toolkit, + boolean isHtml, String label, boolean setupLayoutData) { + FormText text = toolkit.createFormText(parent, true /* track focus */); + if (setupLayoutData) { + TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); + twd.maxWidth = AndroidEditor.TEXT_WIDTH_HINT; + if (parent.getLayout() instanceof TableWrapLayout) { + twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; + } + text.setLayoutData(twd); + } + text.setWhitespaceNormalized(true); + text.setText(label, isHtml /* parseTags */, isHtml /* expandURLs */); + return text; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java new file mode 100644 index 0000000..2fe5783 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java @@ -0,0 +1,43 @@ +/* + * 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.editors.ui; + +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; + +import org.eclipse.jface.viewers.TextCellEditor; +import org.eclipse.swt.widgets.Composite; + +/** + * TextCellEditor able to receive a {@link UiAttributeNode} in the {@link #setValue(Object)} + * method. + */ +public class TextValueCellEditor extends TextCellEditor { + + public TextValueCellEditor(Composite parent) { + super(parent); + } + + @Override + protected void doSetValue(Object value) { + if (value instanceof UiAttributeNode) { + super.doSetValue(((UiAttributeNode)value).getCurrentValue()); + return; + } + + super.doSetValue(value); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/UiElementPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/UiElementPart.java new file mode 100644 index 0000000..66773bd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/UiElementPart.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2007 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.editors.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.manifest.ManifestEditor; +import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +/** + * Generic page's section part that displays all attributes of a given {@link UiElementNode}. + *

          + * This part is designed to be displayed in a page that has a table column layout. + * It is linked to a specific {@link UiElementNode} and automatically displays all of its + * attributes, manages its dirty state and commits the attributes when necessary. + *

          + * No derivation is needed unless the UI or workflow needs to be extended. + */ +public class UiElementPart extends ManifestSectionPart { + + /** A reference to the container editor */ + private ManifestEditor mEditor; + /** The {@link UiElementNode} manipulated by this SectionPart. It can be null. */ + private UiElementNode mUiElementNode; + /** Table that contains all the attributes */ + private Composite mTable; + + public UiElementPart(Composite body, FormToolkit toolkit, ManifestEditor editor, + UiElementNode uiElementNode, String sectionTitle, String sectionDescription, + int extra_style) { + super(body, toolkit, extra_style, sectionDescription != null); + mEditor = editor; + mUiElementNode = uiElementNode; + setupSection(sectionTitle, sectionDescription); + + if (uiElementNode == null) { + // This is serious and should never happen. Instead of crashing, simply abort. + // There will be no UI, which will prevent further damage. + AdtPlugin.log(IStatus.ERROR, "Missing node to edit!"); //$NON-NLS-1$ + return; + } + } + + /** + * Returns the Editor associated with this part. + */ + public ManifestEditor getEditor() { + return mEditor; + } + + /** + * Returns the {@link UiElementNode} associated with this part. + */ + public UiElementNode getUiElementNode() { + return mUiElementNode; + } + + /** + * Changes the element node handled by this part. + * + * @param uiElementNode The new element node for the part. + */ + public void setUiElementNode(UiElementNode uiElementNode) { + mUiElementNode = uiElementNode; + } + + /** + * Initializes the form part. + *

          + * This is called by the owning managed form as soon as the part is added to the form, + * which happens right after the part is actually created. + */ + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + createFormControls(form); + } + + /** + * Setup the section that contains this part. + *

          + * This is called by the constructor to set the section's title and description + * with parameters given in the constructor. + *
          + * Derived class override this if needed, however in most cases the default + * implementation should be enough. + * + * @param sectionTitle The section part's title + * @param sectionDescription The section part's description + */ + protected void setupSection(String sectionTitle, String sectionDescription) { + Section section = getSection(); + section.setText(sectionTitle); + section.setDescription(sectionDescription); + } + + /** + * Create the controls to edit the attributes for the given ElementDescriptor. + *

          + * This MUST not be called by the constructor. Instead it must be called from + * initialize (i.e. right after the form part is added to the managed form.) + *

          + * Derived classes can override this if necessary. + * + * @param managedForm The owner managed form + */ + protected void createFormControls(IManagedForm managedForm) { + setTable(createTableLayout(managedForm.getToolkit(), 2 /* numColumns */)); + + createUiAttributes(managedForm); + } + + /** + * Sets the table where the attribute UI needs to be created. + */ + protected void setTable(Composite table) { + mTable = table; + } + + /** + * Returns the table where the attribute UI needs to be created. + */ + protected Composite getTable() { + return mTable; + } + + /** + * Add all the attribute UI widgets into the underlying table layout. + * + * @param managedForm The owner managed form + */ + protected void createUiAttributes(IManagedForm managedForm) { + Composite table = getTable(); + if (table == null || managedForm == null) { + return; + } + + // Remove any old UI controls + for (Control c : table.getChildren()) { + c.dispose(); + } + + fillTable(table, managedForm); + + // Tell the section that the layout has changed. + layoutChanged(); + } + + /** + * Actually fills the table. + * This is called by {@link #createUiAttributes(IManagedForm)} to populate the new + * table. The default implementation is to use + * {@link #insertUiAttributes(UiElementNode, Composite, IManagedForm)} to actually + * place the attributes of the default {@link UiElementNode} in the table. + *

          + * Derived classes can override this to add controls in the table before or after. + * + * @param table The table to fill. It must have 2 columns. + * @param managedForm The managed form for new controls. + */ + protected void fillTable(Composite table, IManagedForm managedForm) { + int inserted = insertUiAttributes(mUiElementNode, table, managedForm); + + if (inserted == 0) { + createLabel(table, managedForm.getToolkit(), + "No attributes to display, waiting for SDK to finish loading...", + null /* tooltip */ ); + } + } + + /** + * Insert the UI attributes of the given {@link UiElementNode} in the given table. + * + * @param uiNode The {@link UiElementNode} that contains the attributes to display. + * Must not be null. + * @param table The table to fill. It must have 2 columns. + * @param managedForm The managed form for new controls. + * @return The number of UI attributes inserted. It is >= 0. + */ + protected int insertUiAttributes(UiElementNode uiNode, Composite table, IManagedForm managedForm) { + if (uiNode == null || table == null || managedForm == null) { + return 0; + } + + // To iterate over all attributes, we use the {@link ElementDescriptor} instead + // of the {@link UiElementNode} because the attributes' order is guaranteed in the + // descriptor but not in the node itself. + AttributeDescriptor[] attr_desc_list = uiNode.getAttributeDescriptors(); + for (AttributeDescriptor attr_desc : attr_desc_list) { + if (attr_desc instanceof XmlnsAttributeDescriptor) { + // Do not show hidden attributes + continue; + } + + UiAttributeNode ui_attr = uiNode.findUiAttribute(attr_desc); + if (ui_attr != null) { + ui_attr.createUiControl(table, managedForm); + } else { + // The XML has an extra attribute which wasn't declared in + // AndroidManifestDescriptors. This is not a problem, we just ignore it. + AdtPlugin.log(IStatus.WARNING, + "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ + attr_desc.getXmlLocalName(), + uiNode.getDescriptor().getXmlName()); + } + } + return attr_desc_list.length; + } + + /** + * Tests whether the part is dirty i.e. its widgets have state that is + * newer than the data in the model. + *

          + * This is done by iterating over all attributes and updating the super's + * internal dirty flag. Stop once at least one attribute is dirty. + * + * @return true if the part is dirty, false + * otherwise. + */ + @Override + public boolean isDirty() { + if (mUiElementNode != null && !super.isDirty()) { + for (UiAttributeNode ui_attr : mUiElementNode.getUiAttributes()) { + if (ui_attr.isDirty()) { + markDirty(); + break; + } + } + } + return super.isDirty(); + } + + /** + * If part is displaying information loaded from a model, this method + * instructs it to commit the new (modified) data back into the model. + * + * @param onSave + * indicates if commit is called during 'save' operation or for + * some other reason (for example, if form is contained in a + * wizard or a multi-page editor and the user is about to leave + * the page). + */ + @Override + public void commit(boolean onSave) { + if (mUiElementNode != null) { + mEditor.editXmlModel(new Runnable() { + public void run() { + for (UiAttributeNode ui_attr : mUiElementNode.getUiAttributes()) { + ui_attr.commit(); + } + } + }); + } + + // We need to call super's commit after we synchronized the nodes to make sure we + // reset the dirty flag after all the side effects from committing have occurred. + super.commit(onSave); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java new file mode 100644 index 0000000..2aad217 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java @@ -0,0 +1,220 @@ +/* + * 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.apache.xml.serialize.Method; +import org.apache.xml.serialize.OutputFormat; +import org.apache.xml.serialize.XMLSerializer; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +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.document.NodeContainer; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + + +/** + * Provides Cut and Copy actions for the tree nodes. + */ +public class CopyCutAction extends Action { + private List mUiNodes; + private boolean mPerformCut; + private final AndroidEditor mEditor; + private final Clipboard mClipboard; + private final ICommitXml mXmlCommit; + + /** + * Creates a new Copy or Cut action. + * + * @param selected The UI node to cut or copy. It *must* have a non-null XML node. + * @param perform_cut True if the operation is cut, false if it is copy. + */ + public CopyCutAction(AndroidEditor editor, Clipboard clipboard, ICommitXml xmlCommit, + UiElementNode selected, boolean perform_cut) { + this(editor, clipboard, xmlCommit, toList(selected), perform_cut); + } + + /** + * Creates a new Copy or Cut action. + * + * @param selected The UI nodes to cut or copy. They *must* have a non-null XML node. + * The list becomes owned by the {@link CopyCutAction}. + * @param perform_cut True if the operation is cut, false if it is copy. + */ + public CopyCutAction(AndroidEditor editor, Clipboard clipboard, ICommitXml xmlCommit, + List selected, boolean perform_cut) { + super(perform_cut ? "Cut" : "Copy"); + mEditor = editor; + mClipboard = clipboard; + mXmlCommit = xmlCommit; + + ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); + if (perform_cut) { + setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT)); + setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT)); + setDisabledImageDescriptor( + images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED)); + } else { + setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY)); + setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY)); + setDisabledImageDescriptor( + images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED)); + } + + mUiNodes = selected; + mPerformCut = perform_cut; + } + + /** + * Performs the cut or copy action. + * First an XML serializer is used to turn the existing XML node into a valid + * XML fragment, which is added as text to the clipboard. + */ + @Override + public void run() { + super.run(); + if (mUiNodes == null || mUiNodes.size() < 1) { + return; + } + + // Commit the current pages first, to make sure the XML is in sync. + // Committing may change the XML structure. + if (mXmlCommit != null) { + mXmlCommit.commitPendingXmlChanges(); + } + + StringBuilder allText = new StringBuilder(); + ArrayList nodesToCut = mPerformCut ? new ArrayList() : null; + + for (UiElementNode uiNode : mUiNodes) { + try { + Node xml_node = uiNode.getXmlNode(); + if (xml_node == null) { + return; + } + + String data = getXmlTextFromEditor(xml_node); + + // In the unlikely event that IStructuredDocument failed to extract the text + // directly from the editor, try to fall back on a direct XML serialization + // of the XML node. This uses the generic Node interface with no SSE tricks. + if (data == null) { + data = getXmlTextFromSerialization(xml_node); + } + + if (data != null) { + allText.append(data); + if (mPerformCut) { + // only remove notes to cut if we actually got some XML text from them + nodesToCut.add(uiNode); + } + } + + } catch (Exception e) { + AdtPlugin.log(e, "CopyCutAction failed for UI node %1$s", //$NON-NLS-1$ + uiNode.getBreadcrumbTrailDescription(true)); + } + } // for uiNode + + if (allText != null && allText.length() > 0) { + mClipboard.setContents( + new Object[] { allText.toString() }, + new Transfer[] { TextTransfer.getInstance() }); + if (mPerformCut) { + for (UiElementNode uiNode : nodesToCut) { + uiNode.deleteXmlNode(); + } + } + } + } + + /** Get the data directly from the editor. */ + private String getXmlTextFromEditor(Node xml_node) { + String data = null; + IStructuredModel model = mEditor.getModelForRead(); + try { + IStructuredDocument sse_doc = mEditor.getStructuredDocument(); + if (xml_node instanceof NodeContainer) { + // The easy way to get the source of an SSE XML node. + data = ((NodeContainer) xml_node).getSource(); + } else if (xml_node instanceof IndexedRegion && sse_doc != null) { + // Try harder. + IndexedRegion region = (IndexedRegion) xml_node; + int start = region.getStartOffset(); + int end = region.getEndOffset(); + + if (end > start) { + data = sse_doc.get(start, end - start); + } + } + } catch (BadLocationException e) { + // the region offset was invalid. ignore. + } finally { + model.releaseFromRead(); + } + return data; + } + + /** + * Direct XML serialization of the XML node. + *

          + * This uses the generic Node interface with no SSE tricks. It's however slower + * and doesn't respect formatting (since serialization is involved instead of reading + * the actual text buffer.) + */ + private String getXmlTextFromSerialization(Node xml_node) throws IOException { + String data; + StringWriter sw = new StringWriter(); + XMLSerializer serializer = new XMLSerializer(sw, + new OutputFormat(Method.XML, + OutputFormat.Defaults.Encoding /* utf-8 */, + true /* indent */)); + // Serialize will throw an IOException if it fails. + serializer.serialize((Element) xml_node); + data = sw.toString(); + return data; + } + + /** + * Static helper class to wrap on node into a list for the constructors. + */ + private static ArrayList toList(UiElementNode selected) { + ArrayList list = null; + if (selected != null) { + list = new ArrayList(1); + list.add(selected); + } + return list; + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java new file mode 100644 index 0000000..8b6aa0e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.editors.ui.tree; + +/** + * Interface for an object that can commit its changes to the underlying XML model + */ +public interface ICommitXml { + + /** Commits pending data to the underlying XML model. */ + public void commitPendingXmlChanges(); + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java new file mode 100644 index 0000000..772fb52 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2007 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; +import org.eclipse.ui.dialogs.ISelectionStatusValidator; + +import java.util.Arrays; + +/** + * A selection dialog to select the type of the new element node to + * created, either in the application node or the selected sub node. + */ +public class NewItemSelectionDialog extends AbstractElementListSelectionDialog { + + /** The UI node selected in the tree view before creating the new item selection dialog. + * Can be null -- which means new items must be created in the root_node. */ + private UiElementNode mSelectedUiNode; + /** The root node chosen by the user, either root_node or the one passed + * to the constructor if not null */ + private UiElementNode mChosenRootNode; + private UiElementNode mLocalRootNode; + /** The descriptor of the elements to be displayed as root in this tree view. All elements + * of the same type in the root will be displayed. */ + private ElementDescriptor[] mDescriptorFilters; + + /** + * Creates the new item selection dialog. + * + * @param shell The parent shell for the list. + * @param labelProvider ILabelProvider for the list. + * @param descriptorFilters The element allows at the root of the tree + * @param ui_node The selected node, or null if none is selected. + * @param root_node The root of the Ui Tree, either the UiDocumentNode or a sub-node. + */ + public NewItemSelectionDialog(Shell shell, ILabelProvider labelProvider, + ElementDescriptor[] descriptorFilters, + UiElementNode ui_node, + UiElementNode root_node) { + super(shell, labelProvider); + mDescriptorFilters = descriptorFilters; + mLocalRootNode = root_node; + + // Only accept the UI node if it is not the UI root node and it can have children. + // If the node cannot have children, select its parent as a potential target. + if (ui_node != null && ui_node != mLocalRootNode) { + if (ui_node.getDescriptor().hasChildren()) { + mSelectedUiNode = ui_node; + } else { + UiElementNode parent = ui_node.getUiParent(); + if (parent != null && parent != mLocalRootNode) { + mSelectedUiNode = parent; + } + } + } + + setHelpAvailable(false); + setMultipleSelection(false); + + setValidator(new ISelectionStatusValidator() { + public IStatus validate(Object[] selection) { + if (selection.length == 1 && selection[0] instanceof ViewElementDescriptor) { + return new Status(IStatus.OK, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.OK, // code + ((ViewElementDescriptor) selection[0]).getCanonicalClassName(), //msg + null); // exception + } else if (selection.length == 1 && selection[0] instanceof ElementDescriptor) { + return new Status(IStatus.OK, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.OK, // code + "", //$NON-NLS-1$ // msg + null); // exception + } else { + return new Status(IStatus.ERROR, // severity + AdtPlugin.PLUGIN_ID, //plugin id + IStatus.ERROR, // code + "Invalid selection", // msg, translatable + null); // exception + } + } + }); + } + + /** + * @return The root node selected by the user, either root node or the + * one passed to the constructor if not null. + */ + public UiElementNode getChosenRootNode() { + return mChosenRootNode; + } + + /** + * Internal helper to compute the result. Returns the selection from + * the list view, if any. + */ + @Override + protected void computeResult() { + setResult(Arrays.asList(getSelectedElements())); + } + + /** + * Creates the dialog area. + * + * First add a radio area, which may be either 2 radio controls or + * just a message area if there's only one choice (the app root node). + * + * Then uses the default from the AbstractElementListSelectionDialog + * which is to add both a filter text and a filtered list. Adding both + * is necessary (since the base class accesses both internal directly + * fields without checking for null pointers.) + * + * Finally sets the initial selection list. + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite contents = (Composite) super.createDialogArea(parent); + + createRadioControl(contents); + createFilterText(contents); + createFilteredList(contents); + + // Initialize the list state. + // This must be done after the filtered list as been created. + chooseNode(mChosenRootNode); + setSelection(getInitialElementSelections().toArray()); + return contents; + } + + /** + * Creates the message text widget and sets layout data. + * @param content the parent composite of the message area. + */ + private Composite createRadioControl(Composite content) { + + if (mSelectedUiNode != null) { + Button radio1 = new Button(content, SWT.RADIO); + radio1.setText(String.format("Create a new element at the top level, in %1$s.", + mLocalRootNode.getShortDescription())); + + Button radio2 = new Button(content, SWT.RADIO); + radio2.setText(String.format("Create a new element in the selected element, %1$s.", + mSelectedUiNode.getBreadcrumbTrailDescription(false /* include_root */))); + + // Set the initial selection before adding the listeners + // (they can't be run till the filtered list has been created) + radio1.setSelection(false); + radio2.setSelection(true); + mChosenRootNode = mSelectedUiNode; + + radio1.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + chooseNode(mLocalRootNode); + } + }); + + radio2.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + chooseNode(mSelectedUiNode); + } + }); + } else { + setMessage(String.format("Create a new element at the top level, in %1$s.", + mLocalRootNode.getShortDescription())); + createMessageArea(content); + + mChosenRootNode = mLocalRootNode; + } + + return content; + } + + /** + * Internal helper to remember the root node choosen by the user. + * It also sets the list view to the adequate list of children that can + * be added to the chosen root node. + * + * If the chosen root node is mLocalRootNode and a descriptor filter was specified + * when creating the master-detail part, we use this as the set of nodes that + * can be created on the root node. + * + * @param ui_node The chosen root node, either mLocalRootNode or + * mSelectedUiNode. + */ + private void chooseNode(UiElementNode ui_node) { + mChosenRootNode = ui_node; + + if (ui_node == mLocalRootNode && + mDescriptorFilters != null && + mDescriptorFilters.length != 0) { + setListElements(mDescriptorFilters); + } else { + setListElements(ui_node.getDescriptor().getChildren()); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java new file mode 100644 index 0000000..8bb4ad2 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java @@ -0,0 +1,124 @@ +/* + * 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +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.sse.core.internal.provisional.text.IStructuredDocumentRegion; +import org.eclipse.wst.xml.core.internal.document.NodeContainer; +import org.w3c.dom.Node; + + +/** + * Provides Paste operation for the tree nodes + */ +public class PasteAction extends Action { + private UiElementNode mUiNode; + private final AndroidEditor mEditor; + private final Clipboard mClipboard; + + public PasteAction(AndroidEditor editor, Clipboard clipboard, UiElementNode ui_node) { + super("Paste"); + mEditor = editor; + mClipboard = clipboard; + + ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); + setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE)); + setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE)); + setDisabledImageDescriptor( + images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE_DISABLED)); + + mUiNode = ui_node; + } + + /** + * Performs the paste operation. + */ + @Override + public void run() { + super.run(); + + final String data = (String) mClipboard.getContents(TextTransfer.getInstance()); + if (data != null) { + IStructuredModel model = mEditor.getModelForEdit(); + try { + IStructuredDocument sse_doc = mEditor.getStructuredDocument(); + if (sse_doc != null) { + if (mUiNode.getDescriptor().hasChildren()) { + // This UI Node can have children. The new XML is + // inserted as the first child. + + if (mUiNode.getUiChildren().size() > 0) { + // There's already at least one child, so insert right before it. + Node xml_node = mUiNode.getUiChildren().get(0).getXmlNode(); + if (xml_node instanceof IndexedRegion) { // implies xml_node != null + IndexedRegion region = (IndexedRegion) xml_node; + sse_doc.replace(region.getStartOffset(), 0, data); + return; // we're done, no need to try the other cases + } + } + + // If there's no first XML node child. Create one by + // inserting at the end of the *start* tag. + Node xml_node = mUiNode.getXmlNode(); + if (xml_node instanceof NodeContainer) { + NodeContainer container = (NodeContainer) xml_node; + IStructuredDocumentRegion start_tag = + container.getStartStructuredDocumentRegion(); + if (start_tag != null) { + sse_doc.replace(start_tag.getEndOffset(), 0, data); + return; // we're done, no need to try the other case + } + } + } + + // This UI Node doesn't accept children. The new XML is inserted as the + // next sibling. This also serves as a fallback if all the previous + // attempts failed. However, this is not possible if the current node + // has for parent a document -- an XML document can only have one root, + // with no siblings. + if (!(mUiNode.getUiParent() instanceof UiDocumentNode)) { + Node xml_node = mUiNode.getXmlNode(); + if (xml_node instanceof IndexedRegion) { + IndexedRegion region = (IndexedRegion) xml_node; + sse_doc.replace(region.getEndOffset(), 0, data); + } + } + } + + } catch (BadLocationException e) { + AdtPlugin.log(e, "ParseAction failed for UI Node %2$s, content '%1$s'", //$NON-NLS-1$ + mUiNode.getBreadcrumbTrailDescription(true), data); + } finally { + model.releaseFromEdit(); + } + } + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java new file mode 100644 index 0000000..21180b1 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.editors.ui.tree; + +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.widgets.Shell; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.util.List; + +/** + * Performs basic actions on an XML tree: add node, remove node, move up/down. + */ +public abstract class UiActions implements ICommitXml { + + public UiActions() { + } + + //--------------------- + // Actual implementations must override these to provide specific hooks + + /** Returns the UiDocumentNode for the current model. */ + abstract protected UiElementNode getRootNode(); + + /** Commits pending data before the XML model is modified. */ + abstract public void commitPendingXmlChanges(); + + /** + * Utility method to select an outline item based on its model node + * + * @param uiNode The node to select. Can be null (in which case nothing should happen) + */ + abstract protected void selectUiNode(UiElementNode uiNode); + + //--------------------- + + /** + * Called when the "Add..." button next to the tree view is selected. + *

          + * This simplified version of doAdd does not support descriptor filters and creates + * a new {@link UiModelTreeLabelProvider} for each call. + */ + public void doAdd(UiElementNode uiNode, Shell shell) { + doAdd(uiNode, null /* descriptorFilters */, shell, new UiModelTreeLabelProvider()); + } + + /** + * Called when the "Add..." button next to the tree view is selected. + * + * Displays a selection dialog that lets the user select which kind of node + * to create, depending on the current selection. + */ + public void doAdd(UiElementNode uiNode, + ElementDescriptor[] descriptorFilters, + Shell shell, ILabelProvider labelProvider) { + // If the root node is a document with already a root, use it as the root node + UiElementNode rootNode = getRootNode(); + if (rootNode instanceof UiDocumentNode && rootNode.getUiChildren().size() > 0) { + rootNode = rootNode.getUiChildren().get(0); + } + + NewItemSelectionDialog dlg = new NewItemSelectionDialog( + shell, + labelProvider, + descriptorFilters, + uiNode, rootNode); + dlg.open(); + Object[] results = dlg.getResult(); + if (results != null && results.length > 0) { + addElement(dlg.getChosenRootNode(), null, (ElementDescriptor) results[0], + true /*updateLayout*/); + } + } + + /** + * Adds a new XML element based on the {@link ElementDescriptor} to the given parent + * {@link UiElementNode}, and then select it. + *

          + * If the parent is a document root which already contains a root element, the inner + * root element is used as the actual parent. This ensure you can't create a broken + * XML file with more than one root element. + *

          + * If a sibling is given and that sibling has the same parent, the new node is added + * right after that sibling. Otherwise the new node is added at the end of the parent + * child list. + * + * @param uiParent An existing UI node or null to add to the tree root + * @param uiSibling An existing UI node before which to insert the new node. Can be null. + * @param descriptor The descriptor of the element to add + * @param updateLayout True if layout attributes should be set + * @return The new {@link UiElementNode} or null. + */ + public UiElementNode addElement(UiElementNode uiParent, + UiElementNode uiSibling, + ElementDescriptor descriptor, + boolean updateLayout) { + if (uiParent instanceof UiDocumentNode && uiParent.getUiChildren().size() > 0) { + uiParent = uiParent.getUiChildren().get(0); + } + if (uiSibling != null && uiSibling.getUiParent() != uiParent) { + uiSibling = null; + } + + UiElementNode uiNew = addNewTreeElement(uiParent, uiSibling, descriptor, updateLayout); + selectUiNode(uiNew); + + return uiNew; + } + + /** + * Called when the "Remove" button is selected. + * + * If the tree has a selection, remove it. + * This simply deletes the XML node attached to the UI node: when the XML model fires the + * update event, the tree will get refreshed. + */ + public void doRemove(final List nodes, Shell shell) { + + if (nodes == null || nodes.size() == 0) { + return; + } + + final int len = nodes.size(); + + StringBuilder sb = new StringBuilder(); + for (UiElementNode node : nodes) { + sb.append("\n- "); //$NON-NLS-1$ + sb.append(node.getBreadcrumbTrailDescription(false /* include_root */)); + } + + if (MessageDialog.openQuestion(shell, + len > 1 ? "Remove elements from Android XML" // title + : "Remove element from Android XML", + String.format("Do you really want to remove %1$s?", sb.toString()))) { + commitPendingXmlChanges(); + getRootNode().getEditor().editXmlModel(new Runnable() { + public void run() { + UiElementNode previous = null; + UiElementNode parent = null; + + for (int i = len - 1; i >= 0; i--) { + UiElementNode node = nodes.get(i); + previous = node.getUiPreviousSibling(); + parent = node.getUiParent(); + + // delete node + node.deleteXmlNode(); + } + + // try to select the last previous sibling or the last parent + if (previous != null) { + selectUiNode(previous); + } else if (parent != null) { + selectUiNode(parent); + } + } + }); + } + } + + /** + * Called when the "Up" button is selected. + *

          + * If the tree has a selection, move it up, either in the child list or as the last child + * of the previous parent. + */ + public void doUp(final List nodes) { + if (nodes == null || nodes.size() < 1) { + return; + } + + final Node[] select_xml_node = { null }; + UiElementNode last_node = null; + UiElementNode search_root = null; + + for (int i = 0; i < nodes.size(); i++) { + final UiElementNode node = last_node = nodes.get(i); + + // the node will move either up to its parent or grand-parent + search_root = node.getUiParent(); + if (search_root != null && search_root.getUiParent() != null) { + search_root = search_root.getUiParent(); + } + + commitPendingXmlChanges(); + getRootNode().getEditor().editXmlModel(new Runnable() { + public void run() { + Node xml_node = node.getXmlNode(); + if (xml_node != null) { + Node xml_parent = xml_node.getParentNode(); + if (xml_parent != null) { + UiElementNode ui_prev = node.getUiPreviousSibling(); + if (ui_prev != null && ui_prev.getXmlNode() != null) { + // This node is not the first one of the parent, so it can be + // removed and then inserted before its previous sibling. + // If the previous sibling can have children, though, then it + // is inserted at the end of the children list. + Node xml_prev = ui_prev.getXmlNode(); + if (ui_prev.getDescriptor().hasChildren()) { + xml_prev.appendChild(xml_parent.removeChild(xml_node)); + select_xml_node[0] = xml_node; + } else { + xml_parent.insertBefore( + xml_parent.removeChild(xml_node), + xml_prev); + select_xml_node[0] = xml_node; + } + } else if (!(xml_parent instanceof Document) && + xml_parent.getParentNode() != null && + !(xml_parent.getParentNode() instanceof Document)) { + // If the node is the first one of the child list of its + // parent, move it up in the hierarchy as previous sibling + // to the parent. This is only possible if the parent of the + // parent is not a document. + Node grand_parent = xml_parent.getParentNode(); + grand_parent.insertBefore(xml_parent.removeChild(xml_node), + xml_parent); + select_xml_node[0] = xml_node; + } + } + } + } + }); + } + + if (select_xml_node[0] == null) { + // The XML node has not been moved, we can just select the same UI node + selectUiNode(last_node); + } else { + // The XML node has moved. At this point the UI model has been reloaded + // and the XML node has been affected to a new UI node. Find that new UI + // node and select it. + if (search_root == null) { + search_root = last_node.getUiRoot(); + } + if (search_root != null) { + selectUiNode(search_root.findXmlNode(select_xml_node[0])); + } + } + } + + /** + * Called when the "Down" button is selected. + * + * If the tree has a selection, move it down, either in the same child list or as the + * first child of the next parent. + */ + public void doDown(final List nodes) { + if (nodes == null || nodes.size() < 1) { + return; + } + + final Node[] select_xml_node = { null }; + UiElementNode last_node = null; + UiElementNode search_root = null; + + for (int i = nodes.size() - 1; i >= 0; i--) { + final UiElementNode node = last_node = nodes.get(i); + // the node will move either down to its parent or grand-parent + search_root = node.getUiParent(); + if (search_root != null && search_root.getUiParent() != null) { + search_root = search_root.getUiParent(); + } + + commitPendingXmlChanges(); + getRootNode().getEditor().editXmlModel(new Runnable() { + public void run() { + Node xml_node = node.getXmlNode(); + if (xml_node != null) { + Node xml_parent = xml_node.getParentNode(); + if (xml_parent != null) { + UiElementNode uiNext = node.getUiNextSibling(); + if (uiNext != null && uiNext.getXmlNode() != null) { + // This node is not the last one of the parent, so it can be + // removed and then inserted after its next sibling. + // If the next sibling is a node that can have children, though, + // then the node is inserted as the first child. + Node xml_next = uiNext.getXmlNode(); + if (uiNext.getDescriptor().hasChildren()) { + // Note: insertBefore works as append if the ref node is + // null, i.e. when the node doesn't have children yet. + xml_next.insertBefore(xml_parent.removeChild(xml_node), + xml_next.getFirstChild()); + select_xml_node[0] = xml_node; + } else { + // Insert "before after next" ;-) + xml_parent.insertBefore(xml_parent.removeChild(xml_node), + xml_next.getNextSibling()); + select_xml_node[0] = xml_node; + } + } else if (!(xml_parent instanceof Document) && + xml_parent.getParentNode() != null && + !(xml_parent.getParentNode() instanceof Document)) { + // This node is the last node of its parent. + // If neither the parent nor the grandparent is a document, + // then the node can be insert right after the parent. + Node grand_parent = xml_parent.getParentNode(); + grand_parent.insertBefore(xml_parent.removeChild(xml_node), + xml_parent.getNextSibling()); + select_xml_node[0] = xml_node; + } + } + } + } + }); + } + + if (select_xml_node[0] == null) { + // The XML node has not been moved, we can just select the same UI node + selectUiNode(last_node); + } else { + // The XML node has moved. At this point the UI model has been reloaded + // and the XML node has been affected to a new UI node. Find that new UI + // node and select it. + if (search_root == null) { + search_root = last_node.getUiRoot(); + } + if (search_root != null) { + selectUiNode(search_root.findXmlNode(select_xml_node[0])); + } + } + } + + //--------------------- + + /** + * Adds a new element of the given descriptor's type to the given UI parent node. + * + * This actually creates the corresponding XML node in the XML model, which in turn + * will refresh the current tree view. + * + * @param uiParent An existing UI node or null to add to the tree root + * @param uiSibling An existing UI node to insert right before. Can be null. + * @param descriptor The descriptor of the element to add + * @param updateLayout True if layout attributes should be set + * @return The {@link UiElementNode} that has been added to the UI tree. + */ + private UiElementNode addNewTreeElement(UiElementNode uiParent, + final UiElementNode uiSibling, + ElementDescriptor descriptor, + final boolean updateLayout) { + commitPendingXmlChanges(); + + int index = 0; + for (UiElementNode uiChild : uiParent.getUiChildren()) { + if (uiChild == uiSibling) { + break; + } + index++; + } + + final UiElementNode uiNew = uiParent.insertNewUiChild(index, descriptor); + UiElementNode rootNode = getRootNode(); + + rootNode.getEditor().editXmlModel(new Runnable() { + public void run() { + DescriptorsUtils.setDefaultLayoutAttributes(uiNew, updateLayout); + Node xmlNode = uiNew.createXmlNode(); + } + }); + return uiNew; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java new file mode 100644 index 0000000..15c67c3 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2007 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; +import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; +import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.IDetailsPage; +import org.eclipse.ui.forms.IFormPart; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.events.ExpansionEvent; +import org.eclipse.ui.forms.events.IExpansionListener; +import org.eclipse.ui.forms.widgets.FormText; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; +import org.eclipse.ui.forms.widgets.SharedScrolledComposite; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; +import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; + +import java.util.Collection; +import java.util.HashSet; + +/** + * Details page for the {@link UiElementNode} nodes in the tree view. + *

          + * See IDetailsBase for more details. + */ +class UiElementDetail implements IDetailsPage { + + /** The master-detail part, composed of a main tree and an auxiliary detail part */ + private ManifestSectionPart mMasterPart; + + private Section mMasterSection; + private UiElementNode mCurrentUiElementNode; + private Composite mCurrentTable; + private boolean mIsDirty; + + private IManagedForm mManagedForm; + + private final UiTreeBlock mTree; + + public UiElementDetail(UiTreeBlock tree) { + mTree = tree; + mMasterPart = mTree.getMasterPart(); + mManagedForm = mMasterPart.getManagedForm(); + } + + /* (non-java doc) + * Initializes the part. + */ + public void initialize(IManagedForm form) { + mManagedForm = form; + } + + /* (non-java doc) + * Creates the contents of the page in the provided parent. + */ + public void createContents(Composite parent) { + mMasterSection = createMasterSection(parent); + } + + /* (non-java doc) + * Called when the provided part has changed selection state. + *

          + * Only reply when our master part originates the selection. + */ + public void selectionChanged(IFormPart part, ISelection selection) { + if (part == mMasterPart && + !selection.isEmpty() && + selection instanceof ITreeSelection) { + ITreeSelection tree_selection = (ITreeSelection) selection; + + Object first = tree_selection.getFirstElement(); + if (first instanceof UiElementNode) { + UiElementNode ui_node = (UiElementNode) first; + createUiAttributeControls(mManagedForm, ui_node); + } + } + } + + /* (non-java doc) + * Instructs it to commit the new (modified) data back into the model. + */ + public void commit(boolean onSave) { + + IStructuredModel model = mTree.getEditor().getModelForEdit(); + try { + // Notify the model we're about to change data... + model.aboutToChangeModel(); + + if (mCurrentUiElementNode != null) { + mCurrentUiElementNode.commit(); + } + + // Finally reset the dirty flag if everything was saved properly + mIsDirty = false; + } catch (Exception e) { + AdtPlugin.log(e, "Detail node failed to commit XML attribute!"); //$NON-NLS-1$ + } finally { + // Notify the model we're done modifying it. This must *always* be executed. + model.changedModel(); + model.releaseFromEdit(); + } + } + + public void dispose() { + // pass + } + + + /* (non-java doc) + * Returns true if the part has been modified with respect to the data + * loaded from the model. + */ + public boolean isDirty() { + if (mCurrentUiElementNode != null && mCurrentUiElementNode.isDirty()) { + markDirty(); + } + return mIsDirty; + } + + public boolean isStale() { + // pass + return false; + } + + /** + * Called by the master part when the tree is refreshed after the framework resources + * have been reloaded. + */ + public void refresh() { + if (mCurrentTable != null) { + mCurrentTable.dispose(); + mCurrentTable = null; + } + mCurrentUiElementNode = null; + mMasterSection.getParent().pack(true /* changed */); + } + + public void setFocus() { + // pass + } + + public boolean setFormInput(Object input) { + // pass + return false; + } + + /** + * Creates a TableWrapLayout in the DetailsPage, which in turns contains a Section. + * + * All the UI should be created in a layout which parent is the mSection itself. + * The hierarchy is: + *

          +     * DetailPage
          +     * + TableWrapLayout
          +     *   + Section (with title/description && fill_grab horizontal)
          +     *     + TableWrapLayout [*]
          +     *       + Labels/Forms/etc... [*]
          +     * 
          + * Both items marked with [*] are created by the derived classes to fit their needs. + * + * @param parent Parent of the mSection (from createContents) + * @return The new Section + */ + private Section createMasterSection(Composite parent) { + TableWrapLayout layout = new TableWrapLayout(); + layout.topMargin = 0; + parent.setLayout(layout); + + FormToolkit toolkit = mManagedForm.getToolkit(); + Section section = toolkit.createSection(parent, Section.TITLE_BAR); + section.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP)); + return section; + } + + /** + * Create the ui attribute controls to edit the attributes for the given + * ElementDescriptor. + *

          + * This is called by the constructor. + * Derived classes can override this if necessary. + * + * @param managedForm The managed form + */ + private void createUiAttributeControls( + final IManagedForm managedForm, + final UiElementNode ui_node) { + + final ElementDescriptor elem_desc = ui_node.getDescriptor(); + mMasterSection.setText(String.format("Attributes for %1$s", ui_node.getShortDescription())); + + if (mCurrentUiElementNode != ui_node) { + // Before changing the table, commit all dirty state. + if (mIsDirty) { + commit(false); + } + if (mCurrentTable != null) { + mCurrentTable.dispose(); + mCurrentTable = null; + } + + // To iterate over all attributes, we use the {@link ElementDescriptor} instead + // of the {@link UiElementNode} because the attributes order is guaranteed in the + // descriptor but not in the node itself. + AttributeDescriptor[] attr_desc_list = ui_node.getAttributeDescriptors(); + + // If the attribute list contains at least one SeparatorAttributeDescriptor, + // sub-sections will be used. This needs to be known early as it influences the + // creation of the master table. + boolean useSubsections = false; + for (AttributeDescriptor attr_desc : attr_desc_list) { + if (attr_desc instanceof SeparatorAttributeDescriptor) { + // Sub-sections will be used. The default sections should no longer be + useSubsections = true; + break; + } + } + + FormToolkit toolkit = managedForm.getToolkit(); + Composite masterTable = SectionHelper.createTableLayout(mMasterSection, + toolkit, useSubsections ? 1 : 2 /* numColumns */); + mCurrentTable = masterTable; + + mCurrentUiElementNode = ui_node; + + if (elem_desc.getTooltip() != null) { + String tooltip; + if (Sdk.getCurrent() != null && + Sdk.getCurrent().getDocumentationBaseUrl() != null) { + tooltip = DescriptorsUtils.formatFormText(elem_desc.getTooltip(), + elem_desc, + Sdk.getCurrent().getDocumentationBaseUrl()); + } else { + tooltip = elem_desc.getTooltip(); + } + + try { + FormText text = SectionHelper.createFormText(masterTable, toolkit, + true /* isHtml */, tooltip, true /* setupLayoutData */); + text.addHyperlinkListener(mTree.getEditor().createHyperlinkListener()); + Image icon = elem_desc.getIcon(); + if (icon != null) { + text.setImage(DescriptorsUtils.IMAGE_KEY, icon); + } + } catch(Exception e) { + // The FormText parser is really really basic and will fail as soon as the + // HTML javadoc is ever so slightly malformatted. + AdtPlugin.log(e, + "Malformed javadoc, rejected by FormText for node %1$s: '%2$s'", //$NON-NLS-1$ + ui_node.getDescriptor().getXmlName(), + tooltip); + + // Fallback to a pure text tooltip, no fancy HTML + tooltip = DescriptorsUtils.formatTooltip(elem_desc.getTooltip()); + Label label = SectionHelper.createLabel(masterTable, toolkit, + tooltip, tooltip); + } + } + + Composite table = useSubsections ? null : masterTable; + + for (AttributeDescriptor attr_desc : attr_desc_list) { + if (attr_desc instanceof XmlnsAttributeDescriptor) { + // Do not show hidden attributes + continue; + } else if (table == null || attr_desc instanceof SeparatorAttributeDescriptor) { + String title = null; + if (attr_desc instanceof SeparatorAttributeDescriptor) { + // xmlName is actually the label of the separator + title = attr_desc.getXmlLocalName(); + } else { + title = String.format("Attributes from %1$s", elem_desc.getUiName()); + } + + table = createSubSectionTable(toolkit, masterTable, title); + if (attr_desc instanceof SeparatorAttributeDescriptor) { + continue; + } + } + + UiAttributeNode ui_attr = ui_node.findUiAttribute(attr_desc); + + if (ui_attr != null) { + ui_attr.createUiControl(table, managedForm); + + if (ui_attr.getCurrentValue() != null && + ui_attr.getCurrentValue().length() > 0) { + ((Section) table.getParent()).setExpanded(true); + } + } else { + // The XML has an extra unknown attribute. + // This is not expected to happen so it is ignored. + AdtPlugin.log(IStatus.INFO, + "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ + attr_desc.getXmlLocalName(), + ui_node.getDescriptor().getXmlName()); + } + } + + // Create a sub-section for the unknown attributes. + // It is initially hidden till there are some attributes to show here. + final Composite unknownTable = createSubSectionTable(toolkit, masterTable, + "Unknown XML Attributes"); + unknownTable.getParent().setVisible(false); // set section to not visible + final HashSet reference = new HashSet(); + + final IUiUpdateListener updateListener = new IUiUpdateListener() { + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + if (state == UiUpdateState.ATTR_UPDATED) { + updateUnknownAttributesSection(ui_node, unknownTable, managedForm, + reference); + } + } + }; + ui_node.addUpdateListener(updateListener); + + // remove the listener when the UI is disposed + unknownTable.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + ui_node.removeUpdateListener(updateListener); + } + }); + + updateUnknownAttributesSection(ui_node, unknownTable, managedForm, reference); + + mMasterSection.getParent().pack(true /* changed */); + } + } + + /** + * Create a sub Section and its embedding wrapper table with 2 columns. + * @return The table, child of a new section. + */ + private Composite createSubSectionTable(FormToolkit toolkit, + Composite masterTable, String title) { + + // The Section composite seems to ignore colspan when assigned a TableWrapData so + // if the parent is a table with more than one column an extra table with one column + // is inserted to respect colspan. + int parentNumCol = ((TableWrapLayout) masterTable.getLayout()).numColumns; + if (parentNumCol > 1) { + masterTable = SectionHelper.createTableLayout(masterTable, toolkit, 1); + TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); + twd.maxWidth = AndroidEditor.TEXT_WIDTH_HINT; + twd.colspan = parentNumCol; + masterTable.setLayoutData(twd); + } + + Composite table; + Section section = toolkit.createSection(masterTable, + Section.TITLE_BAR | Section.TWISTIE); + + // Add an expansion listener that will trigger a reflow on the parent + // ScrolledPageBook (which is actually a SharedScrolledComposite). This will + // recompute the correct size and adjust the scrollbar as needed. + section.addExpansionListener(new IExpansionListener() { + public void expansionStateChanged(ExpansionEvent e) { + reflowMasterSection(); + } + + public void expansionStateChanging(ExpansionEvent e) { + // pass + } + }); + + section.setText(title); + section.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, + TableWrapData.TOP)); + table = SectionHelper.createTableLayout(section, toolkit, 2 /* numColumns */); + return table; + } + + /** + * Reflow the parent ScrolledPageBook (which is actually a SharedScrolledComposite). + * This will recompute the correct size and adjust the scrollbar as needed. + */ + private void reflowMasterSection() { + for(Composite c = mMasterSection; c != null; c = c.getParent()) { + if (c instanceof SharedScrolledComposite) { + ((SharedScrolledComposite) c).reflow(true /* flushCache */); + break; + } + } + } + + /** + * Updates the unknown attributes section for the UI Node. + */ + private void updateUnknownAttributesSection(UiElementNode ui_node, + final Composite unknownTable, final IManagedForm managedForm, + HashSet reference) { + Collection ui_attrs = ui_node.getUnknownUiAttributes(); + Section section = ((Section) unknownTable.getParent()); + boolean needs_reflow = false; + + // The table was created hidden, show it if there are unknown attributes now + if (ui_attrs.size() > 0 && !section.isVisible()) { + section.setVisible(true); + needs_reflow = true; + } + + // Compare the new attribute set with the old "reference" one + boolean has_differences = ui_attrs.size() != reference.size(); + if (!has_differences) { + for (UiAttributeNode ui_attr : ui_attrs) { + if (!reference.contains(ui_attr)) { + has_differences = true; + break; + } + } + } + + if (has_differences) { + needs_reflow = true; + reference.clear(); + + // Remove all children of the table + for (Control c : unknownTable.getChildren()) { + c.dispose(); + } + + // Recreate all attributes UI + for (UiAttributeNode ui_attr : ui_attrs) { + reference.add(ui_attr); + ui_attr.createUiControl(unknownTable, managedForm); + + if (ui_attr.getCurrentValue() != null && ui_attr.getCurrentValue().length() > 0) { + section.setExpanded(true); + } + } + } + + if (needs_reflow) { + reflowMasterSection(); + } + } + + /** + * Marks the part dirty. Called as a result of user interaction with the widgets in the + * section. + */ + private void markDirty() { + if (!mIsDirty) { + mIsDirty = true; + mManagedForm.dirtyStateChanged(); + } + } +} + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java new file mode 100644 index 0000000..9f34d9e --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2007 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.editors.ui.tree; + +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +import java.util.ArrayList; + +/** + * UiModelTreeContentProvider is a trivial implementation of {@link ITreeContentProvider} + * where elements are expected to be instances of {@link UiElementNode}. + */ +class UiModelTreeContentProvider implements ITreeContentProvider { + + /** The root {@link UiElementNode} which contains all the elements that are to be + * manipulated by this tree view. In general this is the manifest UI node. */ + private UiElementNode mUiRootNode; + /** The descriptor of the elements to be displayed as root in this tree view. All elements + * of the same type in the root will be displayed. */ + private ElementDescriptor[] mDescriptorFilters; + + public UiModelTreeContentProvider(UiElementNode uiRootNode, + ElementDescriptor[] descriptorFilters) { + mUiRootNode = uiRootNode; + mDescriptorFilters = descriptorFilters; + } + + /* (non-java doc) + * Returns all the UI node children of the given element or null if not the right kind + * of object. */ + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof UiElementNode) { + UiElementNode node = (UiElementNode) parentElement; + return node.getUiChildren().toArray(); + } + return null; + } + + /* (non-java doc) + * Returns the parent of a given UI node or null if it's a root node or it's not the + * right kind of node. */ + public Object getParent(Object element) { + if (element instanceof UiElementNode) { + UiElementNode node = (UiElementNode) element; + return node.getUiParent(); + } + return null; + } + + /* (non-java doc) + * Returns true if the UI node has any UI children nodes. */ + public boolean hasChildren(Object element) { + if (element instanceof UiElementNode) { + UiElementNode node = (UiElementNode) element; + return node.getUiChildren().size() > 0; + } + return false; + } + + /* (non-java doc) + * Get root elements for the tree. These are all the UI nodes that + * match the filter descriptor in the current root node. + *

          + * Although not documented, it seems this method should not return null. + * At worse, it should return new Object[0]. + *

          + * inputElement is not currently used. The root node and the filter are given + * by the enclosing class. + */ + public Object[] getElements(Object inputElement) { + ArrayList roots = new ArrayList(); + if (mUiRootNode != null) { + for (UiElementNode ui_node : mUiRootNode.getUiChildren()) { + if (mDescriptorFilters == null || mDescriptorFilters.length == 0) { + roots.add(ui_node); + } else { + for (ElementDescriptor filter : mDescriptorFilters) { + if (ui_node.getDescriptor() == filter) { + roots.add(ui_node); + } + } + } + } + } + + return roots.toArray(); + } + + public void dispose() { + // pass + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // pass + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java new file mode 100644 index 0000000..273a30b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2007 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.ui.ErrorImageComposite; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.swt.graphics.Image; + +/** + * UiModelTreeLabelProvider is a trivial implementation of {@link ILabelProvider} + * where elements are expected to derive from {@link UiElementNode} or + * from {@link ElementDescriptor}. + * + * It is used by both the master tree viewer and by the list in the Add... selection dialog. + */ +public class UiModelTreeLabelProvider implements ILabelProvider { + + public UiModelTreeLabelProvider() { + } + + /** + * Returns the element's logo with a fallback on the android logo. + */ + public Image getImage(Object element) { + ElementDescriptor desc = null; + if (element instanceof ElementDescriptor) { + Image img = ((ElementDescriptor) element).getIcon(); + if (img != null) { + return img; + } + } else if (element instanceof UiElementNode) { + UiElementNode node = (UiElementNode) element; + desc = node.getDescriptor(); + if (desc != null) { + Image img = desc.getIcon(); + if (img != null) { + if (node.hasError()) { + //TODO: cache image + return new ErrorImageComposite(img).createImage(); + } else { + return img; + } + } + } + } + return AdtPlugin.getAndroidLogo(); + } + + /** + * Uses UiElementNode.shortDescription for the label for this tree item. + */ + public String getText(Object element) { + if (element instanceof ElementDescriptor) { + ElementDescriptor desc = (ElementDescriptor) element; + return desc.getUiName(); + } else if (element instanceof UiElementNode) { + UiElementNode node = (UiElementNode) element; + return node.getShortDescription(); + } + return element.toString(); + } + + public void addListener(ILabelProviderListener listener) { + // pass + } + + public void dispose() { + // pass + } + + public boolean isLabelProperty(Object element, String property) { + // pass + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // pass + } +} + + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java new file mode 100644 index 0000000..e3255d9 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java @@ -0,0 +1,868 @@ +/* + * Copyright (C) 2007 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.editors.ui.tree; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; +import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.ITreeSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.forms.DetailsPart; +import org.eclipse.ui.forms.IDetailsPage; +import org.eclipse.ui.forms.IDetailsPageProvider; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.MasterDetailsBlock; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.Section; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; + +/** + * {@link UiTreeBlock} is a {@link MasterDetailsBlock} which displays a tree view for + * a specific set of {@link UiElementNode}. + *

          + * For a given UI element node, the tree view displays all first-level children that + * match a given type (given by an {@link ElementDescriptor}. All children from these + * nodes are also displayed. + *

          + * In the middle next to the tree are some controls to add or delete tree nodes. + * On the left is a details part that displays all the visible UI attributes for a given + * selected UI element node. + */ +public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml { + + /** Height hint for the tree view. Helps the grid layout resize properly on smaller screens. */ + private static final int TREE_HEIGHT_HINT = 50; + + /** Container editor */ + AndroidEditor mEditor; + /** The root {@link UiElementNode} which contains all the elements that are to be + * manipulated by this tree view. In general this is the manifest UI node. */ + private UiElementNode mUiRootNode; + /** The descriptor of the elements to be displayed as root in this tree view. All elements + * of the same type in the root will be displayed. */ + private ElementDescriptor[] mDescriptorFilters; + /** The title for the master-detail part (displayed on the top "tab" on top of the tree) */ + private String mTitle; + /** The description for the master-detail part (displayed on top of the tree view) */ + private String mDescription; + /** The master-detail part, composed of a main tree and an auxiliary detail part */ + private ManifestSectionPart mMasterPart; + /** The tree viewer in the master-detail part */ + private TreeViewer mTreeViewer; + /** The "add" button for the tree view */ + private Button mAddButton; + /** The "remove" button for the tree view */ + private Button mRemoveButton; + /** The "up" button for the tree view */ + private Button mUpButton; + /** The "down" button for the tree view */ + private Button mDownButton; + /** The Managed Form used to create the master part */ + private IManagedForm mManagedForm; + /** Reference to the details part of the tree master block. */ + private DetailsPart mDetailsPart; + /** Reference to the clipboard for copy-paste */ + private Clipboard mClipboard; + /** Listener to refresh the tree viewer when the parent's node has been updated */ + private IUiUpdateListener mUiRefreshListener; + /** Listener to enable/disable the UI based on the application node's presence */ + private IUiUpdateListener mUiEnableListener; + /** An adapter/wrapper to use the add/remove/up/down tree edit actions. */ + private UiTreeActions mUiTreeActions; + /** + * True if the root node can be created on-demand (i.e. as needed as + * soon as children exist). False if an external entity controls the existence of the + * root node. In practise, this is false for the manifest application page (the actual + * "application" node is managed by the ApplicationToggle part) whereas it is true + * for all other tree pages. + */ + private final boolean mAutoCreateRoot; + + + /** + * Creates a new {@link MasterDetailsBlock} that will display all UI nodes matching the + * given filter in the given root node. + * + * @param editor The parent manifest editor. + * @param uiRootNode The root {@link UiElementNode} which contains all the elements that are + * to be manipulated by this tree view. In general this is the manifest UI node or the + * application UI node. This cannot be null. + * @param autoCreateRoot True if the root node can be created on-demand (i.e. as needed as + * soon as children exist). False if an external entity controls the existence of the + * root node. In practise, this is false for the manifest application page (the actual + * "application" node is managed by the ApplicationToggle part) whereas it is true + * for all other tree pages. + * @param descriptorFilters A list of descriptors of the elements to be displayed as root in + * this tree view. Use null or an empty list to accept any kind of node. + * @param title Title for the section + * @param description Description for the section + */ + public UiTreeBlock(AndroidEditor editor, + UiElementNode uiRootNode, + boolean autoCreateRoot, + ElementDescriptor[] descriptorFilters, + String title, + String description) { + mEditor = editor; + mUiRootNode = uiRootNode; + mAutoCreateRoot = autoCreateRoot; + mDescriptorFilters = descriptorFilters; + mTitle = title; + mDescription = description; + } + + /** @returns The container editor */ + AndroidEditor getEditor() { + return mEditor; + } + + /** @returns The reference to the clipboard for copy-paste */ + Clipboard getClipboard() { + return mClipboard; + } + + /** @returns The master-detail part, composed of a main tree and an auxiliary detail part */ + ManifestSectionPart getMasterPart() { + return mMasterPart; + } + + @Override + protected void createMasterPart(final IManagedForm managedForm, Composite parent) { + FormToolkit toolkit = managedForm.getToolkit(); + + mManagedForm = managedForm; + mMasterPart = new ManifestSectionPart(parent, toolkit); + Section section = mMasterPart.getSection(); + section.setText(mTitle); + section.setDescription(mDescription); + section.setLayout(new GridLayout()); + section.setLayoutData(new GridData(GridData.FILL_BOTH)); + + Composite grid = SectionHelper.createGridLayout(section, toolkit, 2); + + Tree tree = createTreeViewer(toolkit, grid, managedForm); + createButtons(toolkit, grid); + createTreeContextMenu(tree); + createSectionActions(section, toolkit); + } + + private void createSectionActions(Section section, FormToolkit toolkit) { + ToolBarManager manager = new ToolBarManager(SWT.FLAT); + manager.removeAll(); + + ToolBar toolbar = manager.createControl(section); + section.setTextClient(toolbar); + + ElementDescriptor[] descs = mDescriptorFilters; + if (descs == null && mUiRootNode != null) { + descs = mUiRootNode.getDescriptor().getChildren(); + } + + if (descs != null && descs.length > 1) { + for (ElementDescriptor desc : descs) { + manager.add(new DescriptorFilterAction(desc)); + } + } + + manager.add(new TreeSortAction()); + + manager.update(true /*force*/); + } + + /** + * Creates the tree and its viewer + * @return The tree control + */ + private Tree createTreeViewer(FormToolkit toolkit, Composite grid, + final IManagedForm managedForm) { + // Note: we *could* use a FilteredTree instead of the Tree+TreeViewer here. + // However the class must be adapted to create an adapted toolkit tree. + final Tree tree = toolkit.createTree(grid, SWT.MULTI); + GridData gd = new GridData(GridData.FILL_BOTH); + gd.widthHint = AndroidEditor.TEXT_WIDTH_HINT; + gd.heightHint = TREE_HEIGHT_HINT; + tree.setLayoutData(gd); + + mTreeViewer = new TreeViewer(tree); + mTreeViewer.setContentProvider(new UiModelTreeContentProvider( + mUiRootNode, mDescriptorFilters)); + mTreeViewer.setLabelProvider(new UiModelTreeLabelProvider()); + mTreeViewer.setInput("unused"); //$NON-NLS-1$ + + // Create a listener that reacts to selections on the tree viewer. + // When a selection is made, ask the managed form to propagate an event to + // all parts in the managed form. + // This is picked up by UiElementDetail.selectionChanged(). + mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + managedForm.fireSelectionChanged(mMasterPart, event.getSelection()); + adjustTreeButtons(event.getSelection()); + } + }); + + // Create three listeners: + // - One to refresh the tree viewer when the parent's node has been updated + // - One to refresh the tree viewer when the framework resources have changed + // - One to enable/disable the UI based on the application node's presence. + mUiRefreshListener = new IUiUpdateListener() { + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + mTreeViewer.refresh(); + } + }; + + mUiEnableListener = new IUiUpdateListener() { + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { + // The UiElementNode for the application XML node always exists, even + // if there is no corresponding XML node in the XML file. + // + // Normally, we enable the UI here if the XML node is not null. + // + // However if mAutoCreateRoot is true, the root node will be created on-demand + // so the tree/block is always enabled. + boolean exists = mAutoCreateRoot || (ui_node.getXmlNode() != null); + if (mMasterPart != null) { + Section section = mMasterPart.getSection(); + if (section.getEnabled() != exists) { + section.setEnabled(exists); + for (Control c : section.getChildren()) { + c.setEnabled(exists); + } + } + } + } + }; + + final Runnable resourceRefreshListener = new Runnable() { + public void run() { + // If a details part has been created, we need to "refresh" it too. + if (mDetailsPart != null) { + // The details part does not directly expose access to its internal + // page book. Instead it is possible to resize the page book to 0 and then + // back to its original value, which as the side effect of removing all + // existing cached pages. + int limit = mDetailsPart.getPageLimit(); + mDetailsPart.setPageLimit(0); + mDetailsPart.setPageLimit(limit); + } + // Refresh the tree, preserving the selection if possible. + mTreeViewer.refresh(); + } + }; + + // Setup the listeners + changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */); + + // Listen on resource framework changes to refresh the tree + AdtPlugin.getDefault().addResourceChangedListener(resourceRefreshListener); + + // Remove listeners when the tree widget gets disposed. + tree.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + UiElementNode node = mUiRootNode.getUiParent() != null ? + mUiRootNode.getUiParent() : + mUiRootNode; + + node.removeUpdateListener(mUiRefreshListener); + mUiRootNode.removeUpdateListener(mUiEnableListener); + + AdtPlugin.getDefault().removeResourceChangedListener(resourceRefreshListener); + if (mClipboard != null) { + mClipboard.dispose(); + mClipboard = null; + } + } + }); + + // Get a new clipboard reference. It is disposed when the tree is disposed. + mClipboard = new Clipboard(tree.getDisplay()); + + return tree; + } + + /** + * Changes the UI root node and the descriptor filters of the tree. + *

          + * This removes the listeners attached to the old root node and reattaches them to the + * new one. + * + * @param uiRootNode The root {@link UiElementNode} which contains all the elements that are + * to be manipulated by this tree view. In general this is the manifest UI node or the + * application UI node. This cannot be null. + * @param descriptorFilters A list of descriptors of the elements to be displayed as root in + * this tree view. Use null or an empty list to accept any kind of node. + * @param forceRefresh If tree, forces the tree to refresh + */ + public void changeRootAndDescriptors(UiElementNode uiRootNode, + ElementDescriptor[] descriptorFilters, boolean forceRefresh) { + UiElementNode node; + + // Remove previous listeners if any + if (mUiRootNode != null) { + node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode; + node.removeUpdateListener(mUiRefreshListener); + mUiRootNode.removeUpdateListener(mUiEnableListener); + } + + mUiRootNode = uiRootNode; + mDescriptorFilters = descriptorFilters; + + // Listen on structural changes on the root node of the tree + // If the node has a parent, listen on the parent instead. + node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode; + node.addUpdateListener(mUiRefreshListener); + + // Use the root node to listen to its presence. + mUiRootNode.addUpdateListener(mUiEnableListener); + + // Initialize the enabled/disabled state + mUiEnableListener.uiElementNodeUpdated(mUiRootNode, null /* state, not used */); + + if (forceRefresh) { + mTreeViewer.refresh(); + } + + createSectionActions(mMasterPart.getSection(), mManagedForm.getToolkit()); + } + + /** + * Creates the buttons next to the tree. + */ + private void createButtons(FormToolkit toolkit, Composite grid) { + + mUiTreeActions = new UiTreeActions(); + + Composite button_grid = SectionHelper.createGridLayout(grid, toolkit, 1); + button_grid.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + mAddButton = toolkit.createButton(button_grid, "Add...", SWT.PUSH); + SectionHelper.addControlTooltip(mAddButton, "Adds a new element."); + mAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | + GridData.VERTICAL_ALIGN_BEGINNING)); + + mAddButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + doTreeAdd(); + } + }); + + mRemoveButton = toolkit.createButton(button_grid, "Remove...", SWT.PUSH); + SectionHelper.addControlTooltip(mRemoveButton, "Removes an existing selected element."); + mRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mRemoveButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + doTreeRemove(); + } + }); + + mUpButton = toolkit.createButton(button_grid, "Up", SWT.PUSH); + SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element up."); + mUpButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mUpButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + doTreeUp(); + } + }); + + mDownButton = toolkit.createButton(button_grid, "Down", SWT.PUSH); + SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element down."); + mDownButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + mDownButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + doTreeDown(); + } + }); + + adjustTreeButtons(TreeSelection.EMPTY); + } + + private void createTreeContextMenu(Tree tree) { + MenuManager menuManager = new MenuManager(); + menuManager.setRemoveAllWhenShown(true); + menuManager.addMenuListener(new IMenuListener() { + /** + * The menu is about to be shown. The menu manager has already been + * requested to remove any existing menu item. This method gets the + * tree selection and if it is of the appropriate type it re-creates + * the necessary actions. + */ + public void menuAboutToShow(IMenuManager manager) { + ISelection selection = mTreeViewer.getSelection(); + if (!selection.isEmpty() && selection instanceof ITreeSelection) { + ArrayList selected = filterSelection((ITreeSelection) selection); + doCreateMenuAction(manager, selected); + return; + } + doCreateMenuAction(manager, null /* ui_node */); + } + }); + Menu contextMenu = menuManager.createContextMenu(tree); + tree.setMenu(contextMenu); + } + + /** + * Adds the menu actions to the context menu when the given UI node is selected in + * the tree view. + * + * @param manager The context menu manager + * @param selected The UI nodes selected in the tree. Can be null, in which case the root + * is to be modified. + */ + private void doCreateMenuAction(IMenuManager manager, ArrayList selected) { + if (selected != null) { + boolean hasXml = false; + for (UiElementNode uiNode : selected) { + if (uiNode.getXmlNode() != null) { + hasXml = true; + break; + } + } + + if (hasXml) { + manager.add(new CopyCutAction(getEditor(), getClipboard(), + null, selected, true /* cut */)); + manager.add(new CopyCutAction(getEditor(), getClipboard(), + null, selected, false /* cut */)); + + // Can't paste with more than one element selected (the selection is the target) + if (selected.size() <= 1) { + // Paste is not valid if it would add a second element on a terminal element + // which parent is a document -- an XML document can only have one child. This + // means paste is valid if the current UI node can have children or if the + // parent is not a document. + UiElementNode ui_root = selected.get(0).getUiRoot(); + if (ui_root.getDescriptor().hasChildren() || + !(ui_root.getUiParent() instanceof UiDocumentNode)) { + manager.add(new PasteAction(getEditor(), getClipboard(), selected.get(0))); + } + } + manager.add(new Separator()); + } + } + + // Append "add" and "remove" actions. They do the same thing as the add/remove + // buttons on the side. + Action action; + IconFactory factory = IconFactory.getInstance(); + + // "Add" makes sense only if there's 0 or 1 item selected since the + // one selected item becomes the target. + if (selected == null || selected.size() <= 1) { + manager.add(new Action("Add...", factory.getImageDescriptor("add")) { //$NON-NLS-1$ + @Override + public void run() { + super.run(); + doTreeAdd(); + } + }); + } + + if (selected != null) { + if (selected != null) { + manager.add(new Action("Remove", factory.getImageDescriptor("delete")) { //$NON-NLS-1$ + @Override + public void run() { + super.run(); + doTreeRemove(); + } + }); + } + manager.add(new Separator()); + + manager.add(new Action("Up", factory.getImageDescriptor("up")) { //$NON-NLS-1$ + @Override + public void run() { + super.run(); + doTreeUp(); + } + }); + manager.add(new Action("Down", factory.getImageDescriptor("down")) { //$NON-NLS-1$ + @Override + public void run() { + super.run(); + doTreeDown(); + } + }); + } + } + + + /** + * This is called by the tree when a selection is made. + * It enables/disables the buttons associated with the tree depending on the current + * selection. + * + * @param selection The current tree selection (same as mTreeViewer.getSelection()) + */ + private void adjustTreeButtons(ISelection selection) { + mRemoveButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); + mUpButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); + mDownButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); + } + + /** + * An adapter/wrapper to use the add/remove/up/down tree edit actions. + */ + private class UiTreeActions extends UiActions { + @Override + protected UiElementNode getRootNode() { + return mUiRootNode; + } + + @Override + protected void selectUiNode(UiElementNode uiNodeToSelect) { + // Select the new item + if (uiNodeToSelect != null) { + LinkedList segments = new LinkedList(); + for (UiElementNode ui_node = uiNodeToSelect; ui_node != mUiRootNode; + ui_node = ui_node.getUiParent()) { + segments.add(0, ui_node); + } + mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray()))); + } + } + + @Override + public void commitPendingXmlChanges() { + commitManagedForm(); + } + } + + /** + * Filters an ITreeSelection to only keep the {@link UiElementNode}s (in case there's + * something else in there). + * + * @return A new list of {@link UiElementNode} with at least one item or null. + */ + @SuppressWarnings("unchecked") + private ArrayList filterSelection(ITreeSelection selection) { + ArrayList selected = new ArrayList(); + + for (Iterator it = selection.iterator(); it.hasNext(); ) { + Object selectedObj = it.next(); + + if (selectedObj instanceof UiElementNode) { + selected.add((UiElementNode) selectedObj); + } + } + + return selected.size() > 0 ? selected : null; + } + + /** + * Called when the "Add..." button next to the tree view is selected. + * + * Displays a selection dialog that lets the user select which kind of node + * to create, depending on the current selection. + */ + private void doTreeAdd() { + UiElementNode ui_node = mUiRootNode; + ISelection selection = mTreeViewer.getSelection(); + if (!selection.isEmpty() && selection instanceof ITreeSelection) { + ITreeSelection tree_selection = (ITreeSelection) selection; + Object first = tree_selection.getFirstElement(); + if (first != null && first instanceof UiElementNode) { + ui_node = (UiElementNode) first; + } + } + + mUiTreeActions.doAdd( + ui_node, + mDescriptorFilters, + mTreeViewer.getControl().getShell(), + (ILabelProvider) mTreeViewer.getLabelProvider()); + } + + /** + * Called when the "Remove" button is selected. + * + * If the tree has a selection, remove it. + * This simply deletes the XML node attached to the UI node: when the XML model fires the + * update event, the tree will get refreshed. + */ + protected void doTreeRemove() { + ISelection selection = mTreeViewer.getSelection(); + if (!selection.isEmpty() && selection instanceof ITreeSelection) { + ArrayList selected = filterSelection((ITreeSelection) selection); + mUiTreeActions.doRemove(selected, mTreeViewer.getControl().getShell()); + } + } + + /** + * Called when the "Up" button is selected. + *

          + * If the tree has a selection, move it up, either in the child list or as the last child + * of the previous parent. + */ + protected void doTreeUp() { + ISelection selection = mTreeViewer.getSelection(); + if (!selection.isEmpty() && selection instanceof ITreeSelection) { + ArrayList selected = filterSelection((ITreeSelection) selection); + mUiTreeActions.doUp(selected); + } + } + + /** + * Called when the "Down" button is selected. + * + * If the tree has a selection, move it down, either in the same child list or as the + * first child of the next parent. + */ + protected void doTreeDown() { + ISelection selection = mTreeViewer.getSelection(); + if (!selection.isEmpty() && selection instanceof ITreeSelection) { + ArrayList selected = filterSelection((ITreeSelection) selection); + mUiTreeActions.doDown(selected); + } + } + + /** + * Commits the current managed form (the one associated with our master part). + * As a side effect, this will commit the current UiElementDetails page. + */ + void commitManagedForm() { + if (mManagedForm != null) { + mManagedForm.commit(false /* onSave */); + } + } + + /* Implements ICommitXml for CopyCutAction */ + public void commitPendingXmlChanges() { + commitManagedForm(); + } + + @Override + protected void createToolBarActions(IManagedForm managedForm) { + // Pass. Not used, toolbar actions are defined by createSectionActions(). + } + + @Override + protected void registerPages(DetailsPart detailsPart) { + // Keep a reference on the details part (the super class doesn't provide a getter + // for it.) + mDetailsPart = detailsPart; + + // The page selection mechanism does not use pages registered by association with + // a node class. Instead it uses a custom details page provider that provides a + // new UiElementDetail instance for each node instance. A limit of 5 pages is + // then set (the value is arbitrary but should be reasonable) for the internal + // page book. + detailsPart.setPageLimit(5); + + final UiTreeBlock tree = this; + + detailsPart.setPageProvider(new IDetailsPageProvider() { + public IDetailsPage getPage(Object key) { + if (key instanceof UiElementNode) { + return new UiElementDetail(tree); + } + return null; + } + + public Object getPageKey(Object object) { + return object; // use node object as key + } + }); + } + + /** + * An alphabetic sort action for the tree viewer. + */ + private class TreeSortAction extends Action { + + private ViewerComparator mComparator; + + public TreeSortAction() { + super("Sorts elements alphabetically.", AS_CHECK_BOX); + setImageDescriptor(IconFactory.getInstance().getImageDescriptor("az_sort")); //$NON-NLS-1$ + + if (mTreeViewer != null) { + boolean is_sorted = mTreeViewer.getComparator() != null; + setChecked(is_sorted); + } + } + + /** + * Called when the button is selected. Toggles the tree viewer comparator. + */ + @Override + public void run() { + if (mTreeViewer == null) { + notifyResult(false /*success*/); + return; + } + + ViewerComparator comp = mTreeViewer.getComparator(); + if (comp != null) { + // Tree is currently sorted. + // Save currently comparator and remove it + mComparator = comp; + mTreeViewer.setComparator(null); + } else { + // Tree is not currently sorted. + // Reuse or add a new comparator. + if (mComparator == null) { + mComparator = new ViewerComparator(); + } + mTreeViewer.setComparator(mComparator); + } + + notifyResult(true /*success*/); + } + } + + /** + * A filter on descriptor for the tree viewer. + *

          + * The tree viewer will contain many of these actions and only one can be enabled at a + * given time. When no action is selected, everything is displayed. + *

          + * Since "radio"-like actions do not allow for unselecting all of them, we manually + * handle the exclusive radio button-like property: when an action is selected, it manually + * removes all other actions as needed. + */ + private class DescriptorFilterAction extends Action { + + private final ElementDescriptor mDescriptor; + private ViewerFilter mFilter; + + public DescriptorFilterAction(ElementDescriptor descriptor) { + super(String.format("Displays only %1$s elements.", descriptor.getUiName()), + AS_CHECK_BOX); + + mDescriptor = descriptor; + setImageDescriptor(descriptor.getImageDescriptor()); + } + + /** + * Called when the button is selected. + *

          + * Find any existing {@link DescriptorFilter}s and remove them. Install ours. + */ + @Override + public void run() { + super.run(); + + if (isChecked()) { + if (mFilter == null) { + // create filter when required + mFilter = new DescriptorFilter(this); + } + + // we add our filter first, otherwise the UI might show the full list + mTreeViewer.addFilter(mFilter); + + // Then remove the any other filters except ours. There should be at most + // one other filter, since that's how the actions are made to look like + // exclusive radio buttons. + for (ViewerFilter filter : mTreeViewer.getFilters()) { + if (filter instanceof DescriptorFilter && filter != mFilter) { + DescriptorFilterAction action = ((DescriptorFilter) filter).getAction(); + action.setChecked(false); + mTreeViewer.removeFilter(filter); + } + } + } else if (mFilter != null){ + mTreeViewer.removeFilter(mFilter); + } + } + + /** + * Filters the tree viewer for the given descriptor. + *

          + * The filter is linked to the action so that an action can iterate through the list + * of filters and un-select the actions. + */ + private class DescriptorFilter extends ViewerFilter { + + private final DescriptorFilterAction mAction; + + public DescriptorFilter(DescriptorFilterAction action) { + mAction = action; + } + + public DescriptorFilterAction getAction() { + return mAction; + } + + /** + * Returns true if an element should be displayed, that if the element or + * any of its parent matches the requested descriptor. + */ + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + while (element instanceof UiElementNode) { + UiElementNode uiNode = (UiElementNode)element; + if (uiNode.getDescriptor() == mDescriptor) { + return true; + } + element = uiNode.getUiParent(); + } + return false; + } + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java new file mode 100644 index 0000000..7fe44da --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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.editors.uimodel; + +/** + * This interface decoration indicates that a given UiAttributeNode can both + * set and get its current value. + */ +public interface IUiSettableAttributeNode { + + /** Returns the current value of the node. */ + public String getCurrentValue(); + + /** Sets the current value of the node. Cannot be null (use an empty string). */ + public void setCurrentValue(String value); + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java new file mode 100644 index 0000000..12cb31b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + + +/** + * Listen to update notifications in UI nodes. + */ +public interface IUiUpdateListener { + + /** Update state of the UI node */ + public enum UiUpdateState { + /** The node's attributes have been updated. They may or may not actually have changed. */ + ATTR_UPDATED, + /** The node sub-structure (i.e. child nodes) has changed */ + CHILDREN_CHANGED, + /** The XML counterpart for the UI node has just been created. */ + CREATED, + /** The XML counterpart for the UI node has just been deleted. + * Note that mandatory UI nodes are never actually deleted. */ + DELETED + } + + /** + * Indicates that an UiElementNode has been updated. + *

          + * This happens when an {@link UiElementNode} is refreshed to match the + * XML model. The actual UI element node may or may not have changed. + * + * @param ui_node The {@link UiElementNode} being updated. + */ + public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java new file mode 100644 index 0000000..5908574 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; + +import org.w3c.dom.Node; + +/** + * Represents an XML attribute in that can be modified using a simple text field + * in the XML editor's user interface. + *

          + * The XML attribute has no default value. When unset, the text field is blank. + * When updating the XML, if the field is empty, the attribute will be removed + * from the XML element. + *

          + * See {@link UiAttributeNode} for more information. + */ +public abstract class UiAbstractTextAttributeNode extends UiAttributeNode + implements IUiSettableAttributeNode { + + protected static final String DEFAULT_VALUE = ""; //$NON-NLS-1$ + + /** Prevent internal listener from firing when internally modifying the text */ + private boolean mInternalTextModification; + /** Last value read from the XML model. Cannot be null. */ + private String mCurrentValue = DEFAULT_VALUE; + + public UiAbstractTextAttributeNode(AttributeDescriptor attributeDescriptor, + UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /** Returns the current value of the node. */ + @Override + public final String getCurrentValue() { + return mCurrentValue; + } + + /** Sets the current value of the node. Cannot be null (use an empty string). */ + public final void setCurrentValue(String value) { + mCurrentValue = value; + } + + /** Returns if the attribute node is valid, and its UI has been created. */ + public abstract boolean isValid(); + + /** Returns the text value present in the UI. */ + public abstract String getTextWidgetValue(); + + /** Sets the text value to be displayed in the UI. */ + public abstract void setTextWidgetValue(String value); + + + /** + * Updates the current text field's value when the XML has changed. + *

          + * The caller doesn't really know if attributes have changed, + * so it will call this to refresh the attribute anyway. The value + * is only set if it has changed. + *

          + * This also resets the "dirty" flag. + */ + @Override + public void updateValue(Node xml_attribute_node) { + mCurrentValue = DEFAULT_VALUE; + if (xml_attribute_node != null) { + mCurrentValue = xml_attribute_node.getNodeValue().trim(); + } + + if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) { + try { + mInternalTextModification = true; + setTextWidgetValue(mCurrentValue); + setDirty(false); + } finally { + mInternalTextModification = false; + } + } + } + + /* (non-java doc) + * Called by the user interface when the editor is saved or its state changed + * and the modified attributes must be committed (i.e. written) to the XML model. + */ + @Override + public void commit() { + UiElementNode parent = getUiParent(); + if (parent != null && isValid() && isDirty()) { + String value = getTextWidgetValue().trim(); + if (parent.commitAttributeToXml(this, value)) { + mCurrentValue = value; + setDirty(false); + } + } + } + + protected final boolean isInInternalTextModification() { + return mInternalTextModification; + } + + protected final void setInInternalTextModification(boolean internalTextModification) { + mInternalTextModification = internalTextModification; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java new file mode 100644 index 0000000..5972f22 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.ui.forms.IManagedForm; +import org.w3c.dom.Node; + +/** + * Represents an XML attribute that can be modified by the XML editor's user interface. + *

          + * The characteristics of an {@link UiAttributeNode} are declared by a + * corresponding {@link AttributeDescriptor}. + *

          + * This is an abstract class. Derived classes must implement the creation of the UI + * and manage its synchronization with the XML. + */ +public abstract class UiAttributeNode { + + private AttributeDescriptor mDescriptor; + private UiElementNode mUiParent; + private boolean mIsDirty; + private boolean mHasError; + + /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} + * and the corresponding runtine {@link UiElementNode} parent. */ + public UiAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { + mDescriptor = attributeDescriptor; + mUiParent = uiParent; + } + + /** Returns the {@link AttributeDescriptor} specific to this UI attribute node */ + public final AttributeDescriptor getDescriptor() { + return mDescriptor; + } + + /** Returns the {@link UiElementNode} that owns this {@link UiAttributeNode} */ + public final UiElementNode getUiParent() { + return mUiParent; + } + + /** Returns the current value of the node. */ + public abstract String getCurrentValue(); + + /** + * @return True if the attribute has been changed since it was last loaded + * from the XML model. + */ + public final boolean isDirty() { + return mIsDirty; + } + + /** + * Sets whether the attribute is dirty and also notifies the editor some part's dirty + * flag as changed. + *

          + * Subclasses should set the to true as a result of user interaction with the widgets in + * the section and then should set to false when the commit() method completed. + */ + public void setDirty(boolean isDirty) { + boolean old_value = mIsDirty; + mIsDirty = isDirty; + // TODO: for unknown attributes, getParent() != null && getParent().getEditor() != null + if (old_value != isDirty) { + getUiParent().getEditor().editorDirtyStateChanged(); + } + } + + /** + * Sets the error flag value. + * @param errorFlag the error flag + */ + public final void setHasError(boolean errorFlag) { + mHasError = errorFlag; + } + + /** + * Returns whether this node has errors. + */ + public final boolean hasError() { + return mHasError; + } + + /** + * Called once by the parent user interface to creates the necessary + * user interface to edit this attribute. + *

          + * This method can be called more than once in the life cycle of an UI node, + * typically when the UI is part of a master-detail tree, as pages are swapped. + * + * @param parent The composite where to create the user interface. + * @param managedForm The managed form owning this part. + */ + public abstract void createUiControl(Composite parent, IManagedForm managedForm); + + /** + * Used to get a list of all possible values for this UI attribute. + *

          + * This is used, among other things, by the XML Content Assists to complete values + * for an attribute. + *

          + * Implementations that do not have any known values should return null. + * + * @return A list of possible completion values or null. + */ + public abstract String[] getPossibleValues(); + + /** + * Called when the XML is being loaded or has changed to + * update the value held by this user interface attribute node. + *

          + * The XML Node may be null, which denotes that the attribute is not + * specified in the XML model. In general, this means the "default" value of the + * attribute should be used. + *

          + * The caller doesn't really know if attributes have changed, + * so it will call this to refresh the attribute anyway. It's up to the + * UI implementation to minimize refreshes. + * + * @param xml_attribute_node + */ + public abstract void updateValue(Node xml_attribute_node); + + /** + * Called by the user interface when the editor is saved or its state changed + * and the modified attributes must be committed (i.e. written) to the XML model. + *

          + * Important behaviors: + *

            + *
          • The caller *must* have called IStructuredModel.aboutToChangeModel before. + * The implemented methods must assume it is safe to modify the XML model. + *
          • On success, the implementation *must* call setDirty(false). + *
          • On failure, the implementation can fail with an exception, which + * is trapped and logged by the caller, or do nothing, whichever is more + * appropriate. + *
          + */ + public abstract void commit(); +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java new file mode 100644 index 0000000..113738f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java @@ -0,0 +1,135 @@ +/* + * 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.editors.uimodel; + +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * Represents an XML document node that can be modified by the user interface in the XML editor. + *

          + * The structure of a given {@link UiDocumentNode} is declared by a corresponding + * {@link DocumentDescriptor}. + */ +public class UiDocumentNode extends UiElementNode { + + /** + * Creates a new {@link UiDocumentNode} described by a given {@link DocumentDescriptor}. + * + * @param documentDescriptor The {@link DocumentDescriptor} for the XML node. Cannot be null. + */ + public UiDocumentNode(DocumentDescriptor documentDescriptor) { + super(documentDescriptor); + } + + /** + * Computes a short string describing the UI node suitable for tree views. + * Uses the element's attribute "android:name" if present, or the "android:label" one + * followed by the element's name. + * + * @return A short string describing the UI node suitable for tree views. + */ + @Override + public String getShortDescription() { + return "Document"; //$NON-NLS-1$ + } + + /** + * Computes a "breadcrumb trail" description for this node. + * + * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect + * when called on the root node itself. + * @return The "breadcrumb trail" description for this node. + */ + @Override + public String getBreadcrumbTrailDescription(boolean include_root) { + return "Document"; //$NON-NLS-1$ + } + + /** + * This method throws an exception when attempted to assign a parent, since XML documents + * cannot have a parent. It is OK to assign null. + */ + @Override + protected void setUiParent(UiElementNode parent) { + if (parent != null) { + // DEBUG. Change to log warning. + throw new UnsupportedOperationException("Documents can't have UI parents"); //$NON-NLS-1$ + } + super.setUiParent(null); + } + + /** + * Populate this element node with all values from the given XML node. + * + * This fails if the given XML node has a different element name -- it won't change the + * type of this ui node. + * + * This method can be both used for populating values the first time and updating values + * after the XML model changed. + * + * @param xml_node The XML node to mirror + * @return Returns true if the XML structure has changed (nodes added, removed or replaced) + */ + @Override + public boolean loadFromXmlNode(Node xml_node) { + boolean structure_changed = (getXmlDocument() != xml_node); + setXmlDocument((Document) xml_node); + structure_changed |= super.loadFromXmlNode(xml_node); + if (structure_changed) { + invokeUiUpdateListeners(UiUpdateState.CHILDREN_CHANGED); + } + return structure_changed; + } + + /** + * This method throws an exception if there is no underlying XML document. + *

          + * XML documents cannot be created per se -- they are a by-product of the StructuredEditor + * XML parser. + * + * @return The current value of getXmlDocument(). + */ + @Override + public Node createXmlNode() { + if (getXmlDocument() == null) { + // By design, a document node cannot be created, it is owned by the XML parser. + // By "design" this should never happen since the XML parser always creates an XML + // document container, even for an empty file. + throw new UnsupportedOperationException("Documents cannot be created"); //$NON-NLS-1$ + } + return getXmlDocument(); + } + + /** + * This method throws an exception and does not even try to delete the XML document. + *

          + * XML documents cannot be deleted per se -- they are a by-product of the StructuredEditor + * XML parser. + * + * @return The removed node or null if it didn't exist in the firtst place. + */ + @Override + public Node deleteXmlNode() { + // DEBUG. Change to log warning. + throw new UnsupportedOperationException("Documents cannot be deleted"); //$NON-NLS-1$ + } +} + diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java new file mode 100644 index 0000000..e0e9a40 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java @@ -0,0 +1,1500 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.layout.descriptors.CustomViewDescriptorService; +import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; +import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; +import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; +import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.views.properties.IPropertyDescriptor; +import org.eclipse.ui.views.properties.IPropertySource; +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.Text; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +/** + * Represents an XML node that can be modified by the user interface in the XML editor. + *

          + * Each tree viewer used in the application page's parts needs to keep a model representing + * each underlying node in the tree. This interface represents the base type for such a node. + *

          + * Each node acts as an intermediary model between the actual XML model (the real data support) + * and the tree viewers or the corresponding page parts. + *

          + * Element nodes don't contain data per se. Their data is contained in their attributes + * as well as their children's attributes, see {@link UiAttributeNode}. + *

          + * The structure of a given {@link UiElementNode} is declared by a corresponding + * {@link ElementDescriptor}. + *

          + * The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when + * an element is selected. The {@link AttributeDescriptor} are used property descriptors. + */ +public class UiElementNode implements IPropertySource { + + /** List of prefixes removed from android:id strings when creating short descriptions. */ + private static String[] ID_PREFIXES = { + "@android:id/", //$NON-NLS-1$ + "@+id/", "@id/", "@+", "@" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + + /** The element descriptor for the node. Always present, never null. */ + private ElementDescriptor mDescriptor; + /** The parent element node in the UI model. It is null for a root element or until + * the node is attached to its parent. */ + private UiElementNode mUiParent; + /** The {@link AndroidEditor} handling the UI hierarchy. This is defined only for the + * root node. All children have the value set to null and query their parent. */ + private AndroidEditor mEditor; + /** The XML {@link Document} model that is being mirror by the UI model. This is defined + * only for the root node. All children have the value set to null and query their parent. */ + private Document mXmlDocument; + /** The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which + * have no corresponding XML node or for new UI nodes before their XML node is set. */ + private Node mXmlNode; + /** The list of all UI children nodes. Can be empty but never null. There's one UI children + * node per existing XML children node. */ + private ArrayList mUiChildren; + /** The list of all UI attributes, as declared in the {@link ElementDescriptor}. + * The list is always defined and never null. Unlike the UiElementNode children list, this + * is always defined, even for attributes that do not exist in the XML model -- that's because + * "missing" attributes in the XML model simply mean a default value is used. Also note that + * the underlying collection is a map, so order is not respected. To get the desired attribute + * order, iterate through the {@link ElementDescriptor}'s attribute list. */ + private HashMap mUiAttributes; + private HashSet mUnknownUiAttributes; + /** A read-only view of the UI children node collection. */ + private List mReadOnlyUiChildren; + /** A read-only view of the UI attributes collection. */ + private Collection mReadOnlyUiAttributes; + /** A map of hidden attribute descriptors. Key is the XML name. */ + private Map mCachedHiddenAttributes; + /** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any + * listeners attached, so the list is only created on demand and can be null. */ + private ArrayList mUiUpdateListeners; + /** Error Flag */ + private boolean mHasError; + /** Temporary data used by the editors. This data is not sync'ed with the XML */ + private Object mEditData; + + /** + * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}. + * + * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null. + */ + public UiElementNode(ElementDescriptor elementDescriptor) { + mDescriptor = elementDescriptor; + clearContent(); + } + + /** + * Clears the {@link UiElementNode} by resetting the children list and + * the {@link UiAttributeNode}s list. + * Also resets the attached XML node, document, editor if any. + *

          + * The parent {@link UiElementNode} node is not reset so that it's position + * in the hierarchy be left intact, if any. + */ + /* package */ void clearContent() { + mXmlNode = null; + mXmlDocument = null; + mEditor = null; + clearAttributes(); + mReadOnlyUiChildren = null; + if (mUiChildren == null) { + mUiChildren = new ArrayList(); + } else { + // We can't remove mandatory nodes, we just clear them. + for (int i = mUiChildren.size() - 1; i >= 0; --i) { + removeUiChildAtIndex(i); + } + } + } + + /** + * Clears the internal list of attributes, the read-only cached version of it + * and the read-only cached hidden attribute list. + */ + private void clearAttributes() { + mUiAttributes = null; + mReadOnlyUiAttributes = null; + mCachedHiddenAttributes = null; + mUnknownUiAttributes = new HashSet(); + } + + /** + * Gets or creates the internal UiAttributes list. + *

          + * When the descriptor derives from ViewElementDescriptor, this list depends on the + * current UiParent node. + * + * @return A new set of {@link UiAttributeNode} that matches the expected + * attributes for this node. + */ + private HashMap getInternalUiAttributes() { + if (mUiAttributes == null) { + AttributeDescriptor[] attr_list = getAttributeDescriptors(); + mUiAttributes = new HashMap(attr_list.length); + for (AttributeDescriptor desc : attr_list) { + UiAttributeNode ui_node = desc.createUiNode(this); + if (ui_node != null) { // Some AttributeDescriptors do not have UI associated + mUiAttributes.put(desc, ui_node); + } + } + } + return mUiAttributes; + } + + /** + * Computes a short string describing the UI node suitable for tree views. + * Uses the element's attribute "android:name" if present, or the "android:label" one + * followed by the element's name. + * + * @return A short string describing the UI node suitable for tree views. + */ + public String getShortDescription() { + if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) { + + // Application and Manifest nodes have a special treatment: they are unique nodes + // so we don't bother trying to differentiate their strings and we fall back to + // just using the UI name below. + Element elem = (Element) mXmlNode; + + String attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + AndroidManifestDescriptors.ANDROID_NAME_ATTR); + if (attr == null || attr.length() == 0) { + attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + AndroidManifestDescriptors.ANDROID_LABEL_ATTR); + } + if (attr == null || attr.length() == 0) { + attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + XmlDescriptors.PREF_KEY_ATTR); + } + if (attr == null || attr.length() == 0) { + attr = elem.getAttribute(ResourcesDescriptors.NAME_ATTR); + } + if (attr == null || attr.length() == 0) { + attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, + LayoutDescriptors.ID_ATTR); + + if (attr != null && attr.length() > 0) { + for (String prefix : ID_PREFIXES) { + if (attr.startsWith(prefix)) { + attr = attr.substring(prefix.length()); + break; + } + } + } + } + if (attr != null && attr.length() > 0) { + return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName()); + } + } + + return String.format("%1$s", mDescriptor.getUiName()); + } + + /** + * Computes a "breadcrumb trail" description for this node. + * It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter" + * + * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect + * when called on the root node itself. + * @return The "breadcrumb trail" description for this node. + */ + public String getBreadcrumbTrailDescription(boolean include_root) { + StringBuilder sb = new StringBuilder(getShortDescription()); + + for (UiElementNode ui_node = getUiParent(); + ui_node != null; + ui_node = ui_node.getUiParent()) { + if (!include_root && ui_node.getUiParent() == null) { + break; + } + sb.insert(0, String.format("%1$s > ", ui_node.getShortDescription())); //$NON-NLS-1$ + } + + return sb.toString(); + } + + /** + * Sets the XML {@link Document}. + *

          + * The XML {@link Document} is initially null. The XML {@link Document} must be set only on the + * UI root element node (this method takes care of that.) + */ + public void setXmlDocument(Document xml_doc) { + if (mUiParent == null) { + mXmlDocument = xml_doc; + } else { + mUiParent.setXmlDocument(xml_doc); + } + } + + /** + * Returns the XML {@link Document}. + *

          + * The value is initially null until the UI node is attached to its UI parent -- the value + * of the document is then propagated. + * + * @return the XML {@link Document} or the parent's XML {@link Document} or null. + */ + public Document getXmlDocument() { + if (mXmlDocument != null) { + return mXmlDocument; + } else if (mUiParent != null) { + return mUiParent.getXmlDocument(); + } + return null; + } + + /** + * Returns the XML node associated with this UI node. + *

          + * Some {@link ElementDescriptor} are declared as being "mandatory". This means the + * corresponding UI node will exist even if there is no corresponding XML node. Such structure + * is created and enforced by the parent of the tree, not the element themselves. However + * such nodes will likely not have an XML node associated, so getXmlNode() can return null. + * + * @return The associated XML node. Can be null for mandatory nodes. + */ + public Node getXmlNode() { + return mXmlNode; + } + + /** + * Returns the {@link ElementDescriptor} for this node. This is never null. + *

          + * Do not use this to call getDescriptor().getAttributes(), instead call + * getAttributeDescriptors() which can be overriden by derived classes. + */ + public ElementDescriptor getDescriptor() { + return mDescriptor; + } + + /** + * Returns the {@link AttributeDescriptor} array for the descriptor of this node. + *

          + * Use this instead of getDescriptor().getAttributes() -- derived classes can override + * this to manipulate the attribute descriptor list depending on the current UI node. + */ + public AttributeDescriptor[] getAttributeDescriptors() { + return mDescriptor.getAttributes(); + } + + /** + * Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node. + * This is a subset of the getAttributeDescriptors() list. + *

          + * Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes + * could override this to manipulate the attribute descriptor list depending on the current + * UI node. There's no need for it right now so keep it private. + */ + private Map getHiddenAttributeDescriptors() { + if (mCachedHiddenAttributes == null) { + mCachedHiddenAttributes = new HashMap(); + for (AttributeDescriptor attr_desc : getAttributeDescriptors()) { + if (attr_desc instanceof XmlnsAttributeDescriptor) { + mCachedHiddenAttributes.put( + ((XmlnsAttributeDescriptor) attr_desc).getXmlNsName(), + attr_desc); + } + } + } + return mCachedHiddenAttributes; + } + + /** + * Sets the parent of this UiElementNode. + *

          + * The root node has no parent. + */ + protected void setUiParent(UiElementNode parent) { + mUiParent = parent; + // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent. + clearAttributes(); + } + + /** + * @return The parent {@link UiElementNode} or null if this is the root node. + */ + public UiElementNode getUiParent() { + return mUiParent; + } + + /** + * Returns The root {@link UiElementNode}. + */ + public UiElementNode getUiRoot() { + UiElementNode root = this; + while (root.mUiParent != null) { + root = root.mUiParent; + } + + return root; + } + + /** + * Returns the previous UI sibling of this UI node. + * If the node does not have a previous sibling, returns null. + */ + public UiElementNode getUiPreviousSibling() { + if (mUiParent != null) { + List childlist = mUiParent.getUiChildren(); + if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) { + int index = childlist.indexOf(this); + return index > 0 ? childlist.get(index - 1) : null; + } + } + return null; + } + + /** + * Returns the next UI sibling of this UI node. + * If the node does not have a next sibling, returns null. + */ + public UiElementNode getUiNextSibling() { + if (mUiParent != null) { + List childlist = mUiParent.getUiChildren(); + if (childlist != null) { + int size = childlist.size(); + if (size > 1 && childlist.get(size - 1) != this) { + int index = childlist.indexOf(this); + return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null; + } + } + } + return null; + } + + /** + * Sets the {@link AndroidEditor} handling this {@link UiElementNode} hierarchy. + *

          + * The editor must always be set on the root node. This method takes care of that. + */ + public void setEditor(AndroidEditor editor) { + if (mUiParent == null) { + mEditor = editor; + } else { + mUiParent.setEditor(editor); + } + } + + /** + * Returns the {@link AndroidEditor} that embeds this {@link UiElementNode}. + *

          + * The value is initially null until the node is attached to its parent -- the value + * of the root node is then propagated. + * + * @return The embedding {@link AndroidEditor} or null. + */ + public AndroidEditor getEditor() { + return mUiParent == null ? mEditor : mUiParent.getEditor(); + } + + /** + * Returns the Android target data for the file being edited. + */ + public AndroidTargetData getAndroidTarget() { + return getEditor().getTargetData(); + } + + /** + * @return A read-only version of the children collection. + */ + public List getUiChildren() { + if (mReadOnlyUiChildren == null) { + mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren); + } + return mReadOnlyUiChildren; + } + + /** + * @return A read-only version of the attributes collection. + */ + public Collection getUiAttributes() { + if (mReadOnlyUiAttributes == null) { + mReadOnlyUiAttributes = Collections.unmodifiableCollection( + getInternalUiAttributes().values()); + } + return mReadOnlyUiAttributes; + } + + /** + * @return A read-only version of the unknown attributes collection. + */ + public Collection getUnknownUiAttributes() { + return Collections.unmodifiableCollection(mUnknownUiAttributes); + } + + /** + * Sets the error flag value. + * @param errorFlag the error flag + */ + public final void setHasError(boolean errorFlag) { + mHasError = errorFlag; + } + + /** + * Returns whether this node, its attributes, or one of the children nodes (and attributes) + * has errors. + */ + public final boolean hasError() { + if (mHasError) { + return true; + } + + // get the error value from the attributes. + Collection attributes = getInternalUiAttributes().values(); + for (UiAttributeNode attribute : attributes) { + if (attribute.hasError()) { + return true; + } + } + + // and now from the children. + for (UiElementNode child : mUiChildren) { + if (child.hasError()) { + return true; + } + } + + return false; + } + + /** + * Adds a new {@link IUiUpdateListener} to the internal update listener list. + */ + public void addUpdateListener(IUiUpdateListener listener) { + if (mUiUpdateListeners == null) { + mUiUpdateListeners = new ArrayList(); + } + if (!mUiUpdateListeners.contains(listener)) { + mUiUpdateListeners.add(listener); + } + } + + /** + * Removes an existing {@link IUiUpdateListener} from the internal update listener list. + * Does nothing if the list is empty or the listener is not registered. + */ + public void removeUpdateListener(IUiUpdateListener listener) { + if (mUiUpdateListeners != null) { + mUiUpdateListeners.remove(listener); + } + } + + /** + * Finds a child node relative to this node using a path-like expression. + * F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and + * returns the latter. If there are multiple nodes with the same name at the same + * level, always uses the first one found. + * + * @param path The path like expression to select a child node. + * @return The ui node found or null. + */ + public UiElementNode findUiChildNode(String path) { + String[] items = path.split("/"); //$NON-NLS-1$ + UiElementNode ui_node = this; + for (String item : items) { + boolean next_segment = false; + for (UiElementNode c : ui_node.mUiChildren) { + if (c.getDescriptor().getXmlName().equals(item)) { + ui_node = c; + next_segment = true; + break; + } + } + if (!next_segment) { + return null; + } + } + return ui_node; + } + + /** + * Finds an {@link UiElementNode} which contains the give XML {@link Node}. + * Looks recursively in all children UI nodes. + * + * @param xmlNode The XML node to look for. + * @return The {@link UiElementNode} that contains xmlNode or null if not found, + */ + public UiElementNode findXmlNode(Node xmlNode) { + if (xmlNode == null) { + return null; + } + if (getXmlNode() == xmlNode) { + return this; + } + + for (UiElementNode uiChild : mUiChildren) { + UiElementNode found = uiChild.findXmlNode(xmlNode); + if (found != null) { + return found; + } + } + + return null; + } + + /** + * Returns the {@link UiAttributeNode} matching this attribute descriptor or + * null if not found. + * + * @param attr_desc The {@link AttributeDescriptor} to match. + * @return the {@link UiAttributeNode} matching this attribute descriptor or null + * if not found. + */ + public UiAttributeNode findUiAttribute(AttributeDescriptor attr_desc) { + return getInternalUiAttributes().get(attr_desc); + } + + /** + * Populate this element node with all values from the given XML node. + * + * This fails if the given XML node has a different element name -- it won't change the + * type of this ui node. + * + * This method can be both used for populating values the first time and updating values + * after the XML model changed. + * + * @param xml_node The XML node to mirror + * @return Returns true if the XML structure has changed (nodes added, removed or replaced) + */ + public boolean loadFromXmlNode(Node xml_node) { + boolean structure_changed = (mXmlNode != xml_node); + mXmlNode = xml_node; + if (xml_node != null) { + updateAttributeList(xml_node); + structure_changed |= updateElementList(xml_node); + invokeUiUpdateListeners(structure_changed ? UiUpdateState.CHILDREN_CHANGED + : UiUpdateState.ATTR_UPDATED); + } + return structure_changed; + } + + /** + * Clears the UI node and reload it from the given XML node. + *

          + * This works by clearing all references to any previous XML or UI nodes and + * then reloads the XML document from scratch. The editor reference is kept. + *

          + * This is used in the special case where the ElementDescriptor structure has changed. + * Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother + * and reload everything. This is not subtle and should be used very rarely. + * + * @param xml_node The XML node or document to reload. Can be null. + */ + public void reloadFromXmlNode(Node xml_node) { + // The editor needs to be preserved, it is not affected by an XML change. + AndroidEditor editor = getEditor(); + clearContent(); + setEditor(editor); + if (xml_node != null) { + setXmlDocument(xml_node.getOwnerDocument()); + } + // This will reload all the XML and recreate the UI structure from scratch. + loadFromXmlNode(xml_node); + } + + /** + * Called by attributes when they want to commit their value + * to an XML node. + *

          + * For mandatory nodes, this makes sure the underlying XML element node + * exists in the model. If not, it is created and assigned as the underlying + * XML node. + *
          + * For non-mandatory nodes, simply return the underlying XML node, which + * must always exists. + * + * @return The XML node matching this {@link UiElementNode} or null. + */ + public Node prepareCommit() { + if (getDescriptor().isMandatory()) { + createXmlNode(); + // The new XML node has been created. + // We don't need to refresh using loadFromXmlNode() since there are + // no attributes or elements that need to be loading into this node. + } + return getXmlNode(); + } + + /** + * Commits the attributes (all internal, inherited from UI parent & unknown attributes). + * This is called by the UI when the embedding part needs to be committed. + */ + public void commit() { + for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) { + ui_attr.commit(); + } + + for (UiAttributeNode ui_attr : mUnknownUiAttributes) { + ui_attr.commit(); + } + } + + /** + * Returns true if the part has been modified with respect to the data + * loaded from the model. + */ + public boolean isDirty() { + for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) { + if (ui_attr.isDirty()) { + return true; + } + } + + for (UiAttributeNode ui_attr : mUnknownUiAttributes) { + if (ui_attr.isDirty()) { + return true; + } + } + + return false; + } + + /** + * Creates the underlying XML element node for this UI node if it doesn't already + * exists. + * + * @return The new value of getXmlNode() (can be null if creation failed) + */ + public Node createXmlNode() { + if (mXmlNode != null) { + return null; + } + Node parentXmlNode = null; + if (mUiParent != null) { + parentXmlNode = mUiParent.prepareCommit(); + if (parentXmlNode == null) { + // The parent failed to create its own backing XML node. Abort. + // No need to throw an exception, the parent will most likely + // have done so itself. + return null; + } + } + + String element_name = getDescriptor().getXmlName(); + Document doc = getXmlDocument(); + + // We *must* have a root node. If not, we need to abort. + if (doc == null) { + throw new RuntimeException( + String.format("Missing XML document for %1$s XML node.", element_name)); + } + + // If we get here and parent_xml_node is null, the node is to be created + // as the root node of the document (which can't be null, cf check above). + if (parentXmlNode == null) { + parentXmlNode = doc; + } + + mXmlNode = doc.createElement(element_name); + + Node xmlNextSibling = null; + + UiElementNode uiNextSibling = getUiNextSibling(); + if (uiNextSibling != null) { + xmlNextSibling = uiNextSibling.getXmlNode(); + } + + parentXmlNode.insertBefore(mXmlNode, xmlNextSibling); + + // Insert a separator after the tag, to make it easier to read + Text sep = doc.createTextNode("\n"); + parentXmlNode.appendChild(sep); + + // Set all initial attributes in the XML node if they are not empty. + // Iterate on the descriptor list to get the desired order and then use the + // internal values, if any. + for (AttributeDescriptor attr_desc : getAttributeDescriptors()) { + if (attr_desc instanceof XmlnsAttributeDescriptor) { + XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attr_desc; + Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, + desc.getXmlNsName()); + attr.setValue(desc.getValue()); + attr.setPrefix(desc.getXmlNsPrefix()); + mXmlNode.getAttributes().setNamedItemNS(attr); + } else { + UiAttributeNode ui_attr = getInternalUiAttributes().get(attr_desc); + commitAttributeToXml(ui_attr, ui_attr.getCurrentValue()); + } + } + + invokeUiUpdateListeners(UiUpdateState.CREATED); + return mXmlNode; + } + + /** + * Removes the XML node corresponding to this UI node if it exists + * and also removes all mirrored information in this UI node (i.e. children, attributes) + * + * @return The removed node or null if it didn't exist in the firtst place. + */ + public Node deleteXmlNode() { + if (mXmlNode == null) { + return null; + } + + // First clear the internals of the node and *then* actually deletes the XML + // node (because doing so will generate an update even and this node may be + // revisited via loadFromXmlNode). + Node old_xml_node = mXmlNode; + clearContent(); + + Node xml_parent = old_xml_node.getParentNode(); + if (xml_parent == null) { + xml_parent = getXmlDocument(); + } + old_xml_node = xml_parent.removeChild(old_xml_node); + + invokeUiUpdateListeners(UiUpdateState.DELETED); + return old_xml_node; + } + + /** + * Updates the element list for this UiElementNode. + * At the end, the list of children UiElementNode here will match the one from the + * provided XML {@link Node}: + *

            + *
          • Walk both the current ui children list and the xml children list at the same time. + *
          • If we have a new xml child but already reached the end of the ui child list, add the + * new xml node. + *
          • Otherwise, check if the xml node is referenced later in the ui child list and if so, + * move it here. It means the XML child list has been reordered. + *
          • Otherwise, this is a new XML node that we add in the middle of the ui child list. + *
          • At the end, we may have finished walking the xml child list but still have remaining + * ui children, simply delete them as they matching trailing xml nodes that have been + * removed unless they are mandatory ui nodes. + *
          + * Note that only the first case is used when populating the ui list the first time. + * + * @param xml_node The XML node to mirror + * @return True when the XML structure has changed. + */ + protected boolean updateElementList(Node xml_node) { + boolean structure_changed = false; + int ui_index = 0; + Node xml_child = xml_node.getFirstChild(); + while (xml_child != null) { + if (xml_child.getNodeType() == Node.ELEMENT_NODE) { + String element_name = xml_child.getNodeName(); + UiElementNode ui_node = null; + if (mUiChildren.size() <= ui_index) { + // A new node is being added at the end of the list + ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name, + false /* recursive */); + if (desc == null) { + // Unknown node. Create a temporary descriptor for it. + // most important we want to auto-add unknown attributes to it. + AndroidEditor editor = getEditor(); + IEditorInput editorInput = editor.getEditorInput(); + if (editorInput instanceof IFileEditorInput) { + IFileEditorInput fileInput = (IFileEditorInput)editorInput; + desc = CustomViewDescriptorService.getInstance().getDescriptor( + fileInput.getFile().getProject(), element_name); + if (desc == null) { + desc = new ElementDescriptor(element_name); + } + } else { + desc = new ElementDescriptor(element_name); + // TODO associate a new "?" icon to this descriptor. + } + } + structure_changed = true; + ui_node = appendNewUiChild(desc); + ui_index++; + } else { + // A new node is being inserted or moved. + // Note: mandatory nodes can be created without an XML node in which case + // getXmlNode() is null. + UiElementNode ui_child; + int n = mUiChildren.size(); + for (int j = ui_index; j < n; j++) { + ui_child = mUiChildren.get(j); + if (ui_child.getXmlNode() != null && ui_child.getXmlNode() == xml_child) { + if (j > ui_index) { + // Found the same XML node at some later index, now move it here. + mUiChildren.remove(j); + mUiChildren.add(ui_index, ui_child); + structure_changed = true; + } + ui_node = ui_child; + ui_index++; + break; + } + } + + if (ui_node == null) { + // Look for an unused mandatory node with no XML node attached + // referencing the same XML element name + for (int j = ui_index; j < n; j++) { + ui_child = mUiChildren.get(j); + if (ui_child.getXmlNode() == null && + ui_child.getDescriptor().isMandatory() && + ui_child.getDescriptor().getXmlName().equals(element_name)) { + if (j > ui_index) { + // Found it, now move it here + mUiChildren.remove(j); + mUiChildren.add(ui_index, ui_child); + } + // assign the XML node to this empty mandatory element. + ui_child.mXmlNode = xml_child; + structure_changed = true; + ui_node = ui_child; + ui_index++; + } + } + } + + if (ui_node == null) { + // Inserting new node + ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name, + false /* recursive */); + if (desc == null) { + // Unknown element. Simply ignore it. + AdtPlugin.log(IStatus.WARNING, + "AndroidManifest: Ignoring unknown '%s' XML element", //$NON-NLS-1$ + element_name); + } else { + structure_changed = true; + ui_node = insertNewUiChild(ui_index, desc); + ui_index++; + } + } + } + if (ui_node != null) { + // If we touched an UI Node, even an existing one, refresh its content. + // For new nodes, this will populate them recursively. + structure_changed |= ui_node.loadFromXmlNode(xml_child); + } + } + xml_child = xml_child.getNextSibling(); + } + + // There might be extra UI nodes at the end if the XML node list got shorter. + for (int index = mUiChildren.size() - 1; index >= ui_index; --index) { + structure_changed |= removeUiChildAtIndex(index); + } + + return structure_changed; + } + + /** + * Internal helper to remove an UI child node given by its index in the + * internal child list. + * + * Also invokes the update listener on the node to be deleted. + * + * @param ui_index The index of the UI child to remove, range 0 .. mUiChildren.size()-1 + * @return True if the structure has changed + * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you + * know that could never happen unless the computer is on fire or something. + */ + private boolean removeUiChildAtIndex(int ui_index) { + UiElementNode ui_node = mUiChildren.get(ui_index); + invokeUiUpdateListeners(UiUpdateState.DELETED); + if (ui_node.getDescriptor().isMandatory()) { + // We can't remove a mandatory node, we just clear its content. + + // A mandatory node with no XML means it doesn't really exist, so it can't be + // deleted. + boolean xml_exists = (ui_node.getXmlNode() != null); + + ui_node.clearContent(); + return xml_exists; + } else { + mUiChildren.remove(ui_index); + return true; + } + } + + /** + * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} + * and appends it to the end of the element children list. + * + * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node. + * @return The new UI node that has been appended + */ + public UiElementNode appendNewUiChild(ElementDescriptor descriptor) { + UiElementNode ui_node; + ui_node = descriptor.createUiNode(); + mUiChildren.add(ui_node); + ui_node.setUiParent(this); + ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED); + return ui_node; + } + + /** + * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} + * and inserts it in the element children list at the specified position. + * + * @param index The position where to insert in the element children list. + * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node. + * @return The new UI node. + */ + public UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) { + UiElementNode ui_node; + ui_node = descriptor.createUiNode(); + mUiChildren.add(index, ui_node); + ui_node.setUiParent(this); + ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED); + return ui_node; + } + + /** + * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}. + *

          + * For a given {@link UiElementNode}, the attribute list always exists in + * full and is totally independent of whether the XML model actually + * has the corresponding attributes. + *

          + * For each attribute declared in this {@link UiElementNode}, get + * the corresponding XML attribute. It may not exist, in which case the + * value will be null. We don't really know if a value has changed, so + * the updateValue() is called on the UI sattribute in all cases. + * + * @param xmlNode The XML node to mirror + */ + protected void updateAttributeList(Node xmlNode) { + NamedNodeMap xmlAttrMap = xmlNode.getAttributes(); + HashSet visited = new HashSet(); + + // For all known (i.e. expected) UI attributes, find an existing XML attribute of + // same (uri, local name) and update the internal Ui attribute value. + for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) { + AttributeDescriptor desc = uiAttr.getDescriptor(); + if (!(desc instanceof SeparatorAttributeDescriptor)) { + Node xmlAttr = xmlAttrMap == null ? null : + xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName()); + uiAttr.updateValue(xmlAttr); + visited.add(xmlAttr); + } + } + + // Clone the current list of unknown attributes. We'll then remove from this list when + // we still attributes which are still unknown. What will be left are the old unknown + // attributes that have been deleted in the current XML attribute list. + @SuppressWarnings("unchecked") //$NON-NLS-1$ + HashSet deleted = (HashSet) mUnknownUiAttributes.clone(); + + // We need to ignore hidden attributes. + Map hiddenAttrDesc = getHiddenAttributeDescriptors(); + + // Traverse the actual XML attribute list to find unknown attributes + if (xmlAttrMap != null) { + for (int i = 0; i < xmlAttrMap.getLength(); i++) { + Node xmlAttr = xmlAttrMap.item(i); + // Ignore attributes which have actual descriptors + if (visited.contains(xmlAttr)) { + continue; + } + + String xmlFullName = xmlAttr.getNodeName(); + + // Ignore attributes which are hidden (based on the prefix:localName key) + if (hiddenAttrDesc.containsKey(xmlFullName)) { + continue; + } + + String xmlAttrLocalName = xmlAttr.getLocalName(); + String xmlNsUri = xmlAttr.getNamespaceURI(); + + UiAttributeNode uiAttr = null; + for (UiAttributeNode a : mUnknownUiAttributes) { + String aLocalName = a.getDescriptor().getXmlLocalName(); + String aNsUri = a.getDescriptor().getNamespaceUri(); + if (aLocalName.equals(xmlAttrLocalName) && + (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) { + // This attribute is still present in the unknown list + uiAttr = a; + // It has not been deleted + deleted.remove(a); + break; + } + } + if (uiAttr == null) { + // Create a new unknown attribute + TextAttributeDescriptor desc = new TextAttributeDescriptor( + xmlAttrLocalName, // xml name + xmlFullName, // ui name + xmlNsUri, // NS uri + "Unknown XML attribute"); // tooltip, translatable + uiAttr = desc.createUiNode(this); + mUnknownUiAttributes.add(uiAttr); + } + + uiAttr.updateValue(xmlAttr); + } + + // Remove from the internal list unknown attributes that have been deleted from the xml + for (UiAttributeNode a : deleted) { + mUnknownUiAttributes.remove(a); + } + } + } + + /** + * Invoke all registered {@link IUiUpdateListener} listening on this UI updates for this node. + */ + protected void invokeUiUpdateListeners(UiUpdateState state) { + if (mUiUpdateListeners != null) { + for (IUiUpdateListener listener : mUiUpdateListeners) { + try { + listener.uiElementNodeUpdated(this, state); + } catch (Exception e) { + // prevent a crashing listener from crashing the whole invocation chain + AdtPlugin.log(e, "UIElement Listener failed: %s, state=%s", //$NON-NLS-1$ + getBreadcrumbTrailDescription(true), + state.toString()); + } + } + } + } + + // --- for derived implementations only --- + + // TODO doc + protected void setXmlNode(Node xml_node) { + mXmlNode = xml_node; + } + + /** + * Sets the temporary data used by the editors. + * @param data the data. + */ + public void setEditData(Object data) { + mEditData = data; + } + + /** + * Returns the temporary data used by the editors for this object. + * @return the data, or null if none has been set. + */ + public Object getEditData() { + return mEditData; + } + + public void refreshUi() { + invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED); + } + + + // ------------- Helpers + + /** + * Helper method to commit a single attribute value to XML. + *

          + * This method updates the XML regardless of the current XML value. + * Callers should check first if an update is needed. + * If the new value is empty, the XML attribute will be actually removed. + *

          + * Note that the caller MUST ensure that modifying the underlying XML model is + * safe and must take care of marking the model as dirty if necessary. + * + * @see AndroidEditor#editXmlModel(Runnable) + * + * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode. + * @param newValue The new value to set. + * @return True if the XML attribute was modified or removed, false if nothing changed. + */ + public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) { + // Get (or create) the underlying XML element node that contains the attributes. + Node element = prepareCommit(); + if (element != null && uiAttr != null) { + String attrLocalName = uiAttr.getDescriptor().getXmlLocalName(); + String attrNsUri = uiAttr.getDescriptor().getNamespaceUri(); + + NamedNodeMap attrMap = element.getAttributes(); + if (newValue == null || newValue.length() == 0) { + // Remove attribute if it's empty + if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) { + attrMap.removeNamedItemNS(attrNsUri, attrLocalName); + return true; + } + } else { + // Add or replace an attribute + Document doc = element.getOwnerDocument(); + if (doc != null) { + Attr attr = doc.createAttributeNS(attrNsUri, attrLocalName); + attr.setValue(newValue); + attr.setPrefix(lookupNamespacePrefix(element, attrNsUri)); + attrMap.setNamedItemNS(attr); + return true; + } + } + } + return false; + } + + /** + * Helper method to commit all dirty attributes values to XML. + *

          + * This method is useful if {@link #setAttributeValue(String, String, boolean)} has been + * called more than once and all the attributes marked as dirty must be commited to the + * XML. It calls {@link #commitAttributeToXml(UiAttributeNode, String)} on each dirty + * attribute. + *

          + * Note that the caller MUST ensure that modifying the underlying XML model is + * safe and must take care of marking the model as dirty if necessary. + * + * @see AndroidEditor#editXmlModel(Runnable) + * + * @return True if one or more values were actually modified or removed, + * false if nothing changed. + */ + public boolean commitDirtyAttributesToXml() { + boolean result = false; + HashMap attributeMap = getInternalUiAttributes(); + + for (Entry entry : attributeMap.entrySet()) { + UiAttributeNode ui_attr = entry.getValue(); + if (ui_attr.isDirty()) { + result |= commitAttributeToXml(ui_attr, ui_attr.getCurrentValue()); + ui_attr.setDirty(false); + } + } + return result; + } + + /** + * Returns the namespace prefix matching the requested namespace URI. + * If no such declaration is found, returns the default "android" prefix. + * + * @param node The current node. Must not be null. + * @param nsUri The namespace URI of which the prefix is to be found, + * e.g. AndroidConstants.NS_RESOURCES + * @return The first prefix declared or the default "android" prefix. + */ + private String lookupNamespacePrefix(Node node, String nsUri) { + // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java + // The following code emulates this simple call: + // String prefix = node.lookupPrefix(AndroidConstants.NS_RESOURCES); + + // if the requested URI is null, it denotes an attribute with no namespace. + if (nsUri == null) { + return null; + } + + // per XML specification, the "xmlns" URI is reserved + if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) { + return "xmlns"; //$NON-NLS-1$ + } + + HashSet visited = new HashSet(); + Document doc = node == null ? null : node.getOwnerDocument(); + + for (; node != null && node.getNodeType() == Node.ELEMENT_NODE; + node = node.getParentNode()) { + NamedNodeMap attrs = node.getAttributes(); + for (int n = attrs.getLength() - 1; n >= 0; --n) { + Node attr = attrs.item(n); + if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$ + String uri = attr.getNodeValue(); + String nsPrefix = attr.getLocalName(); + if (AndroidConstants.NS_RESOURCES.equals(uri)) { + return nsPrefix; + } + visited.add(nsPrefix); + } + } + } + + // Use a sensible default prefix if we can't find one. + // We need to make sure the prefix is not one that was declared in the scope + // visited above. + String prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$ + String base = prefix; + for (int i = 1; visited.contains(prefix); i++) { + prefix = base + Integer.toString(i); + } + + // Also create & define this prefix/URI in the XML document as an attribute in the + // first element of the document. + if (doc != null) { + node = doc.getFirstChild(); + while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { + node = node.getNextSibling(); + } + if (node != null) { + Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, prefix); + attr.setValue(nsUri); + attr.setPrefix("xmlns"); //$NON-NLS-1$ + node.getAttributes().setNamedItemNS(attr); + } + } + + return prefix; + } + + /** + * Utility method to internally set the value of a text attribute for the current + * UiElementNode. + *

          + * This method is a helper. It silently ignores the errors such as the requested + * attribute not being present in the element or attribute not being settable. + * It accepts inherited attributes (such as layout). + *

          + * This does not commit to the XML model. It does mark the attribute node as dirty. + * This is up to the caller. + * + * @see #commitAttributeToXml(UiAttributeNode, String) + * @see #commitDirtyAttributesToXml() + * + * @param attrXmlName The XML name of the attribute to modify + * @param value The new value for the attribute. If set to null, the attribute is removed. + * @param override True if the value must be set even if one already exists. + * @return The {@link UiAttributeNode} that has been modified or null. + */ + public UiAttributeNode setAttributeValue(String attrXmlName, String value, boolean override) { + HashMap attributeMap = getInternalUiAttributes(); + + if (value == null) { + value = ""; //$NON-NLS-1$ -- this removes an attribute + } + + for (Entry entry : attributeMap.entrySet()) { + AttributeDescriptor ui_desc = entry.getKey(); + if (ui_desc.getXmlLocalName().equals(attrXmlName)) { + UiAttributeNode ui_attr = entry.getValue(); + // Not all attributes are editable, ignore those which are not + if (ui_attr instanceof IUiSettableAttributeNode) { + String current = ui_attr.getCurrentValue(); + // Only update (and mark as dirty) if the attribute did not have any + // value or if the value was different. + if (override || current == null || !current.equals(value)) { + ((IUiSettableAttributeNode) ui_attr).setCurrentValue(value); + // mark the attribute as dirty since their internal content + // as been modified, but not the underlying XML model + ui_attr.setDirty(true); + return ui_attr; + } + } + break; + } + } + return null; + } + + /** + * Utility method to retrieve the internal value of an attribute. + *

          + * Note that this retrieves the *field* value if the attribute has some UI, and + * not the actual XML value. They may differ if the attribute is dirty. + * + * @param attrXmlName The XML name of the attribute to modify + * @return The current internal value for the attribute or null in case of error. + */ + public String getAttributeValue(String attrXmlName) { + HashMap attributeMap = getInternalUiAttributes(); + + for (Entry entry : attributeMap.entrySet()) { + AttributeDescriptor ui_desc = entry.getKey(); + if (ui_desc.getXmlLocalName().equals(attrXmlName)) { + UiAttributeNode ui_attr = entry.getValue(); + return ui_attr.getCurrentValue(); + } + } + return null; + } + + // ------ IPropertySource methods + + public Object getEditableValue() { + return null; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors() + * + * Returns the property descriptor for this node. Since the descriptors are not linked to the + * data, the AttributeDescriptor are used directly. + */ + public IPropertyDescriptor[] getPropertyDescriptors() { + List propDescs = new ArrayList(); + + // get the standard descriptors + HashMap attributeMap = getInternalUiAttributes(); + Set keys = attributeMap.keySet(); + + + // we only want the descriptor that do implement the IPropertyDescriptor interface. + for (AttributeDescriptor key : keys) { + if (key instanceof IPropertyDescriptor) { + propDescs.add((IPropertyDescriptor)key); + } + } + + // now get the descriptor from the unknown attributes + for (UiAttributeNode unknownNode : mUnknownUiAttributes) { + if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) { + propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor()); + } + } + + // TODO cache this maybe, as it's not going to change (except for unknown descriptors) + return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]); + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object) + * + * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(), + * which return the AttributeDescriptor itself. + */ + public Object getPropertyValue(Object id) { + HashMap attributeMap = getInternalUiAttributes(); + + UiAttributeNode attribute = attributeMap.get(id); + + if (attribute == null) { + // look for the id in the unknown attributes. + for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { + if (id == unknownAttr.getDescriptor()) { + return unknownAttr; + } + } + } + + return attribute; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object) + * + * Returns whether the property is set. In our case this is if the string is non empty. + */ + public boolean isPropertySet(Object id) { + HashMap attributeMap = getInternalUiAttributes(); + + UiAttributeNode attribute = attributeMap.get(id); + + if (attribute != null) { + return attribute.getCurrentValue().length() > 0; + } + + // look for the id in the unknown attributes. + for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { + if (id == unknownAttr.getDescriptor()) { + return unknownAttr.getCurrentValue().length() > 0; + } + } + + return false; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object) + * + * Reset the property to its default value. For now we simply empty it. + */ + public void resetPropertyValue(Object id) { + HashMap attributeMap = getInternalUiAttributes(); + + UiAttributeNode attribute = attributeMap.get(id); + if (attribute != null) { + // TODO: reset the value of the attribute + + return; + } + + // look for the id in the unknown attributes. + for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { + if (id == unknownAttr.getDescriptor()) { + // TODO: reset the value of the attribute + + return; + } + } + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object) + * + * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the + * AttributeDescriptor itself. Value should be a String. + */ + public void setPropertyValue(Object id, Object value) { + HashMap attributeMap = getInternalUiAttributes(); + + UiAttributeNode attribute = attributeMap.get(id); + + if (attribute == null) { + // look for the id in the unknown attributes. + for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { + if (id == unknownAttr.getDescriptor()) { + attribute = unknownAttr; + break; + } + } + } + + if (attribute != null) { + final UiAttributeNode fAttribute = attribute; + + // get the current value and compare it to the new value + String oldValue = fAttribute.getCurrentValue(); + final String newValue = (String)value; + + if (oldValue.equals(newValue)) { + return; + } + + AndroidEditor editor = getEditor(); + editor.editXmlModel(new Runnable() { + public void run() { + commitAttributeToXml(fAttribute, newValue); + } + }); + } + } + + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java new file mode 100644 index 0000000..ddcf0a0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java @@ -0,0 +1,308 @@ +/* + * 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.editors.uimodel; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.FlagAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Rectangle; +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.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.SelectionStatusDialog; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Represents an XML attribute that is defined by a set of flag values, + * i.e. enum names separated by pipe (|) characters. + * + * Note: in Android resources, a "flag" is a list of fixed values where one or + * more values can be selected using an "or", e.g. "align='left|top'". + * By contrast, an "enum" is a list of fixed values of which only one can be + * selected at a given time, e.g. "gravity='right'". + *

          + * This class handles the "flag" case. + * The "enum" case is done using {@link UiListAttributeNode}. + */ +public class UiFlagAttributeNode extends UiTextAttributeNode { + + public UiFlagAttributeNode(FlagAttributeDescriptor attributeDescriptor, + UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public void createUiControl(Composite parent, IManagedForm managedForm) { + setManagedForm(managedForm); + FormToolkit toolkit = managedForm.getToolkit(); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + + Label label = toolkit.createLabel(parent, desc.getUiName()); + label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); + + Composite composite = toolkit.createComposite(parent); + composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = gl.marginWidth = 0; + composite.setLayout(gl); + // Fixes missing text borders under GTK... also requires adding a 1-pixel margin + // for the text field below + toolkit.paintBordersFor(composite); + + final Text text = toolkit.createText(composite, getCurrentValue()); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK + text.setLayoutData(gd); + final Button selectButton = toolkit.createButton(composite, "Select...", SWT.PUSH); + + setTextWidget(text); + + selectButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + + String currentText = getTextWidgetValue(); + + String result = showDialog(selectButton.getShell(), currentText); + + if (result != null) { + setTextWidgetValue(result); + } + } + }); + } + + /** + * Get the flag names, either from the initial names set in the attribute + * or by querying the framework resource parser. + */ + @Override + public String[] getPossibleValues() { + String attr_name = getDescriptor().getXmlLocalName(); + String element_name = getUiParent().getDescriptor().getXmlName(); + + String[] values = null; + + if (getDescriptor() instanceof FlagAttributeDescriptor && + ((FlagAttributeDescriptor) getDescriptor()).getNames() != null) { + // Get enum values from the descriptor + values = ((FlagAttributeDescriptor) getDescriptor()).getNames(); + } + + if (values == null) { + // or from the AndroidTargetData + UiElementNode uiNode = getUiParent(); + AndroidEditor editor = uiNode.getEditor(); + AndroidTargetData data = editor.getTargetData(); + if (data != null) { + values = data.getAttributeValues(element_name, attr_name); + } + } + + return values; + } + + /** + * Shows a dialog letting the user choose a set of enum, and returns a string + * containing the result. + */ + public String showDialog(Shell shell, String currentValue) { + FlagSelectionDialog dlg = new FlagSelectionDialog( + shell, currentValue.trim().split("\\s*\\|\\s*")); //$NON-NLS-1$ + dlg.open(); + Object[] result = dlg.getResult(); + if (result != null) { + StringBuilder buf = new StringBuilder(); + for (Object name : result) { + if (name instanceof String) { + if (buf.length() > 0) { + buf.append("|"); //$NON-NLS-1$ + } + buf.append(name); + } + } + + return buf.toString(); + } + + return null; + + } + + /** + * Displays a list of flag names with checkboxes. + */ + private class FlagSelectionDialog extends SelectionStatusDialog { + + private Set mCurrentSet; + private Table mTable; + + public FlagSelectionDialog(Shell parentShell, String[] currentNames) { + super(parentShell); + + mCurrentSet = new HashSet(); + for (String name : currentNames) { + if (name.length() > 0) { + mCurrentSet.add(name); + } + } + + int shellStyle = getShellStyle(); + setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); + } + + @Override + protected void computeResult() { + if (mTable != null) { + ArrayList results = new ArrayList(); + + for (TableItem item : mTable.getItems()) { + if (item.getChecked()) { + results.add((String)item.getData()); + } + } + + setResult(results); + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite= new Composite(parent, SWT.NONE); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + composite.setLayout(new GridLayout(1, true)); + composite.setFont(parent.getFont()); + + Label label = new Label(composite, SWT.NONE); + label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + label.setText(String.format("Select the flag values for attribute %1$s:", + ((FlagAttributeDescriptor) getDescriptor()).getUiName())); + + mTable = new Table(composite, SWT.CHECK | SWT.BORDER); + GridData data = new GridData(); + // The 60,18 hints are the ones used by AbstractElementListSelectionDialog + data.widthHint = convertWidthInCharsToPixels(60); + data.heightHint = convertHeightInCharsToPixels(18); + data.grabExcessVerticalSpace = true; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.FILL; + mTable.setLayoutData(data); + + mTable.setHeaderVisible(false); + final TableColumn column = new TableColumn(mTable, SWT.NONE); + + // List all the expected flag names and check those which are currently used + String[] names = getPossibleValues(); + if (names != null) { + for (String name : names) { + TableItem item = new TableItem(mTable, SWT.NONE); + item.setText(name); + item.setData(name); + + boolean hasName = mCurrentSet.contains(name); + item.setChecked(hasName); + if (hasName) { + mCurrentSet.remove(name); + } + } + } + + // If there are unknown flag names currently used, display them at the end if the + // table already checked. + if (!mCurrentSet.isEmpty()) { + FontDescriptor fontDesc = JFaceResources.getDialogFontDescriptor(); + fontDesc = fontDesc.withStyle(SWT.ITALIC); + Font font = fontDesc.createFont(JFaceResources.getDialogFont().getDevice()); + + for (String name : mCurrentSet) { + TableItem item = new TableItem(mTable, SWT.NONE); + item.setText(String.format("%1$s (unknown flag)", name)); + item.setData(name); + item.setChecked(true); + item.setFont(font); + } + } + + // Add a listener that will resize the column to the full width of the table + // so that only one column appears in the table even if the dialog is resized. + ControlAdapter listener = new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = mTable.getClientArea(); + column.setWidth(r.width); + } + }; + + mTable.addControlListener(listener); + listener.controlResized(null /* event not used */); + + // Add a selection listener that will check/uncheck items when they are double-clicked + mTable.addSelectionListener(new SelectionAdapter() { + /** Default selection means double-click on "most" platforms */ + @Override + public void widgetDefaultSelected(SelectionEvent e) { + if (e.item instanceof TableItem) { + TableItem i = (TableItem) e.item; + i.setChecked(!i.getChecked()); + } + super.widgetDefaultSelected(e); + } + }); + + Dialog.applyDialogFont(composite); + setHelpAvailable(false); + + return composite; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java new file mode 100644 index 0000000..aaad0ce --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; + +/** + * Represents an XML attribute which has possible built-in values, and can be modified by + * an editable Combo box. + *

          + * See {@link UiTextAttributeNode} for more information. + */ +public class UiListAttributeNode extends UiAbstractTextAttributeNode { + + protected Combo mCombo; + + public UiListAttributeNode(ListAttributeDescriptor attributeDescriptor, + UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public final void createUiControl(final Composite parent, IManagedForm managedForm) { + FormToolkit toolkit = managedForm.getToolkit(); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + + Label label = toolkit.createLabel(parent, desc.getUiName()); + label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); + + int style = SWT.DROP_DOWN; + mCombo = new Combo(parent, style); + TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE); + twd.maxWidth = 100; + mCombo.setLayoutData(twd); + + fillCombo(); + + setTextWidgetValue(getCurrentValue()); + + mCombo.addModifyListener(new ModifyListener() { + /** + * Sent when the text is modified, whether by the user via manual + * input or programmatic input via setText(). + *

          + * Simply mark the attribute as dirty if it really changed. + * The container SectionPart will collect these flag and manage them. + */ + public void modifyText(ModifyEvent e) { + if (!isInInternalTextModification() && + !isDirty() && + mCombo != null && + getCurrentValue() != null && + !mCombo.getText().equals(getCurrentValue())) { + setDirty(true); + } + } + }); + + // Remove self-reference when the widget is disposed + mCombo.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + mCombo = null; + } + }); + } + + protected void fillCombo() { + String[] values = getPossibleValues(); + + if (values == null) { + AdtPlugin.log(IStatus.ERROR, + "FrameworkResourceManager did not provide values yet for %1$s", + getDescriptor().getXmlLocalName()); + } else { + for (String value : values) { + mCombo.add(value); + } + } + } + + /** + * Get the list values, either from the initial values set in the attribute + * or by querying the framework resource parser. + */ + @Override + public String[] getPossibleValues() { + AttributeDescriptor descriptor = getDescriptor(); + UiElementNode uiParent = getUiParent(); + + String attr_name = descriptor.getXmlLocalName(); + String element_name = uiParent.getDescriptor().getXmlName(); + + // FrameworkResourceManager expects a specific prefix for the attribute. + String prefix = ""; + if (AndroidConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) { + prefix = "android:"; //$NON-NLS-1$ + } else if (XmlnsAttributeDescriptor.XMLNS_URI.equals(descriptor.getNamespaceUri())) { + prefix = "xmlns:"; //$NON-NLS-1$ + } + attr_name = prefix + attr_name; + + String[] values = null; + + if (descriptor instanceof ListAttributeDescriptor && + ((ListAttributeDescriptor) descriptor).getValues() != null) { + // Get enum values from the descriptor + values = ((ListAttributeDescriptor) descriptor).getValues(); + } + + if (values == null) { + // or from the AndroidTargetData + UiElementNode uiNode = getUiParent(); + AndroidEditor editor = uiNode.getEditor(); + AndroidTargetData data = editor.getTargetData(); + if (data != null) { + // get the great-grand-parent descriptor. + + // the parent should always exist. + UiElementNode grandParentNode = uiParent.getUiParent(); + + String greatGrandParentNodeName = null; + if (grandParentNode != null) { + UiElementNode greatGrandParentNode = grandParentNode.getUiParent(); + if (greatGrandParentNode != null) { + greatGrandParentNodeName = + greatGrandParentNode.getDescriptor().getXmlName(); + } + } + + values = data.getAttributeValues(element_name, attr_name, greatGrandParentNodeName); + } + } + + return values; + } + + @Override + public String getTextWidgetValue() { + if (mCombo != null) { + return mCombo.getText(); + } + + return null; + } + + @Override + public final boolean isValid() { + return mCombo != null; + } + + @Override + public void setTextWidgetValue(String value) { + if (mCombo != null) { + mCombo.setText(value); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java new file mode 100644 index 0000000..1c1e1bd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; +import com.android.ide.eclipse.editors.ui.SectionHelper; +import com.android.ide.eclipse.editors.wizards.ReferenceChooserDialog; +import com.android.ide.eclipse.editors.wizards.ResourceChooser; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.window.Window; +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.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; + +/** + * Represents an XML attribute for a resource that can be modified using a simple text field or + * a dialog to choose an existing resource. + *

          + * It can be configured to represent any kind of resource, by providing the desired + * {@link ResourceType} in the constructor. + *

          + * See {@link UiTextAttributeNode} for more information. + */ +public class UiResourceAttributeNode extends UiTextAttributeNode { + + private ResourceType mType; + + public UiResourceAttributeNode(ResourceType type, + AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + + mType = type; + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public void createUiControl(final Composite parent, IManagedForm managedForm) { + setManagedForm(managedForm); + FormToolkit toolkit = managedForm.getToolkit(); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + + Label label = toolkit.createLabel(parent, desc.getUiName()); + label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); + SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); + + Composite composite = toolkit.createComposite(parent); + composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); + GridLayout gl = new GridLayout(2, false); + gl.marginHeight = gl.marginWidth = 0; + composite.setLayout(gl); + // Fixes missing text borders under GTK... also requires adding a 1-pixel margin + // for the text field below + toolkit.paintBordersFor(composite); + + final Text text = toolkit.createText(composite, getCurrentValue()); + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK + text.setLayoutData(gd); + Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); + + setTextWidget(text); + + // TODO Add a validator using onAddModifyListener + + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + String result = showDialog(parent.getShell(), text.getText().trim()); + if (result != null) { + text.setText(result); + } + } + }); + } + + /** + * Shows a dialog letting the user choose a set of enum, and returns a string + * containing the result. + */ + public String showDialog(Shell shell, String currentValue) { + // we need to get the project of the file being edited. + UiElementNode uiNode = getUiParent(); + AndroidEditor editor = uiNode.getEditor(); + IProject project = editor.getProject(); + if (project != null) { + // get the resource repository for this project and the system resources. + IResourceRepository projectRepository = + ResourceManager.getInstance().getProjectResources(project); + + if (mType != null) { + // get the Target Data to get the system resources + AndroidTargetData data = editor.getTargetData(); + IResourceRepository systemRepository = data.getSystemResources(); + + // open a resource chooser dialog for specified resource type. + ResourceChooser dlg = new ResourceChooser(mType, + projectRepository, systemRepository, shell); + + dlg.setCurrentResource(currentValue); + + if (dlg.open() == Window.OK) { + return dlg.getCurrentResource(); + } + } else { + ReferenceChooserDialog dlg = new ReferenceChooserDialog(projectRepository, + shell); + + dlg.setCurrentResource(currentValue); + + if (dlg.open() == Window.OK) { + return dlg.getCurrentResource(); + } + } + } + + return null; + } + + @Override + public String[] getPossibleValues() { + // TODO: compute a list of existing resources for content assist completion + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java new file mode 100644 index 0000000..192f752 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java @@ -0,0 +1,142 @@ +/* + * 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.editors.uimodel; + +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; + +import org.eclipse.swt.SWT; +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.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.TableWrapData; +import org.eclipse.ui.forms.widgets.TableWrapLayout; +import org.w3c.dom.Node; + +/** + * {@link UiSeparatorAttributeNode} does not represent any real attribute. + *

          + * It is used to separate groups of attributes visually. + */ +public class UiSeparatorAttributeNode extends UiAttributeNode { + + /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} */ + public UiSeparatorAttributeNode(SeparatorAttributeDescriptor attrDesc, + UiElementNode uiParent) { + super(attrDesc, uiParent); + } + + /** Returns the current value of the node. */ + @Override + public String getCurrentValue() { + // There is no value here. + return null; + } + + /** + * Sets whether the attribute is dirty and also notifies the editor some part's dirty + * flag as changed. + *

          + * Subclasses should set the to true as a result of user interaction with the widgets in + * the section and then should set to false when the commit() method completed. + */ + @Override + public void setDirty(boolean isDirty) { + // This is never dirty. + } + + /** + * Called once by the parent user interface to creates the necessary + * user interface to edit this attribute. + *

          + * This method can be called more than once in the life cycle of an UI node, + * typically when the UI is part of a master-detail tree, as pages are swapped. + * + * @param parent The composite where to create the user interface. + * @param managedForm The managed form owning this part. + */ + @Override + public void createUiControl(Composite parent, IManagedForm managedForm) { + FormToolkit toolkit = managedForm.getToolkit(); + Composite row = toolkit.createComposite(parent); + + TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); + if (parent.getLayout() instanceof TableWrapLayout) { + twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; + } + row.setLayoutData(twd); + row.setLayout(new GridLayout(3, false /* equal width */)); + + Label sep = toolkit.createSeparator(row, SWT.HORIZONTAL); + GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false); + gd.widthHint = 16; + sep.setLayoutData(gd); + + Label label = toolkit.createLabel(row, getDescriptor().getXmlLocalName()); + label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); + + sep = toolkit.createSeparator(row, SWT.HORIZONTAL); + sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + } + + /** No completion values for this UI attribute. */ + @Override + public String[] getPossibleValues() { + return null; + } + + /** + * Called when the XML is being loaded or has changed to + * update the value held by this user interface attribute node. + *

          + * The XML Node may be null, which denotes that the attribute is not + * specified in the XML model. In general, this means the "default" value of the + * attribute should be used. + *

          + * The caller doesn't really know if attributes have changed, + * so it will call this to refresh the attribute anyway. It's up to the + * UI implementation to minimize refreshes. + * + * @param xml_attribute_node + */ + @Override + public void updateValue(Node xml_attribute_node) { + // No value to update. + } + + /** + * Called by the user interface when the editor is saved or its state changed + * and the modified attributes must be committed (i.e. written) to the XML model. + *

          + * Important behaviors: + *

            + *
          • The caller *must* have called IStructuredModel.aboutToChangeModel before. + * The implemented methods must assume it is safe to modify the XML model. + *
          • On success, the implementation *must* call setDirty(false). + *
          • On failure, the implementation can fail with an exception, which + * is trapped and logged by the caller, or do nothing, whichever is more + * appropriate. + *
          + */ + @Override + public void commit() { + // No value to commit. + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java new file mode 100644 index 0000000..4c53f4c --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; +import com.android.ide.eclipse.editors.ui.SectionHelper; + +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.widgets.TableWrapData; + +/** + * Represents an XML attribute in that can be modified using a simple text field + * in the XML editor's user interface. + *

          + * The XML attribute has no default value. When unset, the text field is blank. + * When updating the XML, if the field is empty, the attribute will be removed + * from the XML element. + *

          + * See {@link UiAttributeNode} for more information. + */ +public class UiTextAttributeNode extends UiAbstractTextAttributeNode { + + /** Text field */ + private Text mText; + /** The managed form, set only once createUiControl has been called. */ + private IManagedForm mManagedForm; + + public UiTextAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /* (non-java doc) + * Creates a label widget and an associated text field. + *

          + * As most other parts of the android manifest editor, this assumes the + * parent uses a table layout with 2 columns. + */ + @Override + public void createUiControl(Composite parent, IManagedForm managedForm) { + setManagedForm(managedForm); + TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); + Text text = SectionHelper.createLabelAndText(parent, managedForm.getToolkit(), + desc.getUiName(), getCurrentValue(), + DescriptorsUtils.formatTooltip(desc.getTooltip())); + + setTextWidget(text); + } + + /** No completion values for this UI attribute. */ + @Override + public String[] getPossibleValues() { + return null; + } + + /** + * Sets the internal managed form. + * This is usually set by createUiControl. + */ + protected void setManagedForm(IManagedForm managedForm) { + mManagedForm = managedForm; + } + + /** + * @return The managed form, set only once createUiControl has been called. + */ + protected IManagedForm getManagedForm() { + return mManagedForm; + } + + /* (non-java doc) + * Returns if the attribute node is valid, and its UI has been created. + */ + @Override + public boolean isValid() { + return mText != null; + } + + @Override + public String getTextWidgetValue() { + if (mText != null) { + return mText.getText(); + } + + return null; + } + + @Override + public void setTextWidgetValue(String value) { + if (mText != null) { + mText.setText(value); + } + } + + /** + * Sets the Text widget object, and prepares it to handle modification and synchronization + * with the XML node. + * @param textWidget + */ + protected final void setTextWidget(Text textWidget) { + mText = textWidget; + + if (textWidget != null) { + // Sets the with hint for the text field. Derived classes can always override it. + // This helps the grid layout to resize correctly on smaller screen sizes. + Object data = textWidget.getLayoutData(); + if (data == null) { + } else if (data instanceof GridData) { + ((GridData)data).widthHint = AndroidEditor.TEXT_WIDTH_HINT; + } else if (data instanceof TableWrapData) { + ((TableWrapData)data).maxWidth = 100; + } + + mText.addModifyListener(new ModifyListener() { + /** + * Sent when the text is modified, whether by the user via manual + * input or programmatic input via setText(). + *

          + * Simply mark the attribute as dirty if it really changed. + * The container SectionPart will collect these flag and manage them. + */ + public void modifyText(ModifyEvent e) { + if (!isInInternalTextModification() && + !isDirty() && + mText != null && + getCurrentValue() != null && + !mText.getText().equals(getCurrentValue())) { + setDirty(true); + } + } + }); + + // Remove self-reference when the widget is disposed + mText.addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + mText = null; + } + }); + } + + onAddValidators(mText); + } + + /** + * Called after the text widget as been created. + *

          + * Derived classes typically want to: + *

        • Create a new {@link ModifyListener} and attach it to the given {@link Text} widget. + *
        • In the modify listener, call getManagedForm().getMessageManager().addMessage() + * and getManagedForm().getMessageManager().removeMessage() as necessary. + *
        • Call removeMessage in a new text.addDisposeListener. + *
        • Call the validator once to setup the initial messages as needed. + *

          + * The base implementation does nothing. + * + * @param text The {@link Text} widget to validate. + */ + protected void onAddValidators(Text text) { + } + + /** + * Returns the text widget. + */ + protected final Text getTextWidget() { + return mText; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java new file mode 100644 index 0000000..5c1db05 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2007 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.editors.uimodel; + +import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.Text; + +/** + * Represents an XML element value in that can be modified using a simple text field + * in the XML editor's user interface. + */ +public class UiTextValueNode extends UiTextAttributeNode { + + public UiTextValueNode(TextValueDescriptor attributeDescriptor, UiElementNode uiParent) { + super(attributeDescriptor, uiParent); + } + + /** + * Updates the current text field's value when the XML has changed. + *

          + * The caller doesn't really know if value of the element has changed, + * so it will call this to refresh the value anyway. The value + * is only set if it has changed. + *

          + * This also resets the "dirty" flag. + */ + @Override + public void updateValue(Node xml_attribute_node) { + setCurrentValue(DEFAULT_VALUE); + + // The argument xml_attribute_node is not used here. It should always be + // null since this is not an attribute. What we want is the "text value" of + // the parent element, which is actually the first text node of the element. + + UiElementNode parent = getUiParent(); + if (parent != null) { + Node xml_node = parent.getXmlNode(); + if (xml_node != null) { + for (Node xml_child = xml_node.getFirstChild(); + xml_child != null; + xml_child = xml_child.getNextSibling()) { + if (xml_child.getNodeType() == Node.TEXT_NODE) { + setCurrentValue(xml_child.getNodeValue()); + break; + } + } + } + } + + if (isValid() && !getTextWidgetValue().equals(getCurrentValue())) { + try { + setInInternalTextModification(true); + setTextWidgetValue(getCurrentValue()); + setDirty(false); + } finally { + setInInternalTextModification(false); + } + } + } + + /* (non-java doc) + * Called by the user interface when the editor is saved or its state changed + * and the modified "attributes" must be committed (i.e. written) to the XML model. + */ + @Override + public void commit() { + UiElementNode parent = getUiParent(); + if (parent != null && isValid() && isDirty()) { + // Get (or create) the underlying XML element node that contains the value. + Node element = parent.prepareCommit(); + if (element != null) { + String value = getTextWidgetValue(); + + // Try to find an existing text child to update. + boolean updated = false; + + for (Node xml_child = element.getFirstChild(); + xml_child != null; + xml_child = xml_child.getNextSibling()) { + if (xml_child.getNodeType() == Node.TEXT_NODE) { + xml_child.setNodeValue(value); + updated = true; + break; + } + } + + // If we didn't find a text child to update, we need to create one. + if (!updated) { + Document doc = element.getOwnerDocument(); + if (doc != null) { + Text text = doc.createTextNode(value); + element.appendChild(text); + } + } + + setCurrentValue(value); + } + } + setDirty(false); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java new file mode 100644 index 0000000..b7dffdd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java @@ -0,0 +1,1279 @@ +/* + * 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.editors.wizards; + +import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier; +import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; +import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier; +import com.android.ide.eclipse.editors.resources.configurations.NetworkCodeQualifier; +import com.android.ide.eclipse.editors.resources.configurations.PixelDensityQualifier; +import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; +import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.editors.resources.configurations.ScreenDimensionQualifier; +import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier; +import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier; +import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier; +import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier.KeyboardState; +import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier.NavigationMethod; +import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier.ScreenOrientation; +import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier.TextInputMethod; +import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier.TouchScreenType; +import com.android.ide.eclipse.editors.resources.manager.ResourceManager; + +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; +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; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +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 java.util.HashMap; + +/** + * Custom UI widget to let user build a Folder configuration. + *

          + * To use this, instantiate somewhere in the UI and then: + *

            + *
          • Use {@link #setConfiguration(String)} or {@link #setConfiguration(FolderConfiguration)}. + *
          • Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)} and + * test it using {@link FolderConfiguration#isValid()}. + *
          + */ +public class ConfigurationSelector extends Composite { + + public static final int WIDTH_HINT = 600; + public static final int HEIGHT_HINT = 250; + + private Runnable mOnChangeListener; + + private TableViewer mFullTableViewer; + private TableViewer mSelectionTableViewer; + private Button mAddButton; + private Button mRemoveButton; + private StackLayout mStackLayout; + + private boolean mOnRefresh = false; + + private final FolderConfiguration mBaseConfiguration = new FolderConfiguration(); + private final FolderConfiguration mSelectedConfiguration = new FolderConfiguration(); + + private final HashMap, QualifierEditBase> mUiMap = + new HashMap, QualifierEditBase>(); + private Composite mQualifierEditParent; + + /** + * Basic of {@link VerifyListener} to only accept digits. + */ + private static class DigitVerifier implements VerifyListener { + public void verifyText(VerifyEvent e) { + // check for digit only. + for (int i = 0 ; i < e.text.length(); i++) { + char letter = e.text.charAt(i); + if (letter < '0' || letter > '9') { + e.doit = false; + return; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for Country Code qualifiers. + */ + public static class MobileCodeVerifier extends DigitVerifier { + @Override + public void verifyText(VerifyEvent e) { + super.verifyText(e); + + // basic tests passed? + if (e.doit) { + // check the max 3 digits. + if (e.text.length() - e.end + e.start + + ((Text)e.getSource()).getText().length() > 3) { + e.doit = false; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for the Language and Region qualifiers. + */ + public static class LanguageRegionVerifier implements VerifyListener { + public void verifyText(VerifyEvent e) { + // check for length + if (e.text.length() - e.end + e.start + ((Combo)e.getSource()).getText().length() > 2) { + e.doit = false; + return; + } + + // check for lower case only. + for (int i = 0 ; i < e.text.length(); i++) { + char letter = e.text.charAt(i); + if ((letter < 'a' || letter > 'z') && (letter < 'A' || letter > 'Z')) { + e.doit = false; + return; + } + } + } + } + + /** + * Implementation of {@link VerifyListener} for the Pixel Density qualifier. + */ + public static class DensityVerifier extends DigitVerifier { } + + /** + * Implementation of {@link VerifyListener} for the Screen Dimension qualifier. + */ + public static class DimensionVerifier extends DigitVerifier { } + + /** + * Enum for the state of the configuration being created. + */ + public enum ConfigurationState { + OK, INVALID_CONFIG, REGION_WITHOUT_LANGUAGE; + } + + public ConfigurationSelector(Composite parent) { + super(parent, SWT.NONE); + + mBaseConfiguration.createDefault(); + + GridLayout gl = new GridLayout(4, false); + gl.marginWidth = gl.marginHeight = 0; + setLayout(gl); + + // first column is the first table + final Table fullTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); + fullTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + fullTable.setHeaderVisible(true); + fullTable.setLinesVisible(true); + + // create the column + final TableColumn fullTableColumn = new TableColumn(fullTable, SWT.LEFT); + // set the header + fullTableColumn.setText("Available Qualifiers"); + + fullTable.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = fullTable.getClientArea(); + fullTableColumn.setWidth(r.width); + } + }); + + mFullTableViewer = new TableViewer(fullTable); + mFullTableViewer.setContentProvider(new QualifierContentProvider()); + mFullTableViewer.setLabelProvider(new QualifierLabelProvider( + false /* showQualifierValue */)); + mFullTableViewer.setInput(mBaseConfiguration); + mFullTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection structSelection = (IStructuredSelection)selection; + Object first = structSelection.getFirstElement(); + + if (first instanceof ResourceQualifier) { + mAddButton.setEnabled(true); + return; + } + } + + mAddButton.setEnabled(false); + } + }); + + // 2nd column is the left/right arrow button + Composite buttonComposite = new Composite(this, SWT.NONE); + gl = new GridLayout(1, false); + gl.marginWidth = gl.marginHeight = 0; + buttonComposite.setLayout(gl); + buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + + new Composite(buttonComposite, SWT.NONE); + mAddButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); + mAddButton.setText("->"); + mAddButton.setEnabled(false); + mAddButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection selection = + (IStructuredSelection)mFullTableViewer.getSelection(); + + Object first = selection.getFirstElement(); + if (first instanceof ResourceQualifier) { + ResourceQualifier qualifier = (ResourceQualifier)first; + + mBaseConfiguration.removeQualifier(qualifier); + mSelectedConfiguration.addQualifier(qualifier); + + mFullTableViewer.refresh(); + mSelectionTableViewer.refresh(); + mSelectionTableViewer.setSelection(new StructuredSelection(qualifier), true); + + onChange(false /* keepSelection */); + } + } + }); + + mRemoveButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); + mRemoveButton.setText("<-"); + mRemoveButton.setEnabled(false); + mRemoveButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection selection = + (IStructuredSelection)mSelectionTableViewer.getSelection(); + + Object first = selection.getFirstElement(); + if (first instanceof ResourceQualifier) { + ResourceQualifier qualifier = (ResourceQualifier)first; + + mSelectedConfiguration.removeQualifier(qualifier); + mBaseConfiguration.addQualifier(qualifier); + + mFullTableViewer.refresh(); + mSelectionTableViewer.refresh(); + + onChange(false /* keepSelection */); + } + } + }); + + // 3rd column is the selected config table + final Table selectionTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); + selectionTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + selectionTable.setHeaderVisible(true); + selectionTable.setLinesVisible(true); + + // create the column + final TableColumn selectionTableColumn = new TableColumn(selectionTable, SWT.LEFT); + // set the header + selectionTableColumn.setText("Chosen Qualifiers"); + + selectionTable.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = selectionTable.getClientArea(); + selectionTableColumn.setWidth(r.width); + } + }); + mSelectionTableViewer = new TableViewer(selectionTable); + mSelectionTableViewer.setContentProvider(new QualifierContentProvider()); + mSelectionTableViewer.setLabelProvider(new QualifierLabelProvider( + true /* showQualifierValue */)); + mSelectionTableViewer.setInput(mSelectedConfiguration); + mSelectionTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + // ignore selection changes during resfreshes in some cases. + if (mOnRefresh) { + return; + } + + ISelection selection = event.getSelection(); + if (selection instanceof IStructuredSelection) { + IStructuredSelection structSelection = (IStructuredSelection)selection; + + if (structSelection.isEmpty() == false) { + Object first = structSelection.getFirstElement(); + + if (first instanceof ResourceQualifier) { + mRemoveButton.setEnabled(true); + + QualifierEditBase composite = mUiMap.get(first.getClass()); + + if (composite != null) { + composite.setQualifier((ResourceQualifier)first); + } + + mStackLayout.topControl = composite; + mQualifierEditParent.layout(); + + return; + } + } else { + mStackLayout.topControl = null; + mQualifierEditParent.layout(); + } + } + + mRemoveButton.setEnabled(false); + } + }); + + // 4th column is the detail of the selected qualifier + mQualifierEditParent = new Composite(this, SWT.NONE); + mQualifierEditParent.setLayout(mStackLayout = new StackLayout()); + mQualifierEditParent.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + + // create the UI for all the qualifiers, and associate them to the ResourceQualifer class. + mUiMap.put(CountryCodeQualifier.class, new MCCEdit(mQualifierEditParent)); + mUiMap.put(NetworkCodeQualifier.class, new MNCEdit(mQualifierEditParent)); + mUiMap.put(LanguageQualifier.class, new LanguageEdit(mQualifierEditParent)); + mUiMap.put(RegionQualifier.class, new RegionEdit(mQualifierEditParent)); + mUiMap.put(ScreenOrientationQualifier.class, new OrientationEdit(mQualifierEditParent)); + mUiMap.put(PixelDensityQualifier.class, new PixelDensityEdit(mQualifierEditParent)); + mUiMap.put(TouchScreenQualifier.class, new TouchEdit(mQualifierEditParent)); + mUiMap.put(KeyboardStateQualifier.class, new KeyboardEdit(mQualifierEditParent)); + mUiMap.put(TextInputMethodQualifier.class, new TextInputEdit(mQualifierEditParent)); + mUiMap.put(NavigationMethodQualifier.class, new NavigationEdit(mQualifierEditParent)); + mUiMap.put(ScreenDimensionQualifier.class, new ScreenDimensionEdit(mQualifierEditParent)); + } + + /** + * Sets a listener to be notified when the configuration changes. + * @param listener A {@link Runnable} whose run() method is called when the + * configuration is changed. The method is called from the UI thread. + */ + public void setOnChangeListener(Runnable listener) { + mOnChangeListener = listener; + } + + /** + * Initialize the UI with a given {@link FolderConfiguration}. This must + * be called from the UI thread. + * @param config The configuration. + */ + public void setConfiguration(FolderConfiguration config) { + mSelectedConfiguration.set(config); + mSelectionTableViewer.refresh(); + + // create the base config, which is the default config minus the qualifiers + // in SelectedConfiguration + mBaseConfiguration.substract(mSelectedConfiguration); + mFullTableViewer.refresh(); + } + + /** + * Initialize the UI with the configuration represented by a resource folder name. + * This must be called from the UI thread. + * + * @param folderSegments the segments of the folder name, + * split using {@link FolderConfiguration#QUALIFIER_SEP}. + * @return true if success, or false if the folder name is not a valid name. + */ + public boolean setConfiguration(String[] folderSegments) { + FolderConfiguration config = ResourceManager.getInstance().getConfig(folderSegments); + + if (config == null) { + return false; + } + + setConfiguration(config); + + return true; + } + + /** + * Initialize the UI with the configuration represented by a resource folder name. + * This must be called from the UI thread. + * @param folderName the name of the folder. + * @return true if success, or false if the folder name is not a valid name. + */ + public boolean setConfiguration(String folderName) { + // split the name of the folder in segments. + String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); + + return setConfiguration(folderSegments); + } + + /** + * Gets the configuration as setup by the widget. + * @param config the {@link FolderConfiguration} object to be filled with the information + * from the UI. + */ + public void getConfiguration(FolderConfiguration config) { + config.set(mSelectedConfiguration); + } + + /** + * Returns the state of the configuration being edited/created. + */ + public ConfigurationState getState() { + if (mSelectedConfiguration.getInvalidQualifier() != null) { + return ConfigurationState.INVALID_CONFIG; + } + + if (mSelectedConfiguration.checkRegion() == false) { + return ConfigurationState.REGION_WITHOUT_LANGUAGE; + } + + return ConfigurationState.OK; + } + + /** + * Returns the first invalid qualifier of the configuration being edited/created, + * or null if they are all valid (or if none exists). + *

          If {@link #getState()} return {@link ConfigurationState#INVALID_CONFIG} then this will + * not return null. + */ + public ResourceQualifier getInvalidQualifier() { + return mSelectedConfiguration.getInvalidQualifier(); + } + + /** + * Handle changes in the configuration. + * @param keepSelection if true attemps to avoid triggering selection change in + * {@link #mSelectedConfiguration}. + */ + private void onChange(boolean keepSelection) { + ISelection selection = null; + if (keepSelection) { + mOnRefresh = true; + selection = mSelectionTableViewer.getSelection(); + } + + mSelectionTableViewer.refresh(true); + + if (keepSelection) { + mSelectionTableViewer.setSelection(selection); + mOnRefresh = false; + } + + if (mOnChangeListener != null) { + mOnChangeListener.run(); + } + } + + /** + * Content provider around a {@link FolderConfiguration}. + */ + private static class QualifierContentProvider implements IStructuredContentProvider { + + private FolderConfiguration mInput; + + public QualifierContentProvider() { + } + + public void dispose() { + // pass + } + + public Object[] getElements(Object inputElement) { + return mInput.getQualifiers(); + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + mInput = null; + if (newInput instanceof FolderConfiguration) { + mInput = (FolderConfiguration)newInput; + } + } + } + + /** + * Label provider for {@link ResourceQualifier} objects. + */ + private static class QualifierLabelProvider implements ITableLabelProvider { + + private final boolean mShowQualifierValue; + + public QualifierLabelProvider(boolean showQualifierValue) { + mShowQualifierValue = showQualifierValue; + } + + public String getColumnText(Object element, int columnIndex) { + // only one column, so we can ignore columnIndex + if (element instanceof ResourceQualifier) { + if (mShowQualifierValue) { + String value = ((ResourceQualifier)element).getStringValue(); + if (value.length() == 0) { + return String.format("%1$s (?)", + ((ResourceQualifier)element).getShortName()); + } else { + return value; + } + + } else { + return ((ResourceQualifier)element).getShortName(); + } + } + + return null; + } + + public Image getColumnImage(Object element, int columnIndex) { + // only one column, so we can ignore columnIndex + if (element instanceof ResourceQualifier) { + return ((ResourceQualifier)element).getIcon(); + } + + return null; + } + + public void addListener(ILabelProviderListener listener) { + // pass + } + + public void dispose() { + // pass + } + + public boolean isLabelProperty(Object element, String property) { + // pass + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // pass + } + } + + /** + * Base class for Edit widget for {@link ResourceQualifier}. + */ + private abstract static class QualifierEditBase extends Composite { + + public QualifierEditBase(Composite parent, String title) { + super(parent, SWT.NONE); + setLayout(new GridLayout(1, false)); + + new Label(this, SWT.NONE).setText(title); + } + + public abstract void setQualifier(ResourceQualifier qualifier); + } + + /** + * Edit widget for {@link CountryCodeQualifier}. + */ + private class MCCEdit extends QualifierEditBase { + + private Text mText; + + public MCCEdit(Composite parent) { + super(parent, CountryCodeQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.addVerifyListener(new MobileCodeVerifier()); + mText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onTextChange(); + } + }); + + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onTextChange(); + } + }); + + new Label(this, SWT.NONE).setText("(3 digit code)"); + } + + private void onTextChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // 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.setCountryCodeQualifier(new CountryCodeQualifier()); + } else { + try { + CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier( + CountryCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setCountryCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + // (for instance not exactly 3 digits). + mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + CountryCodeQualifier q = (CountryCodeQualifier)qualifier; + + mText.setText(Integer.toString(q.getCode())); + } + } + + /** + * Edit widget for {@link NetworkCodeQualifier}. + */ + private class MNCEdit extends QualifierEditBase { + private Text mText; + + public MNCEdit(Composite parent) { + super(parent, NetworkCodeQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.addVerifyListener(new MobileCodeVerifier()); + mText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onTextChange(); + } + }); + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onTextChange(); + } + }); + + new Label(this, SWT.NONE).setText("(1-3 digit code)"); + } + + private void onTextChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // 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.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } else { + try { + NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier( + NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setNetworkCodeQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + // (for instance not exactly 3 digits). + mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NetworkCodeQualifier q = (NetworkCodeQualifier)qualifier; + + mText.setText(Integer.toString(q.getCode())); + } + } + + /** + * Edit widget for {@link LanguageQualifier}. + */ + private class LanguageEdit extends QualifierEditBase { + private Combo mLanguage; + + public LanguageEdit(Composite parent) { + super(parent, LanguageQualifier.NAME); + + mLanguage = new Combo(this, SWT.DROP_DOWN); + mLanguage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mLanguage.addVerifyListener(new LanguageRegionVerifier()); + mLanguage.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onLanguageChange(); + } + public void widgetSelected(SelectionEvent e) { + onLanguageChange(); + } + }); + mLanguage.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onLanguageChange(); + } + }); + + new Label(this, SWT.NONE).setText("(2 letter code)"); + } + + private void onLanguageChange() { + // update the current config + String value = mLanguage.getText(); + + if (value.length() == 0) { + // empty string, 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.setLanguageQualifier(new LanguageQualifier()); + } else { + LanguageQualifier qualifier = null; + String segment = LanguageQualifier.getFolderSegment(value); + if (segment != null) { + qualifier = LanguageQualifier.getQualifier(segment); + } + + if (qualifier != null) { + mSelectedConfiguration.setLanguageQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + mSelectedConfiguration.setLanguageQualifier(new LanguageQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + LanguageQualifier q = (LanguageQualifier)qualifier; + + String value = q.getValue(); + if (value != null) { + mLanguage.setText(value); + } + } + } + + /** + * Edit widget for {@link RegionQualifier}. + */ + private class RegionEdit extends QualifierEditBase { + private Combo mRegion; + + public RegionEdit(Composite parent) { + super(parent, RegionQualifier.NAME); + + mRegion = new Combo(this, SWT.DROP_DOWN); + mRegion.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mRegion.addVerifyListener(new LanguageRegionVerifier()); + mRegion.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onRegionChange(); + } + public void widgetSelected(SelectionEvent e) { + onRegionChange(); + } + }); + mRegion.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onRegionChange(); + } + }); + + new Label(this, SWT.NONE).setText("(2 letter code)"); + } + + private void onRegionChange() { + // update the current config + String value = mRegion.getText(); + + if (value.length() == 0) { + // empty string, 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.setRegionQualifier(new RegionQualifier()); + } else { + RegionQualifier qualifier = null; + String segment = RegionQualifier.getFolderSegment(value); + if (segment != null) { + qualifier = RegionQualifier.getQualifier(segment); + } + + if (qualifier != null) { + mSelectedConfiguration.setRegionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong (for instance a one letter string). + mSelectedConfiguration.setRegionQualifier(new RegionQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + RegionQualifier q = (RegionQualifier)qualifier; + + String value = q.getValue(); + if (value != null) { + mRegion.setText(q.getValue()); + } + } + } + + /** + * Edit widget for {@link ScreenOrientationQualifier}. + */ + private class OrientationEdit extends QualifierEditBase { + + private Combo mOrientation; + + public OrientationEdit(Composite parent) { + super(parent, ScreenOrientationQualifier.NAME); + + mOrientation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + ScreenOrientation[] soValues = ScreenOrientation.values(); + for (ScreenOrientation value : soValues) { + mOrientation.add(value.getDisplayValue()); + } + + mOrientation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mOrientation.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onOrientationChange(); + } + public void widgetSelected(SelectionEvent e) { + onOrientationChange(); + } + }); + } + + protected void onOrientationChange() { + // update the current config + int index = mOrientation.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setScreenOrientationQualifier(new ScreenOrientationQualifier( + ScreenOrientation.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.setScreenOrientationQualifier( + new ScreenOrientationQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenOrientationQualifier q = (ScreenOrientationQualifier)qualifier; + + ScreenOrientation value = q.getValue(); + if (value == null) { + mOrientation.clearSelection(); + } else { + mOrientation.select(ScreenOrientation.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link PixelDensityQualifier}. + */ + private class PixelDensityEdit extends QualifierEditBase { + private Text mText; + + public PixelDensityEdit(Composite parent) { + super(parent, PixelDensityQualifier.NAME); + + mText = new Text(this, SWT.BORDER); + mText.addVerifyListener(new DensityVerifier()); + mText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onTextChange(); + } + }); + mText.addFocusListener(new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onTextChange(); + } + }); + } + + private void onTextChange() { + String value = mText.getText(); + + if (value.length() == 0) { + // empty string, means a qualifier with no value. + // 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.setPixelDensityQualifier(new PixelDensityQualifier()); + } else { + try { + PixelDensityQualifier qualifier = PixelDensityQualifier.getQualifier( + PixelDensityQualifier.getFolderSegment(Integer.parseInt(value))); + if (qualifier != null) { + mSelectedConfiguration.setPixelDensityQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong + // (for instance a one letter string). + // We do nothing in this case. + return; + } + } catch (NumberFormatException nfe) { + // Looks like the code is not a number. This should not happen since the text + // field has a VerifyListener that prevents it. + // We do nothing in this case. + return; + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + PixelDensityQualifier q = (PixelDensityQualifier)qualifier; + + mText.setText(Integer.toString(q.getValue())); + } + } + + /** + * Edit widget for {@link TouchScreenQualifier}. + */ + private class TouchEdit extends QualifierEditBase { + + private Combo mTouchScreen; + + public TouchEdit(Composite parent) { + super(parent, TouchScreenQualifier.NAME); + + mTouchScreen = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + TouchScreenType[] tstValues = TouchScreenType.values(); + for (TouchScreenType value : tstValues) { + mTouchScreen.add(value.getDisplayValue()); + } + + mTouchScreen.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mTouchScreen.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onTouchChange(); + } + public void widgetSelected(SelectionEvent e) { + onTouchChange(); + } + }); + } + + protected void onTouchChange() { + // update the current config + int index = mTouchScreen.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier( + TouchScreenType.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.setTouchTypeQualifier(new TouchScreenQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + TouchScreenQualifier q = (TouchScreenQualifier)qualifier; + + TouchScreenType value = q.getValue(); + if (value == null) { + mTouchScreen.clearSelection(); + } else { + mTouchScreen.select(TouchScreenType.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link KeyboardStateQualifier}. + */ + private class KeyboardEdit extends QualifierEditBase { + + private Combo mKeyboard; + + public KeyboardEdit(Composite parent) { + super(parent, KeyboardStateQualifier.NAME); + + mKeyboard = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + KeyboardState[] ksValues = KeyboardState.values(); + for (KeyboardState value : ksValues) { + mKeyboard.add(value.getDisplayValue()); + } + + mKeyboard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mKeyboard.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onKeyboardChange(); + } + public void widgetSelected(SelectionEvent e) { + onKeyboardChange(); + } + }); + } + + protected void onKeyboardChange() { + // update the current config + int index = mKeyboard.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setKeyboardStateQualifier(new KeyboardStateQualifier( + KeyboardState.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.setKeyboardStateQualifier( + new KeyboardStateQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + KeyboardStateQualifier q = (KeyboardStateQualifier)qualifier; + + KeyboardState value = q.getValue(); + if (value == null) { + mKeyboard.clearSelection(); + } else { + mKeyboard.select(KeyboardState.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link TextInputMethodQualifier}. + */ + private class TextInputEdit extends QualifierEditBase { + + private Combo mTextInput; + + public TextInputEdit(Composite parent) { + super(parent, TextInputMethodQualifier.NAME); + + mTextInput = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + TextInputMethod[] timValues = TextInputMethod.values(); + for (TextInputMethod value : timValues) { + mTextInput.add(value.getDisplayValue()); + } + + mTextInput.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mTextInput.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onTextInputChange(); + } + public void widgetSelected(SelectionEvent e) { + onTextInputChange(); + } + }); + } + + protected void onTextInputChange() { + // update the current config + int index = mTextInput.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setTextInputMethodQualifier(new TextInputMethodQualifier( + TextInputMethod.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.setTextInputMethodQualifier( + new TextInputMethodQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + TextInputMethodQualifier q = (TextInputMethodQualifier)qualifier; + + TextInputMethod value = q.getValue(); + if (value == null) { + mTextInput.clearSelection(); + } else { + mTextInput.select(TextInputMethod.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link NavigationMethodQualifier}. + */ + private class NavigationEdit extends QualifierEditBase { + + private Combo mNavigation; + + public NavigationEdit(Composite parent) { + super(parent, NavigationMethodQualifier.NAME); + + mNavigation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); + NavigationMethod[] nmValues = NavigationMethod.values(); + for (NavigationMethod value : nmValues) { + mNavigation.add(value.getDisplayValue()); + } + + mNavigation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNavigation.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + onNavigationChange(); + } + public void widgetSelected(SelectionEvent e) { + onNavigationChange(); + } + }); + } + + protected void onNavigationChange() { + // update the current config + int index = mNavigation.getSelectionIndex(); + + if (index != -1) { + mSelectedConfiguration.setNavigationMethodQualifier(new NavigationMethodQualifier( + NavigationMethod.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.setNavigationMethodQualifier( + new NavigationMethodQualifier()); + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + NavigationMethodQualifier q = (NavigationMethodQualifier)qualifier; + + NavigationMethod value = q.getValue(); + if (value == null) { + mNavigation.clearSelection(); + } else { + mNavigation.select(NavigationMethod.getIndex(value)); + } + } + } + + /** + * Edit widget for {@link ScreenDimensionQualifier}. + */ + private class ScreenDimensionEdit extends QualifierEditBase { + + private Text mSize1; + private Text mSize2; + + public ScreenDimensionEdit(Composite parent) { + super(parent, ScreenDimensionQualifier.NAME); + + ModifyListener modifyListener = new ModifyListener() { + public void modifyText(ModifyEvent e) { + onSizeChange(); + } + }; + + FocusAdapter focusListener = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + onSizeChange(); + } + }; + + mSize1 = new Text(this, SWT.BORDER); + mSize1.addVerifyListener(new DimensionVerifier()); + mSize1.addModifyListener(modifyListener); + mSize1.addFocusListener(focusListener); + + mSize2 = new Text(this, SWT.BORDER); + mSize2.addVerifyListener(new DimensionVerifier()); + mSize2.addModifyListener(modifyListener); + mSize2.addFocusListener(focusListener); + } + + private void onSizeChange() { + // update the current config + String size1 = mSize1.getText(); + String size2 = mSize2.getText(); + + if (size1.length() == 0 || size2.length() == 0) { + // if one of the strings is empty, reset to 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.setScreenDimensionQualifier(new ScreenDimensionQualifier()); + } else { + ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1, + size2); + + if (qualifier != null) { + mSelectedConfiguration.setScreenDimensionQualifier(qualifier); + } else { + // Failure! Looks like the value is wrong, reset the 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.setScreenDimensionQualifier( + new ScreenDimensionQualifier()); + } + } + + // notify of change + onChange(true /* keepSelection */); + } + + @Override + public void setQualifier(ResourceQualifier qualifier) { + ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier; + + mSize1.setText(Integer.toString(q.getValue1())); + mSize2.setText(Integer.toString(q.getValue2())); + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java new file mode 100644 index 0000000..b27dd4f --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java @@ -0,0 +1,1061 @@ +/* + * 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.editors.wizards; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.project.ProjectChooserHelper; +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; +import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; +import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; +import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; +import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; +import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +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.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; + +/** + * This is the single page of the {@link NewXmlFileWizard} which provides the ability to create + * skeleton XML resources files for Android projects. + *

          + * This page is used to select the project, the resource folder, resource type and file name. + */ +class NewXmlFileCreationPage extends WizardPage { + + /** + * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.) + */ + static class TypeInfo { + private final String mUiName; + private final ResourceFolderType mResFolderType; + private final String mTooltip; + private final Object mRootSeed; + private Button mWidget; + private ArrayList mRoots = new ArrayList(); + private final String mXmlns; + private final String mDefaultAttrs; + private final String mDefaultRoot; + + public TypeInfo(String uiName, + String tooltip, + ResourceFolderType resFolderType, + Object rootSeed, + String defaultRoot, + String xmlns, + String defaultAttrs) { + mUiName = uiName; + mResFolderType = resFolderType; + mTooltip = tooltip; + mRootSeed = rootSeed; + mDefaultRoot = defaultRoot; + mXmlns = xmlns; + mDefaultAttrs = defaultAttrs; + } + + /** Returns the UI name for the resource type. Unique. Never null. */ + String getUiName() { + return mUiName; + } + + /** Returns the tooltip for the resource type. Can be null. */ + String getTooltip() { + return mTooltip; + } + + /** + * Returns the name of the {@link ResourceFolderType}. + * Never null but not necessarily unique, + * e.g. two types use {@link ResourceFolderType#XML}. + */ + String getResFolderName() { + return mResFolderType.getName(); + } + + /** + * Returns the matching {@link ResourceFolderType}. + * Never null but not necessarily unique, + * e.g. two types use {@link ResourceFolderType#XML}. + */ + ResourceFolderType getResFolderType() { + return mResFolderType; + } + + /** Sets the radio button associate with the resource type. Can be null. */ + void setWidget(Button widget) { + mWidget = widget; + } + + /** Returns the radio button associate with the resource type. Can be null. */ + Button getWidget() { + return mWidget; + } + + /** + * Returns the seed used to fill the root element values. + * The seed might be either a String, a String array, an {@link ElementDescriptor}, + * a {@link DocumentDescriptor} or null. + */ + Object getRootSeed() { + return mRootSeed; + } + + /** Returns the default root element that should be selected by default. Can be null. */ + String getDefaultRoot() { + return mDefaultRoot; + } + + /** + * Returns the list of all possible root elements for the resource type. + * This can be an empty ArrayList but not null. + *

          + * TODO: the root list SHOULD depend on the currently selected project, to include + * custom classes. + */ + ArrayList getRoots() { + return mRoots; + } + + /** + * If the generated resource XML file requires an "android" XMLNS, this should be set + * to {@link AndroidConstants#NS_RESOURCES}. When it is null, no XMLNS is generated. + */ + String getXmlns() { + return mXmlns; + } + + /** + * When not null, this represent extra attributes that must be specified in the + * root element of the generated XML file. When null, no extra attributes are inserted. + */ + String getDefaultAttrs() { + return mDefaultAttrs; + } + } + + /** + * TypeInfo, information for each "type" of file that can be created. + */ + private static final TypeInfo[] sTypes = { + new TypeInfo( + "Layout", // UI name + "An XML file that describes a screen layout.", // tooltip + ResourceFolderType.LAYOUT, // folder type + AndroidTargetData.DESCRIPTOR_LAYOUT, // root seed + "LinearLayout", // default root + AndroidConstants.NS_RESOURCES, // xmlns + "android:layout_width=\"wrap_content\"\n" + // default attributes + "android:layout_height=\"wrap_content\"" + ), + new TypeInfo("Values", // UI name + "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip + ResourceFolderType.VALUES, // folder type + ResourcesDescriptors.ROOT_ELEMENT, // root seed + null, // default root + null, // xmlns + null // default attributes + ), + new TypeInfo("Menu", // UI name + "An XML file that describes an menu.", // tooltip + ResourceFolderType.MENU, // folder type + MenuDescriptors.MENU_ROOT_ELEMENT, // root seed + null, // default root + AndroidConstants.NS_RESOURCES, // xmlns + null // default attributes + ), + new TypeInfo("Preference", // UI name + "An XML file that describes preferences.", // tooltip + ResourceFolderType.XML, // folder type + AndroidTargetData.DESCRIPTOR_PREFERENCES, // root seed + AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root + AndroidConstants.NS_RESOURCES, // xmlns + null // default attributes + ), + new TypeInfo("Searchable", // UI name + "An XML file that describes a searchable [TODO].", // tooltip + ResourceFolderType.XML, // folder type + AndroidTargetData.DESCRIPTOR_SEARCHABLE, // root seed + null, // default root + AndroidConstants.NS_RESOURCES, // xmlns + null // default attributes + ), + new TypeInfo("Animation", // UI name + "An XML file that describes an animation.", // tooltip + ResourceFolderType.ANIM, // folder type + // TODO reuse constants if we ever make an editor with descriptors for animations + new String[] { // root seed + "set", //$NON-NLS-1$ + "alpha", //$NON-NLS-1$ + "scale", //$NON-NLS-1$ + "translate", //$NON-NLS-1$ + "rotate" //$NON-NLS-1$ + }, + "set", //$NON-NLS-1$ // default root + null, // xmlns + null // default attributes + ), + }; + + /** Absolute destination folder root, e.g. "/res/" */ + private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; + /** Relative destination folder root, e.g. "res/" */ + private static String sResFolderRel = AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; + + private IProject mProject; + private Text mProjectTextField; + private Button mProjectBrowseButton; + private Text mFileNameTextField; + private Text mWsFolderPathTextField; + private Combo mRootElementCombo; + private IStructuredSelection mInitialSelection; + private ConfigurationSelector mConfigSelector; + private FolderConfiguration mTempConfig = new FolderConfiguration(); + private boolean mInternalWsFolderPathUpdate; + private boolean mInternalTypeUpdate; + private boolean mInternalConfigSelectorUpdate; + private ProjectChooserHelper mProjectChooserHelper; + + + // --- UI creation --- + + /** + * Constructs a new {@link NewXmlFileCreationPage}. + *

          + * Called by {@link NewXmlFileWizard#createMainPage()}. + */ + protected NewXmlFileCreationPage(String pageName) { + super(pageName); + setPageComplete(false); + } + + public void setInitialSelection(IStructuredSelection initialSelection) { + mInitialSelection = initialSelection; + } + + /** + * Called by the parent Wizard to create the UI for this Wizard Page. + * + * {@inheritDoc} + * + * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) + */ + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NULL); + composite.setFont(parent.getFont()); + + initializeDialogUnits(parent); + + composite.setLayout(new GridLayout(3, false /*makeColumnsEqualWidth*/)); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createProjectGroup(composite); + createTypeGroup(composite); + createRootGroup(composite); + + // Show description the first time + setErrorMessage(null); + setMessage(null); + setControl(composite); + + // Update state the first time + initializeRootValues(); + initializeFromSelection(mInitialSelection); + validatePage(); + } + + /** + * Returns the target project or null. + */ + public IProject getProject() { + return mProject; + } + + /** + * Returns the destination filename or an empty string. + */ + public String getFileName() { + return mFileNameTextField == null ? "" : mFileNameTextField.getText(); //$NON-NLS-1$ + } + + /** + * Returns the destination folder path relative to the project or an empty string. + */ + public String getWsFolderPath() { + return mWsFolderPathTextField == null ? "" : mWsFolderPathTextField.getText(); //$NON-NLS-1$ + } + + + /** + * Returns an {@link IFile} on the destination file. + *

          + * Uses {@link #getProject()}, {@link #getWsFolderPath()} and {@link #getFileName()}. + *

          + * Returns null if the project, filename or folder are invalid and the destination file + * cannot be determined. + *

          + * The {@link IFile} is a resource. There might or might not be an actual real file. + */ + public IFile getDestinationFile() { + IProject project = getProject(); + String wsFolderPath = getWsFolderPath(); + String fileName = getFileName(); + if (project != null && wsFolderPath.length() > 0 && fileName.length() > 0) { + IPath dest = new Path(wsFolderPath).append(fileName); + IFile file = project.getFile(dest); + return file; + } + return null; + } + + /** + * Returns the {@link TypeInfo} for the currently selected type radio button. + * Returns null if no radio button is selected. + * + * @return A {@link TypeInfo} or null. + */ + public TypeInfo getSelectedType() { + TypeInfo type = null; + for (TypeInfo ti : sTypes) { + if (ti.getWidget().getSelection()) { + type = ti; + break; + } + } + return type; + } + + /** + * Returns the selected root element string, if any. + * + * @return The selected root element string or null. + */ + public String getRootElement() { + int index = mRootElementCombo.getSelectionIndex(); + if (index >= 0) { + return mRootElementCombo.getItem(index); + } + return null; + } + + // --- UI creation --- + + /** + * Helper method to create a new GridData with an horizontal span. + * + * @param horizSpan The number of cells for the horizontal span. + * @return A new GridData with the horizontal span. + */ + private GridData newGridData(int horizSpan) { + GridData gd = new GridData(); + gd.horizontalSpan = horizSpan; + return gd; + } + + /** + * Helper method to create a new GridData with an horizontal span and a style. + * + * @param horizSpan The number of cells for the horizontal span. + * @param style The style, e.g. {@link GridData#FILL_HORIZONTAL} + * @return A new GridData with the horizontal span and the style. + */ + private GridData newGridData(int horizSpan, int style) { + GridData gd = new GridData(style); + gd.horizontalSpan = horizSpan; + return gd; + } + + /** + * Helper method that creates an empty cell in the parent composite. + * + * @param parent The parent composite. + */ + private void emptyCell(Composite parent) { + new Label(parent, SWT.NONE); + } + + /** + * Creates the project & filename fields. + *

          + * The parent must be a GridLayout with 3 colums. + */ + private void createProjectGroup(Composite parent) { + // project name + String tooltip = "The Android Project where the new resource file will be created."; + Label label = new Label(parent, SWT.NONE); + label.setText("Project"); + label.setToolTipText(tooltip); + + mProjectTextField = new Text(parent, SWT.BORDER); + mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mProjectTextField.setToolTipText(tooltip); + mProjectTextField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onProjectFieldUpdated(); + } + }); + + mProjectBrowseButton = new Button(parent, SWT.NONE); + mProjectBrowseButton.setText("Browse..."); + mProjectBrowseButton.setToolTipText("Allows you to select the Android project to modify."); + mProjectBrowseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + onProjectBrowse(); + } + }); + mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); + + // file name + tooltip = "The name of the resource file to create."; + label = new Label(parent, SWT.NONE); + label.setText("File"); + label.setToolTipText(tooltip); + + mFileNameTextField = new Text(parent, SWT.BORDER); + mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mFileNameTextField.setToolTipText(tooltip); + mFileNameTextField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + validatePage(); + } + }); + + emptyCell(parent); + } + + /** + * Creates the type field, {@link ConfigurationSelector} and the folder field. + *

          + * The parent must be a GridLayout with 3 colums. + */ + private void createTypeGroup(Composite parent) { + // separator + Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); + label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL)); + + // label before type radios + label = new Label(parent, SWT.NONE); + label.setText("What type of resource would you like to create?"); + label.setLayoutData(newGridData(3)); + + // display the types on three columns of radio buttons. + emptyCell(parent); + Composite grid = new Composite(parent, SWT.NONE); + emptyCell(parent); + + grid.setLayout(new GridLayout(3, true /*makeColumnsEqualWidth*/)); + + SelectionListener radioListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + // single-click. Only do something if activated. + if (e.getSource() instanceof Button) { + onRadioTypeUpdated((Button) e.getSource()); + } + } + }; + + int n = sTypes.length; + int num_lines = n/3; + for (int line = 0; line < num_lines; line++) { + for (int i = 0; i < 3; i++) { + TypeInfo type = sTypes[line * 3 + i]; + Button radio = new Button(grid, SWT.RADIO); + type.setWidget(radio); + radio.setSelection(false); + radio.setText(type.getUiName()); + radio.setToolTipText(type.getTooltip()); + radio.addSelectionListener(radioListener); + } + } + + // label before configuration selector + label = new Label(parent, SWT.NONE); + label.setText("What type of resource configuration would you like?"); + label.setLayoutData(newGridData(3)); + + // configuration selector + emptyCell(parent); + mConfigSelector = new ConfigurationSelector(parent); + GridData gd = newGridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); + gd.widthHint = ConfigurationSelector.WIDTH_HINT; + gd.heightHint = ConfigurationSelector.HEIGHT_HINT; + mConfigSelector.setLayoutData(gd); + mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated()); + + // folder name + String tooltip = "The folder where the file will be generated, relative to the project."; + label = new Label(parent, SWT.NONE); + label.setText("Folder"); + label.setToolTipText(tooltip); + + mWsFolderPathTextField = new Text(parent, SWT.BORDER); + mWsFolderPathTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mWsFolderPathTextField.setToolTipText(tooltip); + mWsFolderPathTextField.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + onWsFolderPathUpdated(); + } + }); + + emptyCell(parent); + } + + /** + * Creates the root element combo. + *

          + * The parent must be a GridLayout with 3 colums. + */ + private void createRootGroup(Composite parent) { + // separator + Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); + label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL)); + + // label before the root combo + String tooltip = "The root element to create in the XML file."; + label = new Label(parent, SWT.NONE); + label.setText("Select the root element for the XML file:"); + label.setLayoutData(newGridData(3)); + label.setToolTipText(tooltip); + + // root combo + emptyCell(parent); + + mRootElementCombo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); + mRootElementCombo.setEnabled(false); + mRootElementCombo.select(0); + mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mRootElementCombo.setToolTipText(tooltip); + + emptyCell(parent); + } + + /** + * Called by {@link NewXmlFileWizard} to initialize the page with the selection + * received by the wizard -- typically the current user workbench selection. + *

          + * Things we expect to find out from the selection: + *

            + *
          • The project name, valid if it's an android nature.
          • + *
          • The current folder, valid if it's a folder under /res
          • + *
          • An existing filename, in which case the user will be asked whether to override it.
          • + *
              + * + * @param selection The selection when the wizard was initiated. + */ + private void initializeFromSelection(IStructuredSelection selection) { + if (selection == null) { + return; + } + + + // Find the best match in the element list. In case there are multiple selected elements + // select the one that provides the most information and assign them a score, + // e.g. project=1 + folder=2 + file=4. + IProject targetProject = null; + String targetWsFolderPath = null; + String targetFileName = null; + int targetScore = 0; + for (Object element : selection.toList()) { + if (element instanceof IAdaptable) { + IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class); + IProject project = res != null ? res.getProject() : null; + + // Is this an Android project? + try { + if (project == null || !project.hasNature(AndroidConstants.NATURE)) { + continue; + } + } catch (CoreException e) { + // checking the nature failed, ignore this resource + continue; + } + + int score = 1; // we have a valid project at least + + IPath wsFolderPath = null; + String fileName = null; + if (res.getType() == IResource.FOLDER) { + wsFolderPath = res.getProjectRelativePath(); + } else if (res.getType() == IResource.FILE) { + fileName = res.getName(); + wsFolderPath = res.getParent().getProjectRelativePath(); + } + + // Disregard this folder selection if it doesn't point to /res/something + if (wsFolderPath != null && + wsFolderPath.segmentCount() > 1 && + AndroidConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) { + score += 2; + } else { + wsFolderPath = null; + fileName = null; + } + + score += fileName != null ? 4 : 0; + + if (score > targetScore) { + targetScore = score; + targetProject = project; + targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null; + targetFileName = fileName; + } + } + } + + // Now set the UI accordingly + if (targetScore > 0) { + mProject = targetProject; + mProjectTextField.setText(targetProject != null ? targetProject.getName() : ""); //$NON-NLS-1$ + mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$ + mWsFolderPathTextField.setText(targetWsFolderPath != null ? targetWsFolderPath : ""); //$NON-NLS-1$ + } + } + + /** + * Initialize the root values of the type infos based on the current framework values. + */ + private void initializeRootValues() { + for (TypeInfo type : sTypes) { + // Clear all the roots for this type + ArrayList roots = type.getRoots(); + if (roots.size() > 0) { + roots.clear(); + } + + // depending of the type of the seed, initialize the root in different ways + Object rootSeed = type.getRootSeed(); + + if (rootSeed instanceof String) { + // The seed is a single string, Add it as-is. + roots.add((String) rootSeed); + } else if (rootSeed instanceof String[]) { + // The seed is an array of strings. Add them as-is. + for (String value : (String[]) rootSeed) { + roots.add(value); + } + } else if (rootSeed instanceof Integer && mProject != null) { + // The seed is a descriptor reference defined in AndroidTargetData.DESCRIPTOR_* + // In this case add all the children element descriptors defined, recursively, + // and avoid infinite recursion by keeping track of what has already been added. + + // Note: if project is null, the root list will be empty since it has been + // cleared above. + + // get the AndroidTargetData from the project + IAndroidTarget target = Sdk.getCurrent().getTarget(mProject); + AndroidTargetData data = Sdk.getCurrent().getTargetData(target); + ElementDescriptor descriptor = data.getDescriptorProvider( + (Integer)rootSeed).getDescriptor(); + + HashSet visited = new HashSet(); + initRootElementDescriptor(roots, descriptor, visited); + + // Sort alphabetically. + Collections.sort(roots); + } + } + } + + /** + * Helper method to recursively insert all XML names for the given {@link ElementDescriptor} + * into the roots array list. Keeps track of visited nodes to avoid infinite recursion. + * Also avoids inserting the top {@link DocumentDescriptor} which is generally synthetic + * and not a valid root element. + */ + private void initRootElementDescriptor(ArrayList roots, + ElementDescriptor desc, HashSet visited) { + if (!(desc instanceof DocumentDescriptor)) { + String xmlName = desc.getXmlName(); + if (xmlName != null && xmlName.length() > 0) { + roots.add(xmlName); + } + } + + visited.add(desc); + + for (ElementDescriptor child : desc.getChildren()) { + if (!visited.contains(child)) { + initRootElementDescriptor(roots, child, visited); + } + } + } + + /** + * Callback called when the user edits the project text field. + */ + private void onProjectFieldUpdated() { + String project = mProjectTextField.getText(); + + // Is this a valid project? + IJavaProject[] projects = mProjectChooserHelper.getAndroidProjects(null /*javaModel*/); + IProject found = null; + for (IJavaProject p : projects) { + if (p.getProject().getName().equals(project)) { + found = p.getProject(); + break; + } + } + + if (found != mProject) { + mProject = found; + + // update the Type with the new descriptors. + initializeRootValues(); + + // update the combo + updateRootCombo(getSelectedType()); + + validatePage(); + } + } + + /** + * Callback called when the user uses the "Browse Projects" button. + */ + private void onProjectBrowse() { + IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText()); + if (p != null) { + mProject = p.getProject(); + mProjectTextField.setText(mProject.getName()); + + // update the Type with the new descriptors. + initializeRootValues(); + + // update the combo + updateRootCombo(getSelectedType()); + + validatePage(); + } + } + + /** + * Callback called when the Folder text field is changed, either programmatically + * or by the user. + */ + private void onWsFolderPathUpdated() { + if (mInternalWsFolderPathUpdate) { + return; + } + + String wsFolderPath = mWsFolderPathTextField.getText(); + + // This is a custom path, we need to sanitize it. + // First it should start with "/res/". Then we need to make sure there are no + // relative paths, things like "../" or "./" or even "//". + wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$ + wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$ + wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ + + ArrayList matches = new ArrayList(); + + // We get "res/foo" from selections relative to the project when we want a "/res/foo" path. + if (wsFolderPath.startsWith(sResFolderRel)) { + wsFolderPath = sResFolderAbs + wsFolderPath.substring(sResFolderRel.length()); + + mInternalWsFolderPathUpdate = true; + mWsFolderPathTextField.setText(wsFolderPath); + mInternalWsFolderPathUpdate = false; + } + + if (wsFolderPath.startsWith(sResFolderAbs)) { + wsFolderPath = wsFolderPath.substring(sResFolderAbs.length()); + + int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR); + if (pos >= 0) { + wsFolderPath = wsFolderPath.substring(0, pos); + } + + String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP); + + if (folderSegments.length > 0) { + String folderName = folderSegments[0]; + + // update config selector + mInternalConfigSelectorUpdate = true; + mConfigSelector.setConfiguration(folderSegments); + mInternalConfigSelectorUpdate = false; + + boolean selected = false; + for (TypeInfo type : sTypes) { + if (type.getResFolderName().equals(folderName)) { + matches.add(type); + selected |= type.getWidget().getSelection(); + } + } + + if (matches.size() == 1) { + // If there's only one match, select it if it's not already selected + if (!selected) { + selectType(matches.get(0)); + } + } else if (matches.size() > 1) { + // There are multiple type candidates for this folder. This can happen + // for /res/xml for example. Check to see if one of them is currently + // selected. If yes, leave the selection unchanged. If not, deselect all type. + if (!selected) { + selectType(null); + } + } else { + // Nothing valid was selected. + selectType(null); + } + } + } + + validatePage(); + } + + /** + * Callback called when one of the type radio button is changed. + * + * @param typeWidget The type radio button that changed. + */ + private void onRadioTypeUpdated(Button typeWidget) { + // Do nothing if this is an internal modification or if the widget has been + // de-selected. + if (mInternalTypeUpdate || !typeWidget.getSelection()) { + return; + } + + // Find type info that has just been enabled. + TypeInfo type = null; + for (TypeInfo ti : sTypes) { + if (ti.getWidget() == typeWidget) { + type = ti; + break; + } + } + + if (type == null) { + return; + } + + // update the combo + + updateRootCombo(type); + + // update the folder path + + String wsFolderPath = mWsFolderPathTextField.getText(); + String newPath = null; + + mConfigSelector.getConfiguration(mTempConfig); + ResourceQualifier qual = mTempConfig.getInvalidQualifier(); + if (qual == null) { + // The configuration is valid. Reformat the folder path using the canonical + // value from the configuration. + + newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); + } else { + // The configuration is invalid. We still update the path but this time + // do it manually on the string. + if (wsFolderPath.startsWith(sResFolderAbs)) { + wsFolderPath.replaceFirst( + "^(" + sResFolderAbs +")[^-]*(.*)", //$NON-NLS-1$ //$NON-NLS-2$ + "\\1" + type.getResFolderName() + "\\2"); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); + } + } + + if (newPath != null && !newPath.equals(wsFolderPath)) { + mInternalWsFolderPathUpdate = true; + mWsFolderPathTextField.setText(newPath); + mInternalWsFolderPathUpdate = false; + } + + validatePage(); + } + + /** + * Helper method that fills the values of the "root element" combo box based + * on the currently selected type radio button. Also disables the combo is there's + * only one choice. Always select the first root element for the given type. + * + * @param type The currently selected {@link TypeInfo}. Cannot be null. + */ + private void updateRootCombo(TypeInfo type) { + // reset all the values in the combo + mRootElementCombo.removeAll(); + + if (type != null) { + // get the list of roots. The list can be empty but not null. + ArrayList roots = type.getRoots(); + + // enable the combo if there's more than one choice + mRootElementCombo.setEnabled(roots != null && roots.size() > 1); + + for (String root : roots) { + mRootElementCombo.add(root); + } + + int index = 0; // default is to select the first one + String defaultRoot = type.getDefaultRoot(); + if (defaultRoot != null) { + index = roots.indexOf(defaultRoot); + } + mRootElementCombo.select(index < 0 ? 0 : index); + } + } + + /** + * Callback called when the configuration has changed in the {@link ConfigurationSelector}. + */ + private class onConfigSelectorUpdated implements Runnable { + public void run() { + if (mInternalConfigSelectorUpdate) { + return; + } + + TypeInfo type = getSelectedType(); + + if (type != null) { + mConfigSelector.getConfiguration(mTempConfig); + StringBuffer sb = new StringBuffer(sResFolderAbs); + sb.append(mTempConfig.getFolderName(type.getResFolderType())); + + mInternalWsFolderPathUpdate = true; + mWsFolderPathTextField.setText(sb.toString()); + mInternalWsFolderPathUpdate = false; + + validatePage(); + } + } + } + + /** + * Helper method to select on of the type radio buttons. + * + * @param type The TypeInfo matching the radio button to selected or null to deselect them all. + */ + private void selectType(TypeInfo type) { + if (type == null || !type.getWidget().getSelection()) { + mInternalTypeUpdate = true; + for (TypeInfo type2 : sTypes) { + type2.getWidget().setSelection(type2 == type); + } + updateRootCombo(type); + mInternalTypeUpdate = false; + } + } + + /** + * Validates the fields, displays errors and warnings. + * Enables the finish button if there are no errors. + */ + private void validatePage() { + String error = null; + String warning = null; + + // -- validate project + if (getProject() == null) { + error = "Please select an Android project."; + } + + // -- validate filename + if (error == null) { + String fileName = getFileName(); + if (fileName == null || fileName.length() == 0) { + error = "A destination file name is required."; + } else if (fileName != null && !fileName.endsWith(AndroidConstants.DOT_XML)) { + error = String.format("The filename must end with %1$s.", AndroidConstants.DOT_XML); + } + } + + // -- validate type + if (error == null) { + TypeInfo type = getSelectedType(); + + if (type == null) { + error = "One of the types must be selected (e.g. layout, values, etc.)"; + } + } + + // -- validate folder configuration + if (error == null) { + ConfigurationState state = mConfigSelector.getState(); + if (state == ConfigurationState.INVALID_CONFIG) { + ResourceQualifier qual = mConfigSelector.getInvalidQualifier(); + if (qual != null) { + error = String.format("The qualifier '%1$s' is invalid in the folder configuration.", + qual.getName()); + } + } else if (state == ConfigurationState.REGION_WITHOUT_LANGUAGE) { + error = "The Region qualifier requires the Language qualifier."; + } + } + + // -- validate generated path + if (error == null) { + String wsFolderPath = getWsFolderPath(); + if (!wsFolderPath.startsWith(sResFolderAbs)) { + error = String.format("Target folder must start with %1$s.", sResFolderAbs); + } + } + + // -- validate destination file doesn't exist + if (error == null) { + IFile file = getDestinationFile(); + if (file != null && file.exists()) { + warning = "The destination file already exists"; + } + } + + // -- update UI & enable finish if there's no error + setPageComplete(error == null); + if (error != null) { + setMessage(error, WizardPage.ERROR); + } else if (warning != null) { + setMessage(warning, WizardPage.WARNING); + } else { + setErrorMessage(null); + setMessage(null); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java new file mode 100644 index 0000000..125102b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java @@ -0,0 +1,226 @@ +/* + * 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.editors.wizards; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.IconFactory; +import com.android.ide.eclipse.editors.wizards.NewXmlFileCreationPage.TypeInfo; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +/** + * The "New Android XML File Wizard" provides the ability to create skeleton XML + * resources files for Android projects. + *

              + * The wizard has one page, {@link NewXmlFileCreationPage}, used to select the project, + * the resource folder, resource type and file name. It then creates the XML file. + */ +public class NewXmlFileWizard extends Wizard implements INewWizard { + + private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$ + + protected static final String MAIN_PAGE_NAME = "newAndroidXmlFilePage"; //$NON-NLS-1$ + + private NewXmlFileCreationPage mMainPage; + + public void init(IWorkbench workbench, IStructuredSelection selection) { + setHelpAvailable(false); // TODO have help + setWindowTitle("New Android XML File"); + setImageDescriptor(); + + mMainPage = createMainPage(); + mMainPage.setTitle("New Android XML File"); + mMainPage.setDescription("Creates a new Android XML file."); + mMainPage.setInitialSelection(selection); + } + + /** + * Creates the wizard page. + *

              + * Please do NOT override this method. + *

              + * This is protected so that it can be overridden by unit tests. + * However the contract of this class is private and NO ATTEMPT will be made + * to maintain compatibility between different versions of the plugin. + */ + protected NewXmlFileCreationPage createMainPage() { + return new NewXmlFileCreationPage(MAIN_PAGE_NAME); + } + + // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- + // + // The Wizard class implements most defaults and boilerplate code needed by + // IWizard + + /** + * Adds pages to this wizard. + */ + @Override + public void addPages() { + addPage(mMainPage); + } + + /** + * Performs any actions appropriate in response to the user having pressed + * the Finish button, or refuse if finishing now is not permitted: here, it + * actually creates the workspace project and then switch to the Java + * perspective. + * + * @return True + */ + @Override + public boolean performFinish() { + IFile file = createXmlFile(); + if (file == null) { + return false; + } else { + // Open the file in an editor + IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); + if (win != null) { + IWorkbenchPage page = win.getActivePage(); + if (page != null) { + try { + IDE.openEditor(page, file); + } catch (PartInitException e) { + AdtPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ + file.getFullPath().toString()); + } + } + } + return true; + } + } + + // -- Custom Methods -- + + private IFile createXmlFile() { + IFile file = mMainPage.getDestinationFile(); + String name = file.getFullPath().toString(); + boolean need_delete = false; + + if (file.exists()) { + if (!AdtPlugin.displayPrompt("New Android XML File", + String.format("Do you want to overwrite the file %1$s ?", name))) { + // abort if user selects cancel. + return null; + } + need_delete = true; + } else { + createWsParentDirectory(file.getParent()); + } + + TypeInfo type = mMainPage.getSelectedType(); + if (type == null) { + // this is not expected to happen + AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing type", name); //$NON-NLS-1$ + return null; + } + String xmlns = type.getXmlns(); + String root = mMainPage.getRootElement(); + if (root == null) { + // this is not expected to happen + AdtPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing root element", //$NON-NLS-1$ + file.toString()); + return null; + } + + StringBuilder sb = new StringBuilder("\n"); //$NON-NLS-1$ + + sb.append('<').append(root); + if (xmlns != null) { + sb.append('\n').append(" xmlns:android=\"").append(xmlns).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ + } + + String attrs = type.getDefaultAttrs(); + if (attrs != null) { + sb.append("\n "); //$NON-NLS-1$ + sb.append(attrs.replace("\n", "\n ")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + sb.append(">\n"); //$NON-NLS-1$ + sb.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ + + String result = sb.toString(); + String error = null; + try { + byte[] buf = result.getBytes("UTF8"); + InputStream stream = new ByteArrayInputStream(buf); + if (need_delete) { + file.delete(IFile.KEEP_HISTORY | IFile.FORCE, null /*monitor*/); + } + file.create(stream, true /*force*/, null /*progres*/); + return file; + } catch (UnsupportedEncodingException e) { + error = e.getMessage(); + } catch (CoreException e) { + error = e.getMessage(); + } + + error = String.format("Failed to generate %1$s: %2$s", name, error); + AdtPlugin.displayError("New Android XML File", error); + return null; + } + + private boolean createWsParentDirectory(IContainer wsPath) { + if (wsPath.getType() == IContainer.FOLDER) { + if (wsPath == null || wsPath.exists()) { + return true; + } + + IFolder folder = (IFolder) wsPath; + try { + if (createWsParentDirectory(wsPath.getParent())) { + folder.create(true /* force */, true /* local */, null /* monitor */); + return true; + } + } catch (CoreException e) { + e.printStackTrace(); + } + } + + return false; + } + + /** + * Returns an image descriptor for the wizard logo. + */ + private void setImageDescriptor() { + ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java new file mode 100644 index 0000000..d3ff334 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java @@ -0,0 +1,267 @@ +/* + * 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.editors.wizards; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.DialogSettings; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeSelection; +import org.eclipse.jface.viewers.TreeViewer; +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.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.ui.dialogs.FilteredTree; +import org.eclipse.ui.dialogs.PatternFilter; +import org.eclipse.ui.dialogs.SelectionStatusDialog; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A dialog to let the user choose a reference to a resource. + * + */ +public class ReferenceChooserDialog extends SelectionStatusDialog { + + private static Pattern sResourcePattern = Pattern.compile("@(.*)/(.+)"); //$NON-NLS-1$ + private static Pattern sInlineIdResourcePattern = Pattern.compile("@\\+id/(.+)"); //$NON-NLS-1$ + + private static IDialogSettings sDialogSettings = new DialogSettings(""); + + private IResourceRepository mResources; + private String mCurrentResource; + + private FilteredTree mFilteredTree; + + /** + * @param parent + */ + public ReferenceChooserDialog(IResourceRepository resources, Shell parent) { + super(parent); + + int shellStyle = getShellStyle(); + setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); + + setTitle("Reference Dialog"); + setMessage(String.format("Choose a resource")); + mResources = resources; + + setDialogBoundsSettings(sDialogSettings, getDialogBoundsStrategy()); + } + + public void setCurrentResource(String resource) { + mCurrentResource = resource; + } + + public String getCurrentResource() { + return mCurrentResource; + } + + + /* (non-Javadoc) + * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult() + */ + @Override + protected void computeResult() { + // get the selection + TreePath treeSelection = getSelection(); + if (treeSelection != null) { + if (treeSelection.getSegmentCount() == 2) { + // get the resource type and the resource item + ResourceType resourceType = (ResourceType)treeSelection.getFirstSegment(); + ResourceItem resourceItem = (ResourceItem)treeSelection.getLastSegment(); + + mCurrentResource = resourceType.getXmlString(resourceItem, false /* system */); + } + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = (Composite)super.createDialogArea(parent); + + // create the standard message area + createMessageArea(top); + + // create the filtered tree + createFilteredTree(top); + + // setup the initial selection + setupInitialSelection(); + + return top; + } + + private void createFilteredTree(Composite parent) { + mFilteredTree = new FilteredTree(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION, + new PatternFilter()); + + GridData data = new GridData(); + data.widthHint = convertWidthInCharsToPixels(60); + data.heightHint = convertHeightInCharsToPixels(18); + data.grabExcessVerticalSpace = true; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.FILL; + mFilteredTree.setLayoutData(data); + mFilteredTree.setFont(parent.getFont()); + + TreeViewer treeViewer = mFilteredTree.getViewer(); + Tree tree = treeViewer.getTree(); + + tree.addSelectionListener(new SelectionListener() { + public void widgetDefaultSelected(SelectionEvent e) { + handleDoubleClick(); + } + + public void widgetSelected(SelectionEvent e) { + handleSelection(); + } + }); + + treeViewer.setLabelProvider(new ResourceLabelProvider()); + treeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */)); + treeViewer.setInput(mResources); + } + + protected void handleSelection() { + validateCurrentSelection(); + } + + protected void handleDoubleClick() { + if (validateCurrentSelection()) { + buttonPressed(IDialogConstants.OK_ID); + } + } + + /** + * Returns the selected item in the tree as a {@link TreePath} object. + * @return the TreePath object or null if there was no selection. + */ + private TreePath getSelection() { + ISelection selection = mFilteredTree.getViewer().getSelection(); + if (selection instanceof TreeSelection) { + TreeSelection treeSelection = (TreeSelection)selection; + TreePath[] treePaths = treeSelection.getPaths(); + + // the selection mode is SWT.SINGLE, so we just get the first one. + if (treePaths.length > 0) { + return treePaths[0]; + } + } + + return null; + } + + private boolean validateCurrentSelection() { + TreePath treeSelection = getSelection(); + + IStatus status; + if (treeSelection != null) { + if (treeSelection.getSegmentCount() == 2) { + status = new Status(IStatus.OK, AdtPlugin.PLUGIN_ID, + IStatus.OK, "", //$NON-NLS-1$ + null); + } else { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + IStatus.ERROR, "You must select a Resource Item", + null); + } + } else { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + IStatus.ERROR, "", //$NON-NLS-1$ + null); + } + + updateStatus(status); + + return status.isOK(); + } + + /** + * Sets up the initial selection. + *

              + * This parses {@link #mCurrentResource} to find out the resource type and the resource name. + */ + private void setupInitialSelection() { + // checks the inline id pattern first as it's more restrictive than the other one. + Matcher m = sInlineIdResourcePattern.matcher(mCurrentResource); + if (m.matches()) { + // get the matching name + String resourceName = m.group(1); + + // setup initial selection + setupInitialSelection(ResourceType.ID, resourceName); + } else { + // attempts the inline id pattern + m = sResourcePattern.matcher(mCurrentResource); + if (m.matches()) { + // get the resource type. + ResourceType resourceType = ResourceType.getEnum(m.group(1)); + if (resourceType != null) { + // get the matching name + String resourceName = m.group(2); + + // setup initial selection + setupInitialSelection(resourceType, resourceName); + } + } + } + } + + /** + * Sets up the initial selection based on a {@link ResourceType} and a resource name. + * @param resourceType the resource type. + * @param resourceName the resource name. + */ + private void setupInitialSelection(ResourceType resourceType, String resourceName) { + // get all the resources of this type + ResourceItem[] resourceItems = mResources.getResources(resourceType); + + for (ResourceItem resourceItem : resourceItems) { + if (resourceName.equals(resourceItem.getName())) { + // name of the resource match, we select it, + TreePath treePath = new TreePath(new Object[] { resourceType, resourceItem }); + mFilteredTree.getViewer().setSelection(new TreeSelection(treePath)); + + // and we're done. + return; + } + } + + // if we get here, the resource type is valid, but the resource is missing. + // we select and expand the resource type element. + TreePath treePath = new TreePath(new Object[] { resourceType }); + mFilteredTree.getViewer().setSelection(new TreeSelection(treePath)); + mFilteredTree.getViewer().setExpandedState(resourceType, true /* expanded */); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java new file mode 100644 index 0000000..60a627b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2007 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.editors.wizards; + +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; + +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A dialog to let the user select a resource based on a resource type. + */ +public class ResourceChooser extends AbstractElementListSelectionDialog { + + private Pattern mProjectResourcePattern; + + private ResourceType mResourceType; + + private IResourceRepository mProjectResources; + + // TODO: enable when we can display the system resources. + // private Pattern mSystemResourcePattern; + // private IResourceRepository mSystemResources; + // private Button mProjectButton; + // private Button mSystemButton; + + private String mCurrentResource; + + /** + * Creates a Resource Chooser dialog. + * @param type The type of the resource to choose + * @param project The repository for the project + * @param system The System resource repository + * @param parent the parent shell + */ + public ResourceChooser(ResourceType type, IResourceRepository project, + IResourceRepository system, Shell parent) { + super(parent, new ResourceLabelProvider()); + + mResourceType = type; + mProjectResources = project; + // TODO: enable when we can display the system resources. + // mSystemResources = system; + + mProjectResourcePattern = Pattern.compile( + "@" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ + // TODO: enable when we can display the system resources. + // mSystemResourcePattern = Pattern.compile( + // "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ + + setTitle("Resource Chooser"); + setMessage(String.format("Choose a %1$s resource", + mResourceType.getDisplayName().toLowerCase())); + } + + public void setCurrentResource(String resource) { + mCurrentResource = resource; + } + + public String getCurrentResource() { + return mCurrentResource; + } + + @Override + protected void computeResult() { + Object[] elements = getSelectedElements(); + if (elements.length == 1 && elements[0] instanceof ResourceItem) { + ResourceItem item = (ResourceItem)elements[0]; + + mCurrentResource = mResourceType.getXmlString(item, + // TODO: enable when we can display the system resources. + false /*mSystemButton.getSelection()*/); + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite top = (Composite)super.createDialogArea(parent); + + createMessageArea(top); + + // TODO: enable when we can display the system resources. + // createButtons(top); + + createFilterText(top); + createFilteredList(top); + + setupResourceListAndCurrent(); + + return top; + } + + /** + * Creates the radio button to switch between project and system resources. + * @param top the parent composite + */ + /* TODO: enable when we can display the system resources. + private void createButtons(Composite top) { + mProjectButton = new Button(top, SWT.RADIO); + mProjectButton.setText("Project Resources"); + mProjectButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setListElements(mProjectResources.getResources(mResourceType)); + } + }); + mSystemButton = new Button(top, SWT.RADIO); + mSystemButton.setText("System Resources"); + mSystemButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setListElements(mSystemResources.getResources(mResourceType)); + } + }); + } + */ + + /** + * Setups the current list based on the current resource. + */ + private void setupResourceListAndCurrent() { + if (setupInitialSelection(mProjectResourcePattern, mProjectResources) == false) { + // if we couldn't understand the current value, we default to the project resources + ResourceItem[] items = mProjectResources.getResources(mResourceType); + setListElements(items); + } + /* + * TODO: enable when we can display the system resources. + if (setupInitialSelection(mProjectResourcePattern, mProjectResources) == false) { + if (setupInitialSelection(mSystemResourcePattern, mSystemResources) == false) { + // if we couldn't understand the current value, we default to the project resources + IResourceItem[] items = mProjectResources.getResources(mResourceType); + setListElements(items); + mProjectButton.setSelection(true); + } else { + mSystemButton.setSelection(true); + } + } else { + mProjectButton.setSelection(true); + }*/ + } + + /** + * Attempts to setup the list of element from a repository if the current resource + * matches the provided pattern. + * @param pattern the pattern to test the current value + * @param repository the repository to use if the pattern matches. + * @return true if success. + */ + private boolean setupInitialSelection(Pattern pattern, IResourceRepository repository) { + Matcher m = pattern.matcher(mCurrentResource); + if (m.matches()) { + // we have a project resource, let's setup the list + ResourceItem[] items = repository.getResources(mResourceType); + setListElements(items); + + // and let's look for the item we found + String name = m.group(1); + + for (ResourceItem item : items) { + if (name.equals(item.getName())) { + setSelection(new Object[] { item }); + break; + } + } + return true; + } + return false; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java new file mode 100644 index 0000000..7c6a539 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2007 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.editors.wizards; + +import com.android.ide.eclipse.common.resources.IResourceRepository; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.manager.ConfigurableResourceItem; +import com.android.ide.eclipse.editors.resources.manager.ResourceFile; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Content provider for the Resource Explorer TreeView. + * Each level of the tree is represented by a different class. + *

                + *
              • {@link ResourceType}. This represents the list of existing Resource Type present + * in the resources. This can be matched to the subclasses inside the class R + *
              • + *
                  + *
                • {@link ResourceItem}. This represents one resource (which can existing in various alternate + * versions). This is similar to the resource Ids defined as R.sometype.id. + *
                • + *
                    + *
                  • {@link ResourceFile}. (optional) This represents a particular version of the + * {@link ResourceItem}. It is displayed as a list of resource qualifier. + *
                  • + *
                  + *
                + *
              + * + * @see ResourceLabelProvider + */ +public class ResourceContentProvider implements ITreeContentProvider { + + /** + * The current ProjectResources being displayed. + */ + private IResourceRepository mResources; + + private boolean mFullLevels; + + /** + * Constructs a new content providers for resource display. + * @param fullLevels if true the content provider will suppport all 3 levels. If + * false, only two levels are provided. + */ + public ResourceContentProvider(boolean fullLevels) { + mFullLevels = fullLevels; + } + + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof ResourceType) { + return mResources.getResources((ResourceType)parentElement); + } else if (mFullLevels && parentElement instanceof ConfigurableResourceItem) { + return ((ConfigurableResourceItem)parentElement).getSourceFileArray(); + } + return null; + } + + public Object getParent(Object element) { + // pass + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof ResourceType) { + return mResources.hasResources((ResourceType)element); + } else if (mFullLevels && element instanceof ConfigurableResourceItem) { + return ((ConfigurableResourceItem)element).hasAlternates(); + } + return false; + } + + public Object[] getElements(Object inputElement) { + if (inputElement instanceof IResourceRepository) { + if ((IResourceRepository)inputElement == mResources) { + // get the top level resources. + return mResources.getAvailableResourceTypes(); + } + } + + return new Object[0]; + } + + public void dispose() { + // pass + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof IResourceRepository) { + mResources = (IResourceRepository)newInput; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java new file mode 100644 index 0000000..024d084 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2007 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.editors.wizards; + +import com.android.ide.eclipse.common.resources.IIdResourceItem; +import com.android.ide.eclipse.common.resources.ResourceItem; +import com.android.ide.eclipse.common.resources.ResourceType; +import com.android.ide.eclipse.editors.resources.manager.ConfigurableResourceItem; +import com.android.ide.eclipse.editors.resources.manager.IdResourceItem; +import com.android.ide.eclipse.editors.resources.manager.ResourceFile; + +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + +/** + * Label provider for the Resource Explorer TreeView. + * Each level of the tree is represented by a different class. + *
                + *
              • {@link ResourceType}. This represents the list of existing Resource Type present + * in the resources. This can be matched to the subclasses inside the class R + *
              • + *
                  + *
                • {@link ResourceItem}. This represents one resource. The actual type can be + * {@link ConfigurableResourceItem} (which can exist in various alternate versions), + * or {@link IdResourceItem}. + * This is similar to the resource Ids defined as R.sometype.id. + *
                • + *
                    + *
                  • {@link ResourceFile}. This represents a particular version of the {@link ResourceItem}. + * It is displayed as a list of resource qualifier. + *
                  • + *
                  + *
                + *
              + * + * @see ResourceContentProvider + */ +public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvider { + private Image mWarningImage; + + public ResourceLabelProvider() { + mWarningImage = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( + ISharedImages.IMG_OBJS_WARN_TSK).createImage(); + } + + /** + * @see #getColumnImage(Object, int) + */ + public Image getImage(Object element) { + // pass + return null; + } + + /** + * @see #getColumnText(Object, int) + */ + public String getText(Object element) { + return getColumnText(element, 0); + } + + public void addListener(ILabelProviderListener listener) { + // pass + } + + public void dispose() { + mWarningImage.dispose(); + } + + public boolean isLabelProperty(Object element, String property) { + return false; + } + + public void removeListener(ILabelProviderListener listener) { + // pass + } + + public Image getColumnImage(Object element, int columnIndex) { + if (columnIndex == 1) { + if (element instanceof ConfigurableResourceItem) { + ConfigurableResourceItem item = (ConfigurableResourceItem)element; + if (item.hasDefault() == false) { + return mWarningImage; + } + } + } + return null; + } + + public String getColumnText(Object element, int columnIndex) { + switch (columnIndex) { + case 0: + if (element instanceof ResourceType) { + return ((ResourceType)element).getDisplayName(); + } else if (element instanceof ResourceItem) { + return ((ResourceItem)element).getName(); + } else if (element instanceof ResourceFile) { + return ((ResourceFile)element).getFolder().getConfiguration().toDisplayString(); + } + break; + case 1: + if (element instanceof ConfigurableResourceItem) { + ConfigurableResourceItem item = (ConfigurableResourceItem)element; + int count = item.getAlternateCount(); + if (count > 0) { + if (item.hasDefault()) { + count++; + } + return String.format("%1$d version(s)", count); + } + } else if (element instanceof IIdResourceItem) { + IIdResourceItem idResource = (IIdResourceItem)element; + if (idResource.isDeclaredInline()) { + return "Declared inline"; + } + } + return null; + } + return null; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java new file mode 100644 index 0000000..f28b523 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java @@ -0,0 +1,33 @@ +/* + * 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.editors.xml; + +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.editors.AndroidContentAssist; + +/** + * Content Assist Processor for /res/xml XML files + */ +class XmlContentAssist extends AndroidContentAssist { + + /** + * Constructor for LayoutContentAssist + */ + public XmlContentAssist() { + super(AndroidTargetData.DESCRIPTOR_XML); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlEditor.java new file mode 100644 index 0000000..b1900ae --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlEditor.java @@ -0,0 +1,201 @@ +/* + * 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.editors.xml; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.sdk.AndroidTargetData; +import com.android.ide.eclipse.adt.sdk.Sdk; +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.editors.AndroidEditor; +import com.android.ide.eclipse.editors.FirstElementParser; +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; +import com.android.sdklib.IAndroidTarget; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.part.FileEditorInput; +import org.w3c.dom.Document; + +/** + * Multi-page form editor for /res/xml XML files. + */ +public class XmlEditor extends AndroidEditor { + + public static final String ID = AndroidConstants.EDITORS_NAMESPACE + ".xml.XmlEditor"; //$NON-NLS-1$ + + /** Root node of the UI element hierarchy */ + private UiDocumentNode mUiRootNode; + + /** + * Creates the form editor for resources XML files. + */ + public XmlEditor() { + super(); + } + + /** + * Returns the root node of the UI element hierarchy, which here + * is the document node. + */ + @Override + public UiDocumentNode getUiRootNode() { + return mUiRootNode; + } + + // ---- Static ---- + + /** + * Indicates if this is a file that this {@link XmlEditor} can handle. + *

              + * The {@link XmlEditor} can handle XML files that have a or + * root XML element with the adequate xmlns:android attribute. + * + * @return True if the {@link XmlEditor} can handle that file. + */ + public static boolean canHandleFile(IFile file) { + // we need the target of the file's project to access the descriptors. + IProject project = file.getProject(); + IAndroidTarget target = Sdk.getCurrent().getTarget(project); + if (target != null) { + AndroidTargetData data = Sdk.getCurrent().getTargetData(target); + + FirstElementParser.Result result = FirstElementParser.parse( + file.getLocation().toOSString(), + AndroidConstants.NS_RESOURCES); + + if (result != null) { + String name = result.getElement(); + if (name != null && result.getXmlnsPrefix() != null) { + DocumentDescriptor desc = data.getXmlDescriptors().getDescriptor(); + for (ElementDescriptor elem : desc.getChildren()) { + if (elem.getXmlName().equals(name)) { + // This is an element that this document can handle + return true; + } + } + } + } + } + + return false; + } + + // ---- Base Class Overrides ---- + + /** + * Returns whether the "save as" operation is supported by this editor. + *

              + * Save-As is a valid operation for the ManifestEditor since it acts on a + * single source file. + * + * @see IEditorPart + */ + @Override + public boolean isSaveAsAllowed() { + return true; + } + + /** + * Create the various form pages. + */ + @Override + protected void createFormPages() { + try { + addPage(new XmlTreePage(this)); + } catch (PartInitException e) { + AdtPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ + } + + } + + /* (non-java doc) + * Change the tab/title name to include the project name. + */ + @Override + protected void setInput(IEditorInput input) { + super.setInput(input); + if (input instanceof FileEditorInput) { + FileEditorInput fileInput = (FileEditorInput) input; + IFile file = fileInput.getFile(); + setPartName(String.format("%1$s", file.getName())); + } + } + + /** + * Processes the new XML Model, which XML root node is given. + * + * @param xml_doc The XML document, if available, or null if none exists. + */ + @Override + protected void xmlModelChanged(Document xml_doc) { + // init the ui root on demand + initUiRootNode(false /*force*/); + + mUiRootNode.loadFromXmlNode(xml_doc); + + super.xmlModelChanged(xml_doc); + } + + /** + * Creates the initial UI Root Node, including the known mandatory elements. + * @param force if true, a new UiRootNode is recreated even if it already exists. + */ + @Override + protected void initUiRootNode(boolean force) { + // The root UI node is always created, even if there's no corresponding XML node. + if (mUiRootNode == null || force) { + Document doc = null; + if (mUiRootNode != null) { + doc = mUiRootNode.getXmlDocument(); + } + + // get the target data from the opened file (and its project) + AndroidTargetData data = getTargetData(); + + DocumentDescriptor desc; + if (data == null) { + desc = new DocumentDescriptor("temp", null /*children*/); + } else { + desc = data.getXmlDescriptors().getDescriptor(); + } + + mUiRootNode = (UiDocumentNode) desc.createUiNode(); + mUiRootNode.setEditor(this); + + onDescriptorsChanged(doc); + } + } + + // ---- Local Methods ---- + + /** + * Reloads the UI manifest node from the XML, and calls the pages to update. + */ + private void onDescriptorsChanged(Document document) { + if (document != null) { + mUiRootNode.loadFromXmlNode(document); + } else { + mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlNode()); + } + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java new file mode 100644 index 0000000..d25c812 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java @@ -0,0 +1,30 @@ +/* + * 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.editors.xml; + + +import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; + +/** + * Source Viewer Configuration that calls in XmlContentAssist. + */ +public class XmlSourceViewerConfig extends AndroidSourceViewerConfig { + + public XmlSourceViewerConfig() { + super(new XmlContentAssist()); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java new file mode 100644 index 0000000..91ce6dd --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java @@ -0,0 +1,62 @@ +/* + * 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.editors.xml; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; +import com.android.ide.eclipse.editors.uimodel.UiElementNode; + +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Page for the xml form editor. + */ +public final class XmlTreePage extends FormPage { + /** Page id used for switching tabs programmatically */ + public final static String PAGE_ID = "xml_tree_page"; //$NON-NLS-1$ + + /** Container editor */ + XmlEditor mEditor; + + public XmlTreePage(XmlEditor editor) { + super(editor, PAGE_ID, "Structure"); // tab's label, keep it short + mEditor = editor; + } + + /** + * Creates the content in the form hosted in this page. + * + * @param managedForm the form hosted in this page. + */ + @Override + protected void createFormContent(IManagedForm managedForm) { + super.createFormContent(managedForm); + ScrolledForm form = managedForm.getForm(); + form.setText("Android Xml"); + form.setImage(AdtPlugin.getAndroidLogo()); + + UiElementNode rootNode = mEditor.getUiRootNode(); + UiTreeBlock block = new UiTreeBlock(mEditor, rootNode, + true /* autoCreateRoot */, + null /* no element filters */, + "Xml Elements", + "List of all xml elements in this XML file."); + block.createContent(managedForm); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java new file mode 100644 index 0000000..31b4c61 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java @@ -0,0 +1,314 @@ +/* + * 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.editors.xml.descriptors; + +import com.android.ide.eclipse.common.AndroidConstants; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; +import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; +import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; +import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; +import com.android.ide.eclipse.editors.descriptors.IDescriptorProvider; +import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; +import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; +import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; + +import java.util.ArrayList; +import java.util.Map; + + +/** + * Description of the /res/xml structure. + * Currently supports the and root nodes. + */ +public final class XmlDescriptors implements IDescriptorProvider { + + // Public attributes names, attributes descriptors and elements descriptors referenced + // elsewhere. + public static final String PREF_KEY_ATTR = "key"; //$NON-NLS-1$ + + /** The root document descriptor for both searchable and preferences. */ + private DocumentDescriptor mDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ + + /** The root document descriptor for searchable. */ + private DocumentDescriptor mSearchDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ + + /** The root document descriptor for preferences. */ + private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ + + /** @return the root descriptor for both searchable and preferences. */ + public DocumentDescriptor getDescriptor() { + return mDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mDescriptor.getChildren(); + } + + /** @return the root descriptor for searchable. */ + public DocumentDescriptor getSearchableDescriptor() { + return mSearchDescriptor; + } + + /** @return the root descriptor for preferences. */ + public DocumentDescriptor getPreferencesDescriptor() { + return mPrefDescriptor; + } + + public IDescriptorProvider getSearchableProvider() { + return new IDescriptorProvider() { + public ElementDescriptor getDescriptor() { + return mSearchDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mSearchDescriptor.getChildren(); + } + }; + } + + public IDescriptorProvider getPreferencesProvider() { + return new IDescriptorProvider() { + public ElementDescriptor getDescriptor() { + return mPrefDescriptor; + } + + public ElementDescriptor[] getRootElementDescriptors() { + return mPrefDescriptor.getChildren(); + } + }; + } + + /** + * Updates the document descriptor. + *

              + * It first computes the new children of the descriptor and then updates them + * all at once. + * + * @param searchableStyleMap The map style=>attributes for from the attrs.xml file + * @param prefs The list of non-group preference descriptions + * @param prefGroups The list of preference group descriptions + */ + public synchronized void updateDescriptors( + Map searchableStyleMap, + ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) { + + XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( + "android", //$NON-NLS-1$ + AndroidConstants.NS_RESOURCES); + + ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns); + ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns); + ArrayList list = new ArrayList(); + if (searchable != null) { + list.add(searchable); + mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable }); + } + if (preferences != null) { + list.add(preferences); + mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences }); + } + + if (list.size() > 0) { + mDescriptor.setChildren(list.toArray(new ElementDescriptor[list.size()])); + } + } + + //------------------------- + // Creation of + //------------------------- + + /** + * Returns the new ElementDescriptor for + */ + private ElementDescriptor createSearchable( + Map searchableStyleMap, + XmlnsAttributeDescriptor xmlns) { + + ElementDescriptor action_key = createElement(searchableStyleMap, + "SearchableActionKey", //$NON-NLS-1$ styleName + "actionkey", //$NON-NLS-1$ xmlName + "Action Key", // uiName + null, // sdk url + null, // extraAttribute + null, // childrenElements + false /* mandatory */ ); + + ElementDescriptor searchable = createElement(searchableStyleMap, + "Searchable", //$NON-NLS-1$ styleName + "searchable", //$NON-NLS-1$ xmlName + "Searchable", // uiName + null, // sdk url + xmlns, // extraAttribute + new ElementDescriptor[] { action_key }, // childrenElements + false /* mandatory */ ); + return searchable; + } + + /** + * Returns a new ElementDescriptor constructed from the information given here + * and the javadoc & attributes extracted from the style map if any. + */ + private ElementDescriptor createElement( + Map styleMap, String styleName, + String xmlName, String uiName, String sdkUrl, + AttributeDescriptor extraAttribute, + ElementDescriptor[] childrenElements, boolean mandatory) { + + ElementDescriptor element = new ElementDescriptor(xmlName, uiName, null, sdkUrl, + null, childrenElements, mandatory); + + return updateElement(element, styleMap, styleName, extraAttribute); + } + + /** + * Updates an ElementDescriptor with the javadoc & attributes extracted from the style + * map if any. + */ + private ElementDescriptor updateElement(ElementDescriptor element, + Map styleMap, + String styleName, + AttributeDescriptor extraAttribute) { + ArrayList descs = new ArrayList(); + + DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; + if (style != null) { + DescriptorsUtils.appendAttributes(descs, + null, // elementName + AndroidConstants.NS_RESOURCES, + style.getAttributes(), + null, // requiredAttributes + null); // overrides + element.setTooltip(style.getJavaDoc()); + } + + if (extraAttribute != null) { + descs.add(extraAttribute); + } + + element.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); + return element; + } + + //-------------------------- + // Creation of + //-------------------------- + + /** + * Returns the new ElementDescriptor for + */ + private ElementDescriptor createPreference(ViewClassInfo[] prefs, + ViewClassInfo[] prefGroups, XmlnsAttributeDescriptor xmlns) { + + ArrayList newPrefs = new ArrayList(); + if (prefs != null) { + for (ViewClassInfo info : prefs) { + ElementDescriptor desc = convertPref(info); + newPrefs.add(desc); + } + } + + ElementDescriptor topPreferences = null; + + ArrayList newGroups = new ArrayList(); + if (prefGroups != null) { + for (ViewClassInfo info : prefGroups) { + ElementDescriptor desc = convertPref(info); + newGroups.add(desc); + + if (info.getCanonicalClassName() == AndroidConstants.CLASS_PREFERENCES) { + topPreferences = desc; + } + } + } + + ArrayList everything = new ArrayList(); + everything.addAll(newGroups); + everything.addAll(newPrefs); + ElementDescriptor[] newArray = everything.toArray(new ElementDescriptor[everything.size()]); + + // Link all groups to everything else here.. recursively + for (ElementDescriptor layoutDesc : newGroups) { + layoutDesc.setChildren(newArray); + } + + // The "top" element to be returned corresponds to the class "Preferences". + // Its descriptor has already been created. However the root one also needs + // the hidden xmlns:android definition.. + if (topPreferences != null) { + AttributeDescriptor[] attrs = topPreferences.getAttributes(); + AttributeDescriptor[] newAttrs = new AttributeDescriptor[attrs.length + 1]; + System.arraycopy(attrs, 0, newAttrs, 0, attrs.length); + newAttrs[attrs.length] = xmlns; + return new ElementDescriptor( + topPreferences.getXmlName(), + topPreferences.getUiName(), + topPreferences.getTooltip(), + topPreferences.getSdkUrl(), + newAttrs, + topPreferences.getChildren(), + false /* mandatory */); + } else { + return null; + } + } + + /** + * Creates an element descriptor from a given {@link ViewClassInfo}. + */ + private ElementDescriptor convertPref(ViewClassInfo info) { + String xml_name = info.getShortClassName(); + String tooltip = info.getJavaDoc(); + + // Process all Preference attributes + ArrayList attributes = new ArrayList(); + DescriptorsUtils.appendAttributes(attributes, + null, // elementName + AndroidConstants.NS_RESOURCES, + info.getAttributes(), + null, // requiredAttributes + null); // overrides + + for (ViewClassInfo link = info.getSuperClass(); + link != null; + link = link.getSuperClass()) { + AttributeInfo[] attrList = link.getAttributes(); + if (attrList.length > 0) { + attributes.add(new SeparatorAttributeDescriptor( + String.format("Attributes from %1$s", link.getShortClassName()))); + DescriptorsUtils.appendAttributes(attributes, + null, // elementName + AndroidConstants.NS_RESOURCES, + attrList, + null, // requiredAttributes + null); // overrides + } + } + + return new ViewElementDescriptor(xml_name, + xml_name, // ui_name + info.getCanonicalClassName(), + tooltip, + null, // sdk_url + attributes.toArray(new AttributeDescriptor[attributes.size()]), + null, + null, // children + false /* mandatory */); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/.classpath b/eclipse/plugins/com.android.ide.eclipse.common/.classpath deleted file mode 100644 index a7b3dc1..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.common/.project b/eclipse/plugins/com.android.ide.eclipse.common/.project deleted file mode 100644 index 510efb7..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - common - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/plugins/com.android.ide.eclipse.common/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.common/META-INF/MANIFEST.MF deleted file mode 100644 index dad85a6..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/META-INF/MANIFEST.MF +++ /dev/null @@ -1,22 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Android Common Plugin -Bundle-SymbolicName: com.android.ide.eclipse.common;singleton:=true -Bundle-Version: 0.8.1.qualifier -Bundle-ClassPath: ., - sdkstats.jar, - androidprefs.jar -Bundle-Vendor: The Android Open Source Project -Eclipse-LazyStart: true -Export-Package: com.android.ide.eclipse.common, - com.android.ide.eclipse.common.project, - com.android.ide.eclipse.common.resources -Require-Bundle: org.eclipse.core.resources, - org.eclipse.core.runtime, - org.eclipse.jdt.core, - org.eclipse.ui.console, - org.eclipse.ui, - org.eclipse.jdt.ui, - org.eclipse.jface.text, - org.eclipse.ui.editors -Bundle-Activator: com.android.ide.eclipse.common.CommonPlugin diff --git a/eclipse/plugins/com.android.ide.eclipse.common/MODULE_LICENSE_EPL b/eclipse/plugins/com.android.ide.eclipse.common/MODULE_LICENSE_EPL deleted file mode 100644 index e69de29..0000000 diff --git a/eclipse/plugins/com.android.ide.eclipse.common/NOTICE b/eclipse/plugins/com.android.ide.eclipse.common/NOTICE deleted file mode 100644 index 49c101d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/NOTICE +++ /dev/null @@ -1,224 +0,0 @@ -*Eclipse Public License - v 1.0* - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE -PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF -THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -*1. DEFINITIONS* - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and -documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: - -i) changes to the Program, and - -ii) additions to the Program; - -where such changes and/or additions to the Program originate from and -are distributed by that particular Contributor. A Contribution -'originates' from a Contributor if it was added to the Program by such -Contributor itself or anyone acting on such Contributor's behalf. -Contributions do not include additions to the Program which: (i) are -separate modules of software distributed in conjunction with the Program -under their own license agreement, and (ii) are not derivative works of -the Program. - -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents " mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, -including all Contributors. - -*2. GRANT OF RIGHTS* - -a) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free copyright -license to reproduce, prepare derivative works of, publicly display, -publicly perform, distribute and sublicense the Contribution of such -Contributor, if any, and such derivative works, in source code and -object code form. - -b) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free patent license -under Licensed Patents to make, use, sell, offer to sell, import and -otherwise transfer the Contribution of such Contributor, if any, in -source code and object code form. This patent license shall apply to the -combination of the Contribution and the Program if, at the time the -Contribution is added by the Contributor, such addition of the -Contribution causes such combination to be covered by the Licensed -Patents. The patent license shall not apply to any other combinations -which include the Contribution. No hardware per se is licensed hereunder. - -c) Recipient understands that although each Contributor grants the -licenses to its Contributions set forth herein, no assurances are -provided by any Contributor that the Program does not infringe the -patent or other intellectual property rights of any other entity. Each -Contributor disclaims any liability to Recipient for claims brought by -any other entity based on infringement of intellectual property rights -or otherwise. As a condition to exercising the rights and licenses -granted hereunder, each Recipient hereby assumes sole responsibility to -secure any other intellectual property rights needed, if any. For -example, if a third party patent license is required to allow Recipient -to distribute the Program, it is Recipient's responsibility to acquire -that license before distributing the Program. - -d) Each Contributor represents that to its knowledge it has sufficient -copyright rights in its Contribution, if any, to grant the copyright -license set forth in this Agreement. - -*3. REQUIREMENTS* - -A Contributor may choose to distribute the Program in object code form -under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and - -b) its license agreement: - -i) effectively disclaims on behalf of all Contributors all warranties -and conditions, express and implied, including warranties or conditions -of title and non-infringement, and implied warranties or conditions of -merchantability and fitness for a particular purpose; - -ii) effectively excludes on behalf of all Contributors all liability for -damages, including direct, indirect, special, incidental and -consequential damages, such as lost profits; - -iii) states that any provisions which differ from this Agreement are -offered by that Contributor alone and not by any other party; and - -iv) states that source code for the Program is available from such -Contributor, and informs licensees how to obtain it in a reasonable -manner on or through a medium customarily used for software exchange. - -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and - -b) a copy of this Agreement must be included with each copy of the Program. - -Contributors may not remove or alter any copyright notices contained -within the Program. - -Each Contributor must identify itself as the originator of its -Contribution, if any, in a manner that reasonably allows subsequent -Recipients to identify the originator of the Contribution. - -*4. COMMERCIAL DISTRIBUTION* - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, the -Contributor who includes the Program in a commercial product offering -should do so in a manner which does not create potential liability for -other Contributors. Therefore, if a Contributor includes the Program in -a commercial product offering, such Contributor ("Commercial -Contributor") hereby agrees to defend and indemnify every other -Contributor ("Indemnified Contributor") against any losses, damages and -costs (collectively "Losses") arising from claims, lawsuits and other -legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the -Program in a commercial product offering. The obligations in this -section do not apply to any claims or Losses relating to any actual or -alleged intellectual property infringement. In order to qualify, an -Indemnified Contributor must: a) promptly notify the Commercial -Contributor in writing of such claim, and b) allow the Commercial -Contributor to control, and cooperate with the Commercial Contributor -in, the defense and any related settlement negotiations. The Indemnified -Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those -performance claims and warranties, and if a court requires any other -Contributor to pay any damages as a result, the Commercial Contributor -must pay those damages. - -*5. NO WARRANTY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED -ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES -OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR -A PARTICULAR PURPOSE. Each Recipient is solely responsible for -determining the appropriateness of using and distributing the Program -and assumes all risks associated with its exercise of rights under this -Agreement , including but not limited to the risks and costs of program -errors, compliance with applicable laws, damage to or loss of data, -programs or equipment, and unavailability or interruption of operations. - -*6. DISCLAIMER OF LIABILITY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR -ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING -WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR -DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -*7. GENERAL* - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further action -by the parties hereto, such provision shall be reformed to the minimum -extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including -a cross-claim or counterclaim in a lawsuit) alleging that the Program -itself (excluding combinations of the Program with other software or -hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails -to comply with any of the material terms or conditions of this Agreement -and does not cure such failure in a reasonable period of time after -becoming aware of such noncompliance. If all Recipient's rights under -this Agreement terminate, Recipient agrees to cease use and distribution -of the Program as soon as reasonably practicable. However, Recipient's -obligations under this Agreement and any licenses granted by Recipient -relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and may -only be modified in the following manner. The Agreement Steward reserves -the right to publish new versions (including revisions) of this -Agreement from time to time. No one other than the Agreement Steward has -the right to modify this Agreement. The Eclipse Foundation is the -initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is -published, Contributor may elect to distribute the Program (including -its Contributions) under the new version. Except as expressly stated in -Sections 2(a) and 2(b) above, Recipient receives no rights or licenses -to the intellectual property of any Contributor under this Agreement, -whether expressly, by implication, estoppel or otherwise. All rights in -the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the -intellectual property laws of the United States of America. No party to -this Agreement will bring a legal action under this Agreement more than -one year after the cause of action arose. Each party waives its rights -to a jury trial in any resulting litigation. - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.common/build.properties b/eclipse/plugins/com.android.ide.eclipse.common/build.properties deleted file mode 100644 index 4c9981d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/build.properties +++ /dev/null @@ -1,7 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - sdkstats.jar,\ - plugin.xml,\ - androidprefs.jar diff --git a/eclipse/plugins/com.android.ide.eclipse.common/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.common/plugin.xml deleted file mode 100644 index 115eb97..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/plugin.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/AndroidConstants.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/AndroidConstants.java deleted file mode 100644 index 02cef2d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/AndroidConstants.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (C) 2007 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.common; - -import java.io.File; - -/** - * Constant definition class.
              - *
              - * Most constants have a prefix defining the content. - *

                - *
              • WS_ Workspace path constant. Those are absolute paths, - * from the project root.
              • - *
              • OS_ OS path constant. These paths are different depending on the platform.
              • - *
              • FN_ File name constant.
              • - *
              • FD_ Folder name constant.
              • - *
              • MARKER_ Resource Marker Ids constant.
              • - *
              • EXT_ File extension constant. This does NOT include a dot.
              • - *
              • DOT_ File extension constant. This start with a dot.
              • - *
              • RE_ Regexp constant.
              • - *
              • NS_ Namespace constant.
              • - *
              • CLASS_ Fully qualified class name.
              • - *
              - * - */ -public class AndroidConstants { - /** The Editors Plugin ID */ - public static final String EDITORS_PLUGIN_ID = "com.android.ide.eclipse.editors"; // $NON-NLS-1$ - - /** Nature of android projects */ - public final static String NATURE = "com.android.ide.eclipse.adt.AndroidNature"; //$NON-NLS-1$ - - public final static int PLATFORM_UNKNOWN = 0; - public final static int PLATFORM_LINUX = 1; - public final static int PLATFORM_WINDOWS = 2; - public final static int PLATFORM_DARWIN = 3; - - /** - * Returns current platform, one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, - * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. - */ - public final static int CURRENT_PLATFORM = currentPlatform(); - - /** Separator for workspace path, i.e. "/". */ - public final static String WS_SEP = "/"; //$NON-NLS-1$ - /** Separator character for workspace path, i.e. '/'. */ - public final static char WS_SEP_CHAR = '/'; - - /** Extension of the Application package Files, i.e. "apk". */ - public final static String EXT_ANDROID_PACKAGE = "apk"; //$NON-NLS-1$ - /** Extension of java files, i.e. "java" */ - public final static String EXT_JAVA = "java"; //$NON-NLS-1$ - /** Extension of compiled java files, i.e. "class" */ - public final static String EXT_CLASS = "class"; //$NON-NLS-1$ - /** Extension of xml files, i.e. "xml" */ - public final static String EXT_XML = "xml"; //$NON-NLS-1$ - /** Extension of jar files, i.e. "jar" */ - public final static String EXT_JAR = "jar"; //$NON-NLS-1$ - /** Extension of aidl files, i.e. "aidl" */ - public final static String EXT_AIDL = "aidl"; //$NON-NLS-1$ - - private final static String DOT = "."; //$NON-NLS-1$ - - /** Dot-Extension of the Application package Files, i.e. ".apk". */ - public final static String DOT_ANDROID_PACKAGE = DOT + EXT_ANDROID_PACKAGE; - /** Dot-Extension of java files, i.e. ".java" */ - public final static String DOT_JAVA = DOT + EXT_JAVA; - /** Dot-Extension of compiled java files, i.e. ".class" */ - public final static String DOT_CLASS = DOT + EXT_CLASS; - /** Dot-Extension of xml files, i.e. ".xml" */ - public final static String DOT_XML = DOT + EXT_XML; - /** Dot-Extension of jar files, i.e. ".jar" */ - public final static String DOT_JAR = DOT + EXT_JAR; - /** Dot-Extension of aidl files, i.e. ".aidl" */ - public final static String DOT_AIDL = DOT + EXT_AIDL; - - /** Name of the manifest file, i.e. "AndroidManifest.xml". */ - public static final String FN_ANDROID_MANIFEST = "AndroidManifest.xml"; //$NON-NLS-1$ - - /** Name of the framework library, i.e. "android.jar" */ - public static final String FN_FRAMEWORK_LIBRARY = "android.jar"; //$NON-NLS-1$ - /** Name of the layout attributes, i.e. "attrs.xml" */ - public static final String FN_ATTRS_XML = "attrs.xml"; //$NON-NLS-1$ - /** Name of the layout attributes, i.e. "attrs_manifest.xml" */ - public static final String FN_ATTRS_MANIFEST_XML = "attrs_manifest.xml"; //$NON-NLS-1$ - /** framework aidl import file */ - public static final String FN_FRAMEWORK_AIDL = "framework.aidl"; //$NON-NLS-1$ - /** layoutlib.jar file */ - public static final String FN_LAYOUTLIB_JAR = "layoutlib.jar"; //$NON-NLS-1$ - /** dex.jar file */ - public static final String FN_DX_JAR = "dx.jar"; //$NON-NLS-1$ - /** Name of the android sources directory */ - public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$ - - /** Resource java class filename, i.e. "R.java" */ - public final static String FN_RESOURCE_CLASS = "R.java"; //$NON-NLS-1$ - /** Resource class file filename, i.e. "R.class" */ - public final static String FN_COMPILED_RESOURCE_CLASS = "R.class"; //$NON-NLS-1$ - /** Manifest java class filename, i.e. "Manifest.java" */ - public final static String FN_MANIFEST_CLASS = "Manifest.java"; //$NON-NLS-1$ - /** Dex conversion output filname, i.e. "classes.dex" */ - public final static String FN_CLASSES_DEX = "classes.dex"; //$NON-NLS-1$ - /** Temporary packaged resources file name, i.e. "resources.ap_" */ - public final static String FN_RESOURCES_AP_ = "resources.ap_"; //$NON-NLS-1$ - /** build properties file */ - public final static String FN_BUILD_PROP = "build.prop"; //$NON-NLS-1$ - /** plugin properties file */ - public final static String FN_PLUGIN_PROP = "plugin.prop"; //$NON-NLS-1$ - - public final static String FN_ADB = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "adb.exe" : "adb"; //$NON-NLS-1$ //$NON-NLS-2$ - - public final static String FN_AAPT = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "aapt.exe" : "aapt"; //$NON-NLS-1$ //$NON-NLS-2$ - - public final static String FN_AIDL = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "aidl.exe" : "aidl"; //$NON-NLS-1$ //$NON-NLS-2$ - - public final static String FN_EMULATOR = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "emulator.exe" : "emulator"; //$NON-NLS-1$ //$NON-NLS-2$ - - public final static String FN_TRACEVIEW = (CURRENT_PLATFORM == PLATFORM_WINDOWS) ? - "traceview.exe" : "traceview"; //$NON-NLS-1$ //$NON-NLS-2$ - - /** Skin layout file */ - public final static String FN_LAYOUT = "layout";//$NON-NLS-1$ - - /** Resources folder name, i.e. "res". */ - public final static String FD_RESOURCES = "res"; //$NON-NLS-1$ - /** Assets folder name, i.e. "assets" */ - public final static String FD_ASSETS = "assets"; //$NON-NLS-1$ - /** Default source folder name, i.e. "src" */ - public final static String FD_SOURCES = "src"; //$NON-NLS-1$ - /** Default bin folder name, i.e. "bin" */ - public final static String FD_BINARIES = "bin"; //$NON-NLS-1$ - /** Default anim resource folder name, i.e. "anim" */ - public final static String FD_ANIM = "anim"; //$NON-NLS-1$ - /** Default color resource folder name, i.e. "color" */ - public final static String FD_COLOR = "color"; //$NON-NLS-1$ - /** Default drawable resource folder name, i.e. "drawable" */ - public final static String FD_DRAWABLE = "drawable"; //$NON-NLS-1$ - /** Default layout resource folder name, i.e. "layout" */ - public final static String FD_LAYOUT = "layout"; //$NON-NLS-1$ - /** Default menu resource folder name, i.e. "menu" */ - public final static String FD_MENU = "menu"; //$NON-NLS-1$ - /** Default values resource folder name, i.e. "values" */ - public final static String FD_VALUES = "values"; //$NON-NLS-1$ - /** Default xml resource folder name, i.e. "xml" */ - public final static String FD_XML = "xml"; //$NON-NLS-1$ - /** Default raw resource folder name, i.e. "raw" */ - public final static String FD_RAW = "raw"; //$NON-NLS-1$ - /** Name of the tools folder. */ - public final static String FD_TOOLS = "tools"; //$NON-NLS-1$ - /** Name of the libs folder. */ - public final static String FD_LIBS = "lib"; //$NON-NLS-1$ - /** Name of the docs folder. */ - public final static String FD_DOCS = "docs"; //$NON-NLS-1$ - /** Name of the images folder. */ - public final static String FD_IMAGES = "images"; //$NON-NLS-1$ - /** Name of the skins folder. */ - public final static String FD_SKINS = "skins"; //$NON-NLS-1$ - /** Name of the samples folder. */ - public final static String FD_SAMPLES = "samples"; //$NON-NLS-1$ - /** Name of the folder containing the default framework resources. */ - public final static String FD_DEFAULT_RES = "default"; //$NON-NLS-1$ - /** SDK font folder name, i.e. "fonts" */ - public final static String FD_FONTS = "fonts"; //$NON-NLS-1$ - - /** Absolute path of the workspace root, i.e. "/" */ - public final static String WS_ROOT = WS_SEP; - - /** Absolute path of the resource folder, eg "/res".
              This is a workspace path. */ - public final static String WS_RESOURCES = WS_SEP + FD_RESOURCES; - - /** Absolute path of the resource folder, eg "/assets".
              This is a workspace path. */ - public final static String WS_ASSETS = WS_SEP + FD_ASSETS; - - /** Leaf of the javaDoc folder. Does not start with a separator. */ - public final static String WS_JAVADOC_FOLDER_LEAF = FD_DOCS + "/reference"; //$NON-NLS-1$ - - /** Path of the documentation directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_DOCS_FOLDER = FD_DOCS + File.separator; - - /** Path of the tools directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_TOOLS_FOLDER = FD_TOOLS + File.separator; - - /** Path of the samples directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_SAMPLES_FOLDER = FD_SAMPLES + File.separator; - - /** Path of the lib directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_LIBS_FOLDER = - OS_SDK_TOOLS_FOLDER + FD_LIBS + File.separator; - - /** Path of the resources directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_RESOURCES_FOLDER = - OS_SDK_LIBS_FOLDER + FD_RESOURCES + File.separator; - - /** Path of the resources directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_FONTS_FOLDER = - OS_SDK_LIBS_FOLDER + FD_FONTS + File.separator; - - /** Path of the skin directory relative to the sdk folder. - * This is an OS path, ending with a separator. */ - public final static String OS_SDK_SKINS_FOLDER = - OS_SDK_LIBS_FOLDER + FD_IMAGES + File.separator + FD_SKINS + File.separator; - - /** Path of the attrs.xml file relative to the sdk folder. */ - public final static String OS_SDK_ATTRS_XML = - OS_SDK_RESOURCES_FOLDER + File.separator + FD_DEFAULT_RES + File.separator + - FD_VALUES + File.separator + FN_ATTRS_XML; - - /** Path of the attrs_manifest.xml file relative to the sdk folder. */ - public final static String OS_SDK_ATTRS_MANIFEST_XML = - OS_SDK_RESOURCES_FOLDER + File.separator + FD_DEFAULT_RES + File.separator + - FD_VALUES + File.separator + FN_ATTRS_MANIFEST_XML; - - /** Path of the layoutlib.jar file relative to the sdk folder. */ - public final static String OS_SDK_LIBS_LAYOUTLIB_JAR = - OS_SDK_LIBS_FOLDER + FN_LAYOUTLIB_JAR; - - /** Path of the dx.jar file relative to the sdk folder. */ - public final static String OS_SDK_LIBS_DX_JAR = - OS_SDK_LIBS_FOLDER + FN_DX_JAR; - - /** Regexp for single dot */ - public final static String RE_DOT = "\\."; //$NON-NLS-1$ - /** Regexp for java extension, i.e. "\.java$" */ - public final static String RE_JAVA_EXT = "\\.java$"; //$NON-NLS-1$ - /** Regexp for aidl extension, i.e. "\.aidl$" */ - public final static String RE_AIDL_EXT = "\\.aidl$"; //$NON-NLS-1$ - - /** Namespace for the resource XML, i.e. "http://schemas.android.com/apk/res/android" */ - public final static String NS_RESOURCES = "http://schemas.android.com/apk/res/android"; //$NON-NLS-1$ - - /** Namespace pattern for the custom resource XML, i.e. "http://schemas.android.com/apk/res/%s" */ - public final static String NS_CUSTOM_RESOURCES = "http://schemas.android.com/apk/res/%1$s"; //$NON-NLS-1$ - - /** aapt marker error. */ - public final static String MARKER_AAPT = CommonPlugin.PLUGIN_ID + ".aaptProblem"; //$NON-NLS-1$ - - /** XML marker error. */ - public final static String MARKER_XML = CommonPlugin.PLUGIN_ID + ".xmlProblem"; //$NON-NLS-1$ - - /** aidl marker error. */ - public final static String MARKER_AIDL = CommonPlugin.PLUGIN_ID + ".aidlProblem"; //$NON-NLS-1$ - - /** android marker error */ - public final static String MARKER_ANDROID = CommonPlugin.PLUGIN_ID + ".androidProblem"; //$NON-NLS-1$ - - /** Name for the "type" marker attribute */ - public final static String MARKER_ATTR_TYPE = "android.type"; //$NON-NLS-1$ - /** Name for the "class" marker attribute */ - public final static String MARKER_ATTR_CLASS = "android.class"; //$NON-NLS-1$ - /** activity value for marker attribute "type" */ - public final static String MARKER_ATTR_TYPE_ACTIVITY = "activity"; //$NON-NLS-1$ - /** service value for marker attribute "type" */ - public final static String MARKER_ATTR_TYPE_SERVICE = "service"; //$NON-NLS-1$ - /** receiver value for marker attribute "type" */ - public final static String MARKER_ATTR_TYPE_RECEIVER = "receiver"; //$NON-NLS-1$ - /** provider value for marker attribute "type" */ - public final static String MARKER_ATTR_TYPE_PROVIDER = "provider"; //$NON-NLS-1$ - - public final static String CLASS_ACTIVITY = "android.app.Activity"; //$NON-NLS-1$ - public final static String CLASS_SERVICE = "android.app.Service"; //$NON-NLS-1$ - public final static String CLASS_BROADCASTRECEIVER = "android.content.BroadcastReceiver"; //$NON-NLS-1$ - public final static String CLASS_CONTENTPROVIDER = "android.content.ContentProvider"; //$NON-NLS-1$ - public final static String CLASS_INSTRUMENTATION = "android.app.Instrumentation"; //$NON-NLS-1$ - public final static String CLASS_BUNDLE = "android.os.Bundle"; //$NON-NLS-1$ - public final static String CLASS_R = "android.R"; //$NON-NLS-1$ - public final static String CLASS_MANIFEST_PERMISSION = "android.Manifest$permission"; //$NON-NLS-1$ - public final static String CLASS_INTENT = "android.content.Intent"; //$NON-NLS-1$ - public final static String CLASS_CONTEXT = "android.content.Context"; //$NON-NLS-1$ - public final static String CLASS_VIEW = "android.view.View"; //$NON-NLS-1$ - public final static String CLASS_VIEWGROUP = "android.view.ViewGroup"; //$NON-NLS-1$ - public final static String CLASS_LAYOUTPARAMS = "LayoutParams"; //$NON-NLS-1$ - public final static String CLASS_VIEWGROUP_LAYOUTPARAMS = - CLASS_VIEWGROUP + "$" + CLASS_LAYOUTPARAMS; //$NON-NLS-1$ - public final static String CLASS_FRAMELAYOUT = "FrameLayout"; //$NON-NLS-1$ - public final static String CLASS_PREFERENCE = "android.preference.Preference"; //$NON-NLS-1$ - public final static String CLASS_PREFERENCE_SCREEN = "PreferenceScreen"; //$NON-NLS-1$ - public final static String CLASS_PREFERENCES = - "android.preference." + CLASS_PREFERENCE_SCREEN; //$NON-NLS-1$ - public final static String CLASS_PREFERENCEGROUP = "android.preference.PreferenceGroup"; //$NON-NLS-1$ - - public final static String CLASS_BRIDGE = "com.android.layoutlib.bridge.Bridge"; //$NON-NLS-1$ - - /** - * Prefered compiler level, i.e. "1.5". - */ - public final static String COMPILER_COMPLIANCE_PREFERRED = "1.5"; //$NON-NLS-1$ - /** - * List of valid compiler level, i.e. "1.5" and "1.6" - */ - public final static String[] COMPILER_COMPLIANCE = { - "1.5", //$NON-NLS-1$ - "1.6", //$NON-NLS-1$ - }; - - /** The base URL where to find the Android class & manifest documentation */ - public static final String CODESITE_BASE_URL = "http://code.google.com/android"; //$NON-NLS-1$ - - /** - * Returns current platform - * - * @return one of {@link #PLATFORM_WINDOWS}, {@link #PLATFORM_DARWIN}, - * {@link #PLATFORM_LINUX} or {@link #PLATFORM_UNKNOWN}. - */ - private static int currentPlatform() { - String os = System.getProperty("os.name"); //$NON-NLS-1$ - if (os.startsWith("Mac OS")) { //$NON-NLS-1$ - return PLATFORM_DARWIN; - } else if (os.startsWith("Windows")) { //$NON-NLS-1$ - return PLATFORM_WINDOWS; - } else if (os.startsWith("Linux")) { //$NON-NLS-1$ - return PLATFORM_LINUX; - } - - return PLATFORM_UNKNOWN; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/CommonPlugin.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/CommonPlugin.java deleted file mode 100644 index cfee50f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/CommonPlugin.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.common; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.console.ConsolePlugin; -import org.eclipse.ui.console.IConsole; -import org.eclipse.ui.console.MessageConsole; -import org.eclipse.ui.plugin.AbstractUIPlugin; -import org.osgi.framework.BundleContext; - - -/** - * The activator class controls the plug-in life cycle - */ -public class CommonPlugin extends AbstractUIPlugin { - - // The plug-in ID - public static final String PLUGIN_ID = "com.android.ide.eclipse.common"; // $NON-NLS-1$ - - // The shared instance - private static CommonPlugin sPlugin; - - // The global android console - private MessageConsole mAndroidConsole; - - /** - * The constructor - */ - public CommonPlugin() { - // pass - } - - /** - * Returns the shared instance - * - * @return the shared instance - */ - public static CommonPlugin getDefault() { - return sPlugin; - } - - /** Returns the global android console */ - public MessageConsole getAndroidConsole() { - return mAndroidConsole; - } - - /** - * The AbstractUIPlugin implementation of this Plugin - * method refreshes the plug-in actions. Subclasses may extend this method, - * but must send super first. - * - * {@inheritDoc} - */ - @Override - public void start(BundleContext context) throws Exception { - super.start(context); - sPlugin = this; - - /* - * WARNING: think before adding any initialization here as plugins are dynamically - * started and since no UI is being displayed by this plugin, it'll only start when - * another plugin accesses some of its code. - */ - - // set the default android console. - mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$ - ConsolePlugin.getDefault().getConsoleManager().addConsoles( - new IConsole[] { mAndroidConsole }); - } - - /** - * The AbstractUIPlugin implementation of this Plugin - * method saves this plug-in's preference and dialog stores and shuts down - * its image registry (if they are in use). Subclasses may extend this - * method, but must send super last. A try-finally statement should - * be used where necessary to ensure that super.shutdown() is - * always done. - * - * {@inheritDoc} - */ - @Override - public void stop(BundleContext context) throws Exception { - sPlugin = null; - super.stop(context); - } - - /** - * Logs a message to the default Eclipse log. - * - * @param severity One of IStatus' severity codes: OK, ERROR, INFO, WARNING or CANCEL. - * @param format The format string, like for String.format(). - * @param args The arguments for the format string, like for String.format(). - */ - public static void log(int severity, String format, Object ... args) { - String message = String.format(format, args); - Status status = new Status(severity, PLUGIN_ID, message); - getDefault().getLog().log(status); - } - - /** - * Logs an exception to the default Eclipse log. - *

              - * The status severity is always set to ERROR. - * - * @param exception The exception to log. Its call trace will be recorded. - * @param format The format string, like for String.format(). - * @param args The arguments for the format string, like for String.format(). - */ - public static void log(Throwable exception, String format, Object ... args) { - String message = String.format(format, args); - Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); - getDefault().getLog().log(status); - } - - private static Display getDisplay() { - IWorkbench bench = sPlugin.getWorkbench(); - if (bench!=null) { - return bench.getDisplay(); - } - return null; - } - - /** - * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread, - * therefore this message can be called from any thread. - * @param title The title of the dialog box - * @param message The error message - * @return true if OK was clicked. - */ - public final static boolean displayPrompt(final String title, final String message) { - // get the current Display and Shell - final Display display = getDisplay(); - - // we need to ask the user what he wants to do. - final Boolean[] wrapper = new Boolean[] { new Boolean(false) }; - display.syncExec(new Runnable() { - public void run() { - Shell shell = display.getActiveShell(); - wrapper[0] = new Boolean(MessageDialog.openQuestion(shell, title, message)); - } - }); - return wrapper[0].booleanValue(); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/EclipseUiHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/EclipseUiHelper.java deleted file mode 100644 index 8dd8e60..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/EclipseUiHelper.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.common; - -import org.eclipse.ui.IViewPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; - -/** - * Helpers for Eclipse UI related stuff. - */ -public final class EclipseUiHelper { - - /** View Id for the default Eclipse Content Outline view. */ - public static final String CONTENT_OUTLINE_VIEW_ID = "org.eclipse.ui.views.ContentOutline"; - /** View Id for the default Eclipse Property Sheet view. */ - public static final String PROPERTY_SHEET_VIEW_ID = "org.eclipse.ui.views.PropertySheet"; - - /** This class never gets instantiated. */ - private EclipseUiHelper() { - } - - /** - * Shows the corresponding view. - *

              - * Silently fails in case of error. - * - * @param viewId One of {@link #CONTENT_OUTLINE_VIEW_ID}, {@link #PROPERTY_SHEET_VIEW_ID}. - * @param activate True to force activate (i.e. takes focus), false to just make visible (i.e. - * does not steal focus.) - */ - public static void showView(String viewId, boolean activate) { - IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (win != null) { - IWorkbenchPage page = win.getActivePage(); - if (page != null) { - try { - IViewPart part = page.showView(viewId, - null /* secondaryId */, - activate ? page.VIEW_ACTIVATE : page.VIEW_VISIBLE); - } catch (PartInitException e) { - // ignore - } - } - } - - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/Messages.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/Messages.java deleted file mode 100644 index 3f1bde4..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/Messages.java +++ /dev/null @@ -1,21 +0,0 @@ - -package com.android.ide.eclipse.common; - -import org.eclipse.osgi.util.NLS; - -public class Messages extends NLS { - private static final String BUNDLE_NAME = "com.android.ide.eclipse.common.messages"; //$NON-NLS-1$ - - public static String Console_Data_Project_Tag; - - public static String Console_Date_Tag; - - - static { - // initialize resource bundle - NLS.initializeMessages(BUNDLE_NAME, Messages.class); - } - - private Messages() { - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/SdkStatsHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/SdkStatsHelper.java deleted file mode 100644 index 345c663..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/SdkStatsHelper.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.common; - -import com.android.sdkstats.SdkStatsService; - -import org.osgi.framework.Version; - -/** - * Helper class to access the ping usage stat server. - */ -public class SdkStatsHelper { - - /** - * Pings the usage start server. - * @param pluginName the name of the plugin to appear in the stats - * @param pluginVersion the {@link Version} of the plugin. - */ - public static void pingUsageServer(String pluginName, Version pluginVersion) { - String versionString = String.format("%1$d.%2$d.%3$d", pluginVersion.getMajor(), - pluginVersion.getMinor(), pluginVersion.getMicro()); - - SdkStatsService.ping(pluginName, versionString); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/StreamHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/StreamHelper.java deleted file mode 100644 index 6ccf4f2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/StreamHelper.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2007 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.common; - -import org.eclipse.ui.console.MessageConsoleStream; - -import java.util.Calendar; - -/** - * Stream helper class. - */ -public class StreamHelper { - - /** - * Prints messages, associated with a project to the specified stream - * @param stream The stream to write to - * @param tag The tag associated to the message. Can be null - * @param objects The objects to print through their toString() method (or directly for - * {@link String} objects. - */ - public static synchronized void printToStream(MessageConsoleStream stream, String tag, - Object... objects) { - String dateTag = getMessageTag(tag); - - for (Object obj : objects) { - stream.print(dateTag); - if (obj instanceof String) { - stream.println((String)obj); - } else { - stream.println(obj.toString()); - } - } - } - - /** - * Creates a string containing the current date/time, and the tag - * @param tag The tag associated to the message. Can be null - * @return The dateTag - */ - public static String getMessageTag(String tag) { - Calendar c = Calendar.getInstance(); - - if (tag == null) { - return String.format(Messages.Console_Date_Tag, c); - } - - return String.format(Messages.Console_Data_Project_Tag, c, tag); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/messages.properties b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/messages.properties deleted file mode 100644 index dba6edc..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/messages.properties +++ /dev/null @@ -1,2 +0,0 @@ -Console_Date_Tag=[%1$tF %1$tT] -Console_Data_Project_Tag=[%1$tF %1$tT - %2$s] diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java deleted file mode 100644 index 58c2f40..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/preferences/UsagePreferencePage.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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.common.preferences; - -import com.android.sdkstats.SdkStatsService; - -import org.eclipse.jface.preference.BooleanFieldEditor; -import org.eclipse.jface.preference.PreferencePage; -import org.eclipse.jface.preference.PreferenceStore; -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.Control; -import org.eclipse.swt.widgets.Link; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPreferencePage; - -import java.io.IOException; - -public class UsagePreferencePage extends PreferencePage implements IWorkbenchPreferencePage { - - private BooleanFieldEditor mOptInCheckBox; - - public UsagePreferencePage() { - } - - public void init(IWorkbench workbench) { - // pass - } - - @Override - protected Control createContents(Composite parent) { - Composite top = new Composite(parent, SWT.NONE); - top.setLayout(new GridLayout(1, false)); - top.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - Link text = new Link(top, SWT.WRAP); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.widthHint = 200; - text.setLayoutData(gd); - text.setText(SdkStatsService.BODY_TEXT); - - text.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - SdkStatsService.openUrl(event.text); - } - }); - - mOptInCheckBox = new BooleanFieldEditor(SdkStatsService.PING_OPT_IN, - SdkStatsService.CHECKBOX_TEXT, top); - mOptInCheckBox.setPage(this); - mOptInCheckBox.setPreferenceStore(SdkStatsService.getPreferenceStore()); - mOptInCheckBox.load(); - - return top; - } - - /* (non-Javadoc) - * @see org.eclipse.jface.preference.PreferencePage#performCancel() - */ - @Override - public boolean performCancel() { - mOptInCheckBox.load(); - return super.performCancel(); - } - - /* (non-Javadoc) - * @see org.eclipse.jface.preference.PreferencePage#performDefaults() - */ - @Override - protected void performDefaults() { - mOptInCheckBox.loadDefault(); - super.performDefaults(); - } - - /* (non-Javadoc) - * @see org.eclipse.jface.preference.PreferencePage#performOk() - */ - @Override - public boolean performOk() { - save(); - return super.performOk(); - } - - /* (non-Javadoc) - * @see org.eclipse.jface.preference.PreferencePage#performApply() - */ - @Override - protected void performApply() { - save(); - super.performApply(); - } - - private void save() { - try { - PreferenceStore store = SdkStatsService.getPreferenceStore(); - if (store != null) { - store.setValue(SdkStatsService.PING_OPT_IN, mOptInCheckBox.getBooleanValue()); - store.save(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java deleted file mode 100644 index 2db9e9b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestHelper.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2007 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.xml.sax.InputSource; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathExpressionException; - -/** - * Utility class that manages the AndroidManifest.xml file. - *

              - * All the get method work by XPath. Repeated calls to those may warrant using - * {@link AndroidManifestParser} instead. - */ -public class AndroidManifestHelper { - private IFile mManifestIFile; - private File mManifestFile; - private XPath mXPath; - - /** - * Creates an AndroidManifest based on an existing Eclipse {@link IProject} object. - *

              - * Use {@link #exists()} to check if the manifest file really exists in the project. - * - * @param project The project to search for the manifest. - */ - public AndroidManifestHelper(IProject project) { - mXPath = AndroidXPathFactory.newXPath(); - mManifestIFile = getManifest(project); - } - - /** - * Creates an AndroidManifest based on a file path. - *

              - * Use {@link #exists()} to check if the manifest file really exists. - * - * @param osManifestFilePath the os path to the AndroidManifest.xml file. - */ - public AndroidManifestHelper(String osManifestFilePath) { - mXPath = AndroidXPathFactory.newXPath(); - mManifestFile = new File(osManifestFilePath); - } - - - /** - * Returns the underlying {@link IFile} for the android manifest XML file, if found in the - * given Eclipse project. - * - * Always return null if the constructor that takes an {@link IProject} was NOT called. - * - * @return The IFile for the androidManifest.xml or null if no such file could be found. - */ - public IFile getManifestIFile() { - return mManifestIFile; - } - - /** - * Returns the underlying {@link File} for the android manifest XML file. - */ - public File getManifestFile() { - if (mManifestIFile != null) { - return mManifestIFile.getLocation().toFile(); - } - - return mManifestFile; - } - - /** - * Returns the package name defined in the manifest file. - * - * @return A String object with the package or null if any error happened. - */ - public String getPackageName() { - try { - return getPackageNameInternal(mXPath, getSource()); - } catch (XPathExpressionException e1) { - // If the XPath failed to evaluate, we'll return null. - } catch (Exception e) { - // if this happens this is due to the resource being out of sync. - // so we must refresh it and do it again - - // for any other kind of exception we must return null as well; - } - - return null; - } - - /** - * Returns the i-th activity defined in the manifest file. - * - * @param manifest The manifest's IFile object. - * @param index The 1-based index of the activity to return. - * @param xpath An optional xpath object. If null is provided a new one will - * be created. - * @return A String object with the activity or null if any error happened. - */ - public String getActivityName(int index) { - try { - return getActivityNameInternal(index, mXPath, getSource()); - } catch (XPathExpressionException e1) { - // If the XPath failed to evaluate, we'll return null. - } catch (Exception e) { - // if this happens this is due to the resource being out of sync. - // so we must refresh it and do it again - - // for any other kind of exception we must return null as well; - } - return null; - } - - /** - * Returns an IFile object representing the manifest for the specified - * project. - * - * @param project The project containing the manifest file. - * @return An IFile object pointing to the manifest or null if the manifest - * is missing. - */ - public static IFile getManifest(IProject project) { - IResource r = project.findMember(AndroidConstants.WS_SEP - + AndroidConstants.FN_ANDROID_MANIFEST); - - if (r == null || r.exists() == false || (r instanceof IFile) == false) { - return null; - } - return (IFile) r; - } - - /** - * Combines a java package, with a class value from the manifest to make a fully qualified - * class name - * @param javaPackage the java package from the manifest. - * @param className the class name from the manifest. - * @return the fully qualified class name. - */ - public static String combinePackageAndClassName(String javaPackage, String className) { - if (className == null || className.length() == 0) { - return javaPackage; - } - if (javaPackage == null || javaPackage.length() == 0) { - return className; - } - - // the class name can be a subpackage (starts with a '.' - // char), a simple class name (no dot), or a full java package - boolean startWithDot = (className.charAt(0) == '.'); - boolean hasDot = (className.indexOf('.') != -1); - if (startWithDot || hasDot == false) { - - // add the concatenation of the package and class name - if (startWithDot) { - return javaPackage + className; - } else { - return javaPackage + '.' + className; - } - } else { - // just add the class as it should be a fully qualified java name. - return className; - } - } - - - - /** - * Returns true either if an androidManifest.xml file was found in the project - * or if the given file path exists. - */ - public boolean exists() { - if (mManifestIFile != null) { - return mManifestIFile.exists(); - } else if (mManifestFile != null) { - return mManifestFile.exists(); - } - - return false; - } - - /** - * Returns an InputSource for XPath. - * - * @throws FileNotFoundException if file does not exist. - * @throws CoreException if the {@link IFile} does not exist. - */ - private InputSource getSource() throws FileNotFoundException, CoreException { - if (mManifestIFile != null) { - return new InputSource(mManifestIFile.getContents()); - } else if (mManifestFile != null) { - return new InputSource(new FileReader(mManifestFile)); - } - - return null; - } - - /** - * Performs the actual XPath evaluation to get the package name. - * Extracted so that we can share it with AndroidManifestFromProject. - */ - private static String getPackageNameInternal(XPath xpath, InputSource source) - throws XPathExpressionException { - return xpath.evaluate("/manifest/@package", source); //$NON-NLS-1$ - } - - /** - * Performs the actual XPath evaluation to get the activity name. - * Extracted so that we can share it with AndroidManifestFromProject. - */ - private static String getActivityNameInternal(int index, XPath xpath, InputSource source) - throws XPathExpressionException { - return xpath.evaluate("/manifest/application/activity[" //$NON-NLS-1$ - + index - + "]/@" //$NON-NLS-1$ - + AndroidXPathFactory.DEFAULT_NS_PREFIX +":name", //$NON-NLS-1$ - source); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java deleted file mode 100644 index 42f2a8b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidManifestParser.java +++ /dev/null @@ -1,632 +0,0 @@ -/* - * Copyright (C) 2007 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IJavaProject; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.Locator; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Set; -import java.util.TreeSet; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -public class AndroidManifestParser { - - private final static String ATTRIBUTE_PACKAGE = "package"; //$NON-NLS-1$ - private final static String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ - private final static String ATTRIBUTE_PROCESS = "process"; //$NON-NLS-$ - private final static String ATTRIBUTE_DEBUGGABLE = "debuggable"; //$NON-NLS-$ - private final static String NODE_MANIFEST = "manifest"; //$NON-NLS-1$ - private final static String NODE_APPLICATION = "application"; //$NON-NLS-1$ - private final static String NODE_ACTIVITY = "activity"; //$NON-NLS-1$ - private final static String NODE_SERVICE = "service"; //$NON-NLS-1$ - private final static String NODE_RECEIVER = "receiver"; //$NON-NLS-1$ - private final static String NODE_PROVIDER = "provider"; //$NON-NLS-1$ - private final static String NODE_INTENT = "intent-filter"; //$NON-NLS-1$ - private final static String NODE_ACTION = "action"; //$NON-NLS-1$ - private final static String NODE_CATEGORY = "category"; //$NON-NLS-1$ - - private final static int LEVEL_MANIFEST = 0; - private final static int LEVEL_APPLICATION = 1; - private final static int LEVEL_ACTIVITY = 2; - private final static int LEVEL_INTENT_FILTER = 3; - private final static int LEVEL_CATEGORY = 4; - - private final static String ACTION_MAIN = "android.intent.action.MAIN"; //$NON-NLS-1$ - private final static String CATEGORY_LAUNCHER = "android.intent.category.LAUNCHER"; //$NON-NLS-1$ - - private static class ManifestHandler extends XmlErrorHandler { - - //--- data read from the parsing - - /** Application package */ - private String mPackage; - /** List of all activities */ - private final ArrayList mActivities = new ArrayList(); - /** Launcher activity */ - private String mLauncherActivity = null; - /** list of process names declared by the manifest */ - private Set mProcesses = null; - /** debuggable attribute value. If null, the attribute is not present. */ - private Boolean mDebuggable = null; - - //--- temporary data/flags used during parsing - private IJavaProject mJavaProject; - private boolean mGatherData = false; - private boolean mMarkErrors = false; - private int mCurrentLevel = 0; - private int mValidLevel = 0; - private boolean mFoundMainAction = false; - private boolean mFoundLauncherCategory = false; - private String mCurrentActivity = null; - private Locator mLocator; - - /** - * - * @param manifestFile - * @param errorListener - * @param gatherData - * @param javaProject - * @param markErrors - */ - ManifestHandler(IFile manifestFile, XmlErrorListener errorListener, - boolean gatherData, IJavaProject javaProject, boolean markErrors) { - super(manifestFile, errorListener); - mGatherData = gatherData; - mJavaProject = javaProject; - mMarkErrors = markErrors; - } - - /** - * Returns the package defined in the manifest, if found. - * @return The package name or null if not found. - */ - String getPackage() { - return mPackage; - } - - /** - * Returns the list of activities found in the manifest. - * @return An array of fully qualified class names, or empty if no activity were found. - */ - String[] getActivities() { - return mActivities.toArray(new String[mActivities.size()]); - } - - /** - * Returns the name of one activity found in the manifest, that is configured to show - * up in the HOME screen. - * @return the fully qualified name of a HOME activity or null if none were found. - */ - String getLauncherActivity() { - return mLauncherActivity; - } - - /** - * Returns the list of process names declared by the manifest. - */ - String[] getProcesses() { - if (mProcesses != null) { - return mProcesses.toArray(new String[mProcesses.size()]); - } - - return new String[0]; - } - - /** - * Returns the debuggable attribute value or null if it is not set. - */ - Boolean getDebuggable() { - return mDebuggable; - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator) - */ - @Override - public void setDocumentLocator(Locator locator) { - mLocator = locator; - super.setDocumentLocator(locator); - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, - * java.lang.String, org.xml.sax.Attributes) - */ - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) - throws SAXException { - try { - if (mGatherData == false) { - return; - } - - // if we're at a valid level - if (mValidLevel == mCurrentLevel) { - String processName; - switch (mValidLevel) { - case LEVEL_MANIFEST: - if (NODE_MANIFEST.equals(localName)) { - // lets get the package name. - mPackage = getAttributeValue(attributes, ATTRIBUTE_PACKAGE, - false /* hasNamespace */); - mValidLevel++; - } - break; - case LEVEL_APPLICATION: - if (NODE_APPLICATION.equals(localName)) { - processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, - true /* hasNamespace */); - if (processName != null) { - addProcessName(processName); - } - - String debuggable = getAttributeValue(attributes, - ATTRIBUTE_DEBUGGABLE, true /* hasNamespace*/); - if (debuggable != null) { - mDebuggable = Boolean.parseBoolean(debuggable); - } - - mValidLevel++; - } - break; - case LEVEL_ACTIVITY: - if (NODE_ACTIVITY.equals(localName)) { - processActivityNode(attributes); - mValidLevel++; - } else if (NODE_SERVICE.equals(localName)) { - processNode(attributes, AndroidConstants.CLASS_SERVICE); - mValidLevel++; - } else if (NODE_RECEIVER.equals(localName)) { - processNode(attributes, AndroidConstants.CLASS_BROADCASTRECEIVER); - mValidLevel++; - } else if (NODE_PROVIDER.equals(localName)) { - processNode(attributes, AndroidConstants.CLASS_CONTENTPROVIDER); - mValidLevel++; - } - break; - case LEVEL_INTENT_FILTER: - // only process this level if we are in an activity - if (mCurrentActivity != null && NODE_INTENT.equals(localName)) { - // if we're at the intent level, lets reset some flag to - // be used when parsing the children - mFoundMainAction = false; - mFoundLauncherCategory = false; - mValidLevel++; - } - break; - case LEVEL_CATEGORY: - if (mCurrentActivity != null && mLauncherActivity == null) { - if (NODE_ACTION.equals(localName)) { - // get the name attribute - if (ACTION_MAIN.equals( - getAttributeValue(attributes, ATTRIBUTE_NAME, - true /* hasNamespace */))) { - mFoundMainAction = true; - } - } else if (NODE_CATEGORY.equals(localName)) { - if (CATEGORY_LAUNCHER.equals( - getAttributeValue(attributes, ATTRIBUTE_NAME, - true /* hasNamespace */))) { - mFoundLauncherCategory = true; - } - } - - // no need to increase mValidLevel as we don't process anything - // below this level. - } - break; - } - } - - mCurrentLevel++; - } finally { - super.startElement(uri, localName, name, attributes); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, - * java.lang.String) - */ - @Override - public void endElement(String uri, String localName, String name) throws SAXException { - try { - if (mGatherData == false) { - return; - } - - // decrement the levels. - if (mValidLevel == mCurrentLevel) { - mValidLevel--; - } - mCurrentLevel--; - - // if we're at a valid level - // process the end of the element - if (mValidLevel == mCurrentLevel) { - switch (mValidLevel) { - case LEVEL_ACTIVITY: - mCurrentActivity = null; - break; - case LEVEL_INTENT_FILTER: - // if we found both a main action and a launcher category, this is our - // launcher activity! - if (mCurrentActivity != null && - mFoundMainAction && mFoundLauncherCategory) { - mLauncherActivity = mCurrentActivity; - } - break; - default: - break; - } - - } - } finally { - super.endElement(uri, localName, name); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#error(org.xml.sax.SAXParseException) - */ - @Override - public void error(SAXParseException e) throws SAXException { - if (mMarkErrors) { - super.error(e); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#fatalError(org.xml.sax.SAXParseException) - */ - @Override - public void fatalError(SAXParseException e) throws SAXException { - if (mMarkErrors) { - super.fatalError(e); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#warning(org.xml.sax.SAXParseException) - */ - @Override - public void warning(SAXParseException e) throws SAXException { - if (mMarkErrors) { - super.warning(e); - } - } - - /** - * Processes the activity node. - * @param attributes the attributes for the activity node. - * @throws CoreException - */ - private void processActivityNode(Attributes attributes) { - // lets get the activity name, and add it to the list - String activityName = getAttributeValue(attributes, ATTRIBUTE_NAME, - true /* hasNamespace */); - if (activityName != null) { - mCurrentActivity = AndroidManifestHelper.combinePackageAndClassName(mPackage, - activityName); - mActivities.add(mCurrentActivity); - - if (mMarkErrors) { - checkClass(mCurrentActivity, AndroidConstants.CLASS_ACTIVITY, - true /* testVisibility */); - } - } else { - // no activity found! Aapt will output an error, - // so we don't have to do anything - mCurrentActivity = activityName; - } - - String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, - true /* hasNamespace */); - if (processName != null) { - addProcessName(processName); - } - } - - /** - * Processes the service/receiver/provider nodes. - * @param attributes the attributes for the activity node. - * @param superClassName the fully qualified name of the super class that this - * node is representing - * @throws CoreException - */ - private void processNode(Attributes attributes, String superClassName) { - // lets get the class name, and check it if required. - String serviceName = getAttributeValue(attributes, ATTRIBUTE_NAME, - true /* hasNamespace */); - if (serviceName != null) { - serviceName = AndroidManifestHelper.combinePackageAndClassName(mPackage, - serviceName); - - if (mMarkErrors) { - checkClass(serviceName, superClassName, false /* testVisibility */); - } - } - - String processName = getAttributeValue(attributes, ATTRIBUTE_PROCESS, - true /* hasNamespace */); - if (processName != null) { - addProcessName(processName); - } - } - - /** - * Checks that a class is valid and can be used in the Android Manifest. - *

              - * Errors are put as {@link IMarker} on the manifest file. - * @param className the fully qualified name of the class to test. - * @param superClassName the fully qualified name of the class it is supposed to extend. - * @param testVisibility if true, the method will check the visibility of - * the class or of its constructors. - */ - private void checkClass(String className, String superClassName, boolean testVisibility) { - // we need to check the validity of the activity. - String result = BaseProjectHelper.testClassForManifest(mJavaProject, - className, superClassName, testVisibility); - if (result != BaseProjectHelper.TEST_CLASS_OK) { - // get the line number - int line = mLocator.getLineNumber(); - - // mark the file - IMarker marker = BaseProjectHelper.addMarker(getFile(), - AndroidConstants.MARKER_ANDROID, - result, line, IMarker.SEVERITY_ERROR); - - // add custom attributes to be used by the manifest editor. - if (marker != null) { - try { - marker.setAttribute(AndroidConstants.MARKER_ATTR_TYPE, - AndroidConstants.MARKER_ATTR_TYPE_ACTIVITY); - marker.setAttribute(AndroidConstants.MARKER_ATTR_CLASS, className); - } catch (CoreException e) { - } - } - } - - } - - /** - * Searches through the attributes list for a particular one and returns its value. - * @param attributes the attribute list to search through - * @param attributeName the name of the attribute to look for. - * @param hasNamespace Indicates whether the attribute has an android namespace. - * @return a String with the value or null if the attribute was not found. - * @see AndroidConstants#NS_RESOURCES - */ - private String getAttributeValue(Attributes attributes, String attributeName, - boolean hasNamespace) { - int count = attributes.getLength(); - for (int i = 0 ; i < count ; i++) { - if (attributeName.equals(attributes.getLocalName(i)) && - ((hasNamespace && - AndroidConstants.NS_RESOURCES.equals(attributes.getURI(i))) || - (hasNamespace == false && attributes.getURI(i).length() == 0))) { - return attributes.getValue(i); - } - } - - return null; - } - - private void addProcessName(String processName) { - if (mProcesses == null) { - mProcesses = new TreeSet(); - } - - mProcesses.add(processName); - } - } - - private static SAXParserFactory sParserFactory; - - private final String mJavaPackage; - private final String[] mActivities; - private final String mLauncherActivity; - private final String[] mProcesses; - private final Boolean mDebuggable; - - static { - sParserFactory = SAXParserFactory.newInstance(); - sParserFactory.setNamespaceAware(true); - } - - /** - * Parses the Android Manifest, and returns an object containing - * the result of the parsing. - * @param javaProject The java project. - * @param manifestFile the {@link IFile} representing the manifest file. - * @param errorListener - * @param gatherData indicates whether the parsing will extract data from the manifest. - * @param markErrors indicates whether the error found during parsing should put a - * marker on the file. For class validation errors to put a marker, gatherData - * must be set to true - * @return an {@link AndroidManifestParser} or null if the parsing failed. - * @throws CoreException - */ - public static AndroidManifestParser parse(IJavaProject javaProject, IFile manifestFile, - XmlErrorListener errorListener, boolean gatherData, boolean markErrors) - throws CoreException { - try { - SAXParser parser = sParserFactory.newSAXParser(); - - ManifestHandler manifestHandler = new ManifestHandler(manifestFile, - errorListener, gatherData, javaProject, markErrors); - - parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); - - // get the result from the handler - - return new AndroidManifestParser(manifestHandler.getPackage(), - manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), - manifestHandler.getProcesses(), manifestHandler.getDebuggable()); - } catch (ParserConfigurationException e) { - } catch (SAXException e) { - } catch (IOException e) { - } finally { - } - - return null; - } - - /** - * Parses the Android Manifest for the specified project, and returns an object containing - * the result of the parsing. - * @param javaProject The java project. Required if markErrors is true - * @param errorListener the {@link XmlErrorListener} object being notified of the presence - * of errors. Optional. - * @param gatherData indicates whether the parsing will extract data from the manifest. - * @param markErrors indicates whether the error found during parsing should put a - * marker on the file. For class validation errors to put a marker, gatherData - * must be set to true - * @return an {@link AndroidManifestParser} or null if the parsing failed. - * @throws CoreException - */ - public static AndroidManifestParser parse(IJavaProject javaProject, - XmlErrorListener errorListener, boolean gatherData, boolean markErrors) - throws CoreException { - try { - SAXParser parser = sParserFactory.newSAXParser(); - - IFile manifestFile = AndroidManifestHelper.getManifest(javaProject.getProject()); - if (manifestFile != null) { - ManifestHandler manifestHandler = new ManifestHandler(manifestFile, - errorListener, gatherData, javaProject, markErrors); - - parser.parse(new InputSource(manifestFile.getContents()), manifestHandler); - - // get the result from the handler - return new AndroidManifestParser(manifestHandler.getPackage(), - manifestHandler.getActivities(), manifestHandler.getLauncherActivity(), - manifestHandler.getProcesses(), manifestHandler.getDebuggable()); - } - } catch (ParserConfigurationException e) { - } catch (SAXException e) { - } catch (IOException e) { - } finally { - } - - return null; - } - - /** - * Parses the manifest file, collects data, and checks for errors. - * @param javaProject The java project. Required. - * @param manifestFile - * @param errorListener the {@link XmlErrorListener} object being notified of the presence - * of errors. Optional. - * @return an {@link AndroidManifestParser} or null if the parsing failed. - * @throws CoreException - * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} - */ - public static AndroidManifestParser parseForError(IJavaProject javaProject, IFile manifestFile, - XmlErrorListener errorListener) throws CoreException { - return parse(javaProject, manifestFile, errorListener, true, true); - } - - /** - * Parses the manifest file, and collects data. - * @param manifestFile - * @param errorListener the {@link XmlErrorListener} object being notified of the presence - * of errors. Optional. - * @return an {@link AndroidManifestParser} or null if the parsing failed. - * @throws CoreException - * @see {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)} - */ - public static AndroidManifestParser parseForData(IFile manifestFile) throws CoreException { - return parse(null /* javaProject */, manifestFile, null /* errorListener */, - true /* gatherData */, false /* markErrors */); - } - - /** - * Returns the package defined in the manifest, if found. - * @return The package name or null if not found. - */ - public String getPackage() { - return mJavaPackage; - } - - /** - * Returns the list of activities found in the manifest. - * @return An array of fully qualified class names, or empty if no activity were found. - */ - public String[] getActivities() { - return mActivities; - } - - /** - * Returns the name of one activity found in the manifest, that is configured to show - * up in the HOME screen. - * @return the fully qualified name of a HOME activity or null if none were found. - */ - public String getLauncherActivity() { - return mLauncherActivity; - } - - /** - * Returns the list of process names declared by the manifest. - */ - public String[] getProcesses() { - return mProcesses; - } - - /** - * Returns the debuggable attribute value or null if it is not set. - */ - public Boolean getDebuggable() { - return mDebuggable; - } - - /** - * Private constructor to enforce using - * {@link #parse(IJavaProject, XmlErrorListener, boolean, boolean)}, - * {@link #parse(IJavaProject, IFile, XmlErrorListener, boolean, boolean)}, - * or {@link #parseForError(IJavaProject, IFile, XmlErrorListener)} to get an - * {@link AndroidManifestParser} object. - * @param javaPackage the package parsed from the manifest. - * @param activities the list of activities parsed from the manifest. - * @param launcherActivity the launcher activity parser from the manifest. - * @param processes the list of custom processes declared in the manifest. - * @param debuggable the debuggable attribute. - */ - private AndroidManifestParser(String javaPackage, String[] activities, - String launcherActivity, String[] processes, Boolean debuggable) { - mJavaPackage = javaPackage; - mActivities = activities; - mLauncherActivity = launcherActivity; - mProcesses = processes; - mDebuggable = debuggable; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java deleted file mode 100644 index 530c89e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/AndroidXPathFactory.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2007 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; - -import java.util.Iterator; - -import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathFactory; - -/** - * XPath factory with automatic support for the android namespace. - */ -public class AndroidXPathFactory { - public final static String DEFAULT_NS_PREFIX = "android"; //$NON-NLS-1$ - - private final static XPathFactory sFactory = XPathFactory.newInstance(); - - /** Namespace context for Android resource XML files. */ - private static class AndroidNamespaceContext implements NamespaceContext { - private String mAndroidPrefix; - - /** - * Construct the context with the prefix associated with the android namespace. - * @param prefix the Prefix - */ - public AndroidNamespaceContext(String androidPrefix) { - mAndroidPrefix = androidPrefix; - } - - public String getNamespaceURI(String prefix) { - if (prefix != null) { - if (prefix.equals(mAndroidPrefix)) { - return AndroidConstants.NS_RESOURCES; - } - } - - return XMLConstants.NULL_NS_URI; - } - - public String getPrefix(String namespaceURI) { - // This isn't necessary for our use. - assert false; - return null; - } - - public Iterator getPrefixes(String namespaceURI) { - // This isn't necessary for our use. - assert false; - return null; - } - } - - /** - * Creates a new XPath object, specifying which prefix in the query is used for the - * android namespace. - * @param prefix The namespace prefix. - */ - public static XPath newXPath(String androidPrefix) { - XPath xpath = sFactory.newXPath(); - xpath.setNamespaceContext(new AndroidNamespaceContext(androidPrefix)); - return xpath; - } - - /** - * Creates a new XPath object using the default prefix for the android namespace. - * @see #DEFAULT_NS_PREFIX - */ - public static XPath newXPath() { - return newXPath(DEFAULT_NS_PREFIX); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java deleted file mode 100644 index 57ca496..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/BaseProjectHelper.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (C) 2007 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.CommonPlugin; -import com.android.ide.eclipse.common.project.XmlErrorHandler.XmlErrorListener; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jdt.core.Flags; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaModel; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IMethod; -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.ui.JavaUI; -import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IRegion; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.texteditor.IDocumentProvider; -import org.eclipse.ui.texteditor.ITextEditor; - -import java.util.ArrayList; - -/** - * Utility methods to manipulate projects. - */ -public final class BaseProjectHelper { - - public static final String TEST_CLASS_OK = null; - - /** - * returns a list of source classpath for a specified project - * @param javaProject - * @return a list of path relative to the workspace root. - */ - public static ArrayList getSourceClasspaths(IJavaProject javaProject) { - ArrayList sourceList = new ArrayList(); - IClasspathEntry[] classpaths = javaProject.readRawClasspath(); - if (classpaths != null) { - for (IClasspathEntry e : classpaths) { - if (e.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - sourceList.add(e.getPath()); - } - } - } - return sourceList; - } - - /** - * Adds a marker to a file on a specific line - * @param file the file to be marked - * @param markerId The id of the marker to add. - * @param message the message associated with the mark - * @param lineNumber the line number where to put the mark - * @param severity the severity of the marker. - * @return the IMarker that was added or null if it failed to add one. - */ - public final static IMarker addMarker(IResource file, String markerId, - String message, int lineNumber, int severity) { - try { - IMarker marker = file.createMarker(markerId); - marker.setAttribute(IMarker.MESSAGE, message); - marker.setAttribute(IMarker.SEVERITY, severity); - if (lineNumber == -1) { - lineNumber = 1; - } - marker.setAttribute(IMarker.LINE_NUMBER, lineNumber); - - // on Windows, when adding a marker to a project, it takes a refresh for the marker - // to show. In order to fix this we're forcing a refresh of elements receiving - // markers (and only the element, not its children), to force the marker display. - file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); - - return marker; - } catch (CoreException e) { - CommonPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", - markerId, file.getFullPath()); - } - - return null; - } - - /** - * Adds a marker to a resource. - * @param file the file to be marked - * @param markerId The id of the marker to add. - * @param message the message associated with the mark - * @param lineNumber the line number where to put the mark - * @param severity the severity of the marker. - * @return the IMarker that was added or null if it failed to add one. - */ - public final static IMarker addMarker(IResource resource, String markerId, - String message, int severity) { - try { - IMarker marker = resource.createMarker(markerId); - marker.setAttribute(IMarker.MESSAGE, message); - marker.setAttribute(IMarker.SEVERITY, severity); - - // on Windows, when adding a marker to a project, it takes a refresh for the marker - // to show. In order to fix this we're forcing a refresh of elements receiving - // markers (and only the element, not its children), to force the marker display. - resource.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor()); - - return marker; - } catch (CoreException e) { - CommonPlugin.log(e, "Failed to add marker '%1$s' to '%2$s'", - markerId, resource.getFullPath()); - } - - return null; - } - - /** - * Tests that a class name is valid for usage in the manifest. - *

              - * This tests the class existence, that it can be instantiated (ie it must not be abstract, - * nor non static if enclosed), and that it extends the proper super class (not necessarily - * directly) - * @param javaProject the {@link IJavaProject} containing the class. - * @param className the fully qualified name of the class to test. - * @param superClassName the fully qualified name of the expected super class. - * @param testVisibility if true, the method will check the visibility of the class - * or of its constructors. - * @return {@link #TEST_CLASS_OK} or an error message. - */ - public final static String testClassForManifest(IJavaProject javaProject, String className, - String superClassName, boolean testVisibility) { - try { - // replace $ by . - String javaClassName = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$ - - // look for the IType object for this class - IType type = javaProject.findType(javaClassName); - if (type != null && type.exists()) { - // test that the class is not abstract - int flags = type.getFlags(); - if (Flags.isAbstract(flags)) { - return String.format("%1$s is abstract", className); - } - - // test whether the class is public or not. - if (testVisibility && Flags.isPublic(flags) == false) { - // if its not public, it may have a public default constructor, - // which would then be fine. - IMethod basicConstructor = type.getMethod(type.getElementName(), new String[0]); - if (basicConstructor != null && basicConstructor.exists()) { - int constructFlags = basicConstructor.getFlags(); - if (Flags.isPublic(constructFlags) == false) { - return String.format( - "%1$s or its default constructor must be public for the system to be able to instantiate it", - className); - } - } else { - return String.format( - "%1$s must be public, or the system will not be able to instantiate it.", - className); - } - } - - // If it's enclosed, test that it's static. If its declaring class is enclosed - // as well, test that it is also static, and public. - IType declaringType = type; - do { - IType tmpType = declaringType.getDeclaringType(); - if (tmpType != null) { - if (tmpType.exists()) { - flags = declaringType.getFlags(); - if (Flags.isStatic(flags) == false) { - return String.format("%1$s is enclosed, but not static", - declaringType.getFullyQualifiedName()); - } - - flags = tmpType.getFlags(); - if (testVisibility && Flags.isPublic(flags) == false) { - return String.format("%1$s is not public", - tmpType.getFullyQualifiedName()); - } - } else { - // if it doesn't exist, we need to exit so we may as well mark it null. - tmpType = null; - } - } - declaringType = tmpType; - } while (declaringType != null); - - // test the class inherit from the specified super class. - // get the type hierarchy - ITypeHierarchy hierarchy = type.newSupertypeHierarchy(new NullProgressMonitor()); - - // if the super class is not the reference class, it may inherit from - // it so we get its supertype. At some point it will be null and we - // will stop - IType superType = type; - boolean foundProperSuperClass = false; - while ((superType = hierarchy.getSuperclass(superType)) != null && - superType.exists()) { - if (superClassName.equals(superType.getFullyQualifiedName())) { - foundProperSuperClass = true; - } - } - - // didn't find the proper superclass? return false. - if (foundProperSuperClass == false) { - return String.format("%1$s does not extend %2$s", className, superClassName); - } - - return TEST_CLASS_OK; - } else { - return String.format("Class %1$s does not exist", className); - } - } catch (JavaModelException e) { - return String.format("%1$s: %2$s", className, e.getMessage()); - } - } - - /** - * Parses the manifest file for errors. - *

              - * This starts by removing the current XML marker, and then parses the xml for errors, both - * of XML type and of Android type (checking validity of class files). - * @param manifestFile - * @param errorListener - * @throws CoreException - */ - public static AndroidManifestParser parseManifestForError(IFile manifestFile, - XmlErrorListener errorListener) throws CoreException { - // remove previous markers - if (manifestFile.exists()) { - manifestFile.deleteMarkers(AndroidConstants.MARKER_XML, true, IResource.DEPTH_ZERO); - manifestFile.deleteMarkers(AndroidConstants.MARKER_ANDROID, true, IResource.DEPTH_ZERO); - } - - // and parse - return AndroidManifestParser.parseForError( - BaseProjectHelper.getJavaProject(manifestFile.getProject()), - manifestFile, errorListener); - } - - /** - * Returns the {@link IJavaProject} for a {@link IProject} object. - *

              - * This checks if the project has the Java Nature first. - * @param project - * @return the IJavaProject or null if the project couldn't be created or if the project - * does not have the Java Nature. - * @throws CoreException - */ - public static IJavaProject getJavaProject(IProject project) throws CoreException { - if (project != null && project.hasNature(JavaCore.NATURE_ID)) { - return JavaCore.create(project); - } - return null; - } - - /** - * Reveals a specific line in the source file defining a specified class, - * for a specific project. - * @param project - * @param className - * @param line - */ - public static void revealSource(IProject project, String className, int line) { - // in case the type is enclosed, we need to replace the $ with . - className = className.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$ - - // get the java project - IJavaProject javaProject = JavaCore.create(project); - - try { - // look for the IType matching the class name. - IType result = javaProject.findType(className); - if (result != null && result.exists()) { - // before we show the type in an editor window, we make sure the current - // workbench page has an editor area (typically the ddms perspective doesn't). - IWorkbench workbench = PlatformUI.getWorkbench(); - IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); - IWorkbenchPage page = window.getActivePage(); - if (page.isEditorAreaVisible() == false) { - // no editor area? we open the java perspective. - new OpenJavaPerspectiveAction().run(); - } - - IEditorPart editor = JavaUI.openInEditor(result); - if (editor instanceof ITextEditor) { - // get the text editor that was just opened. - ITextEditor textEditor = (ITextEditor)editor; - - IEditorInput input = textEditor.getEditorInput(); - - // get the location of the line to show. - IDocumentProvider documentProvider = textEditor.getDocumentProvider(); - IDocument document = documentProvider.getDocument(input); - IRegion lineInfo = document.getLineInformation(line - 1); - - // select and reveal the line. - textEditor.selectAndReveal(lineInfo.getOffset(), lineInfo.getLength()); - } - } - } catch (JavaModelException e) { - } catch (PartInitException e) { - } catch (BadLocationException e) { - } - } - - /** - * Returns the list of android-flagged projects. This list contains projects that are opened - * in the workspace and that are flagged as android project (through the android nature) - * @return an array of IJavaProject, which can be empty if no projects match. - */ - public static IJavaProject[] getAndroidProjects() { - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - IJavaModel javaModel = JavaCore.create(workspaceRoot); - - return getAndroidProjects(javaModel); - } - - /** - * Returns the list of android-flagged projects for the specified java Model. - * This list contains projects that are opened in the workspace and that are flagged as android - * project (through the android nature) - * @param javaModel the Java Model object corresponding for the current workspace root. - * @return an array of IJavaProject, which can be empty if no projects match. - */ - public static IJavaProject[] getAndroidProjects(IJavaModel javaModel) { - // get the java projects - IJavaProject[] javaProjectList = null; - try { - javaProjectList = javaModel.getJavaProjects(); - } - catch (JavaModelException jme) { - return new IJavaProject[0]; - } - - // temp list to build the android project array - ArrayList androidProjectList = new ArrayList(); - - // loop through the projects and add the android flagged projects to the temp list. - for (IJavaProject javaProject : javaProjectList) { - // get the workspace project object - IProject project = javaProject.getProject(); - - // check if it's an android project based on its nature - try { - if (project.hasNature(AndroidConstants.NATURE)) { - androidProjectList.add(javaProject); - } - } catch (CoreException e) { - // this exception, thrown by IProject.hasNature(), means the project either doesn't - // exist or isn't opened. So, in any case we just skip it (the exception will - // bypass the ArrayList.add() - } - } - - // return the android projects list. - return androidProjectList.toArray(new IJavaProject[androidProjectList.size()]); - } - - /** - * Returns the {@link IFolder} representing the output for the project. - *

              - * The project must be a java project and be opened, or the method will return null. - * @param project the {@link IProject} - * @return an IFolder item or null. - */ - public final static IFolder getOutputFolder(IProject project) { - try { - if (project.isOpen() && project.hasNature(JavaCore.NATURE_ID)) { - // get a java project from the normal project object - IJavaProject javaProject = JavaCore.create(project); - - IPath path = javaProject.getOutputLocation(); - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - IResource outputResource = wsRoot.findMember(path); - if (outputResource != null && outputResource.getType() == IResource.FOLDER) { - return (IFolder)outputResource; - } - } - } catch (JavaModelException e) { - // Let's do nothing and return null - } catch (CoreException e) { - // Let's do nothing and return null - } - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ExportHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ExportHelper.java deleted file mode 100644 index 4b169a1..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ExportHelper.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Shell; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.jar.JarEntry; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -/** - * Export helper for project. - */ -public final class ExportHelper { - - private static IExportCallback sCallback; - - public interface IExportCallback { - void startExportWizard(IProject project); - } - - public static void setCallback(IExportCallback callback) { - sCallback = callback; - } - - public static void startExportWizard(IProject project) { - if (sCallback != null) { - sCallback.startExportWizard(project); - } - } - - /** - * Exports an unsigned version of the application created by the given project. - * @param project the project to export - */ - public static void exportProject(IProject project) { - Shell shell = Display.getCurrent().getActiveShell(); - - // get the java project to get the output directory - IFolder outputFolder = BaseProjectHelper.getOutputFolder(project); - if (outputFolder != null) { - IPath binLocation = outputFolder.getLocation(); - - // make the full path to the package - String fileName = project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE; - - File file = new File(binLocation.toOSString() + File.separator + fileName); - - if (file.exists() == false || file.isFile() == false) { - MessageDialog.openInformation(Display.getCurrent().getActiveShell(), - "Android IDE Plug-in", - String.format("Failed to export %1$s: %2$s doesn't exist!", - project.getName(), file.getPath())); - return; - } - - // ok now pop up the file save window - FileDialog fileDialog = new FileDialog(shell, SWT.SAVE); - - fileDialog.setText("Export Project"); - fileDialog.setFileName(fileName); - - String saveLocation = fileDialog.open(); - if (saveLocation != null) { - // get the stream from the original file - - ZipInputStream zis = null; - ZipOutputStream zos = null; - FileInputStream input = null; - FileOutputStream output = null; - - try { - input = new FileInputStream(file); - zis = new ZipInputStream(input); - - // get an output stream into the new file - File saveFile = new File(saveLocation); - output = new FileOutputStream(saveFile); - zos = new ZipOutputStream(output); - } catch (FileNotFoundException e) { - // only the input/output stream are throwing this exception. - // so we only have to close zis if output is the one that threw. - if (zis != null) { - try { - zis.close(); - } catch (IOException e1) { - // pass - } - } - - MessageDialog.openInformation(shell, "Android IDE Plug-in", - String.format("Failed to export %1$s: %2$s doesn't exist!", - project.getName(), file.getPath())); - return; - } - - try { - ZipEntry entry; - - byte[] buffer = new byte[4096]; - - while ((entry = zis.getNextEntry()) != null) { - String name = entry.getName(); - - // do not take directories or anything inside the META-INF folder since - // we want to strip the signature. - if (entry.isDirectory() || name.startsWith("META-INF/")) { //$NON-NL1$ - continue; - } - - ZipEntry newEntry; - - // Preserve the STORED method of the input entry. - if (entry.getMethod() == JarEntry.STORED) { - newEntry = new JarEntry(entry); - } else { - // Create a new entry so that the compressed len is recomputed. - newEntry = new JarEntry(name); - } - - // add the entry to the jar archive - zos.putNextEntry(newEntry); - - // read the content of the entry from the input stream, and write it into the archive. - int count; - while ((count = zis.read(buffer)) != -1) { - zos.write(buffer, 0, count); - } - - // close the entry for this file - zos.closeEntry(); - zis.closeEntry(); - - } - - } catch (IOException e) { - MessageDialog.openInformation(shell, "Android IDE Plug-in", - String.format("Failed to export %1$s: %2$s", - project.getName(), e.getMessage())); - } finally { - try { - zos.close(); - } catch (IOException e) { - // pass - } - try { - zis.close(); - } catch (IOException e) { - // pass - } - } - } - } else { - MessageDialog.openInformation(shell, "Android IDE Plug-in", - String.format("Failed to export %1$s: Could not get project output location", - project.getName())); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java deleted file mode 100644 index 0c43499..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/ProjectChooserHelper.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.common.project; - -import com.android.ide.eclipse.common.project.BaseProjectHelper; - -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.jdt.core.IJavaModel; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.ui.JavaElementLabelProvider; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.dialogs.ElementListSelectionDialog; - -/** - * Helper class to deal with displaying a project choosing dialog that lists only the - * projects with the Android nature. - */ -public class ProjectChooserHelper { - - private final Shell mParentShell; - - /** - * List of current android projects. Since the dialog is modal, we'll just get - * the list once on-demand. - */ - private IJavaProject[] mAndroidProjects; - - public ProjectChooserHelper(Shell parentShell) { - mParentShell = parentShell; - } - /** - * Displays a project chooser dialog which lists all available projects with the Android nature. - *

              - * The list of project is built from Android flagged projects currently opened in the workspace. - * - * @param projectName If non null and not empty, represents the name of an Android project - * that will be selected by default. - * @return the project chosen by the user in the dialog, or null if the dialog was canceled. - */ - public IJavaProject chooseJavaProject(String projectName) { - ILabelProvider labelProvider = new JavaElementLabelProvider( - JavaElementLabelProvider.SHOW_DEFAULT); - ElementListSelectionDialog dialog = new ElementListSelectionDialog( - mParentShell, labelProvider); - dialog.setTitle("Project Selection"); - dialog.setMessage("Select a project to constrain your search."); - - IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); - IJavaModel javaModel = JavaCore.create(workspaceRoot); - - // set the elements in the dialog. These are opened android projects. - dialog.setElements(getAndroidProjects(javaModel)); - - // look for the project matching the given project name - IJavaProject javaProject = null; - if (projectName != null && projectName.length() > 0) { - javaProject = javaModel.getJavaProject(projectName); - } - - // if we found it, we set the initial selection in the dialog to this one. - if (javaProject != null) { - dialog.setInitialSelections(new Object[] { javaProject }); - } - - // open the dialog and return the object selected if OK was clicked, or null otherwise - if (dialog.open() == Window.OK) { - return (IJavaProject)dialog.getFirstResult(); - } - return null; - } - - /** - * Returns the list of Android projects. - *

              - * Because this list can be time consuming, this class caches the list of project. - * It is recommended to call this method instead of - * {@link BaseProjectHelper#getAndroidProjects()}. - * - * @param javaModel the java model. Can be null. - */ - public IJavaProject[] getAndroidProjects(IJavaModel javaModel) { - if (mAndroidProjects == null) { - if (javaModel == null) { - mAndroidProjects = BaseProjectHelper.getAndroidProjects(); - } else { - mAndroidProjects = BaseProjectHelper.getAndroidProjects(javaModel); - } - } - - return mAndroidProjects; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java deleted file mode 100644 index 3492cb4..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/project/XmlErrorHandler.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2007 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.common.project; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.xml.sax.SAXException; -import org.xml.sax.SAXParseException; -import org.xml.sax.helpers.DefaultHandler; - -/** - * XML error handler used by the parser to report errors/warnings. - */ -public class XmlErrorHandler extends DefaultHandler { - - /** file being parsed */ - private IFile mFile; - - /** link to the delta visitor, to set the xml error flag */ - private XmlErrorListener mErrorListener; - - /** - * Classes which implement this interface provide a method that deals - * with XML errors. - */ - public interface XmlErrorListener { - /** - * Sent when an XML error is detected. - */ - public void errorFound(); - } - - public static class BasicXmlErrorListener implements XmlErrorListener { - public boolean mHasXmlError = false; - - public void errorFound() { - mHasXmlError = true; - } - } - - public XmlErrorHandler(IFile file, XmlErrorListener errorListener) { - mFile = file; - mErrorListener = errorListener; - } - - /** - * Xml Error call back - * @param exception the parsing exception - */ - @Override - public void error(SAXParseException exception) throws SAXException { - if (mErrorListener != null) { - mErrorListener.errorFound(); - } - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - exception.getLineNumber(), IMarker.SEVERITY_ERROR); - } - - /** - * Xml Fatal Error call back - * @param exception the parsing exception - */ - @Override - public void fatalError(SAXParseException exception) - throws SAXException { - if (mErrorListener != null) { - mErrorListener.errorFound(); - } - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - exception.getLineNumber(), IMarker.SEVERITY_ERROR); - } - - /** - * Xml Warning call back - * @param exception the parsing exception - */ - @Override - public void warning(SAXParseException exception) throws SAXException { - BaseProjectHelper.addMarker(mFile, AndroidConstants.MARKER_XML, exception.getMessage(), - exception.getLineNumber(), IMarker.SEVERITY_WARNING); - } - - protected final IFile getFile() { - return mFile; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java deleted file mode 100644 index 43260c0..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/AttrsXmlParser.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * 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.common.resources; - -import com.android.ide.eclipse.common.CommonPlugin; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format; -import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; - -import org.eclipse.core.runtime.IStatus; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeSet; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -/** - * Parser for attributes description files. - */ -public final class AttrsXmlParser { - - private Document mDocument; - private String mOsAttrsXmlPath; - // all attributes that have the same name are supposed to have the same - // parameters so we'll keep a cache of them to avoid processing them twice. - private HashMap mAttributeMap; - - /** Map of all attribute names for a given element */ - private HashMap mStyleMap; - - /** Map of all (constant, value) pairs for attributes of format enum or flag. - * E.g. for attribute name=gravity, this tells us there's an enum/flag called "center" - * with value 0x11. - */ - private Map> mEnumFlagValues; - - - /** - * Creates a new {@link AttrsXmlParser}, set to load things from the given - * XML file. Nothing has been parsed yet. Callers should call {@link #preload()} - * next. - */ - public AttrsXmlParser(String osAttrsXmlPath) { - this(osAttrsXmlPath, null /* inheritableAttributes */); - } - - /** - * Creates a new {@link AttrsXmlParser} set to load things from the given - * XML file. If inheritableAttributes is non-null, it must point to a preloaded - * {@link AttrsXmlParser} which attributes will be used for this one. Since - * already defined attributes are not modifiable, they are thus "inherited". - */ - public AttrsXmlParser(String osAttrsXmlPath, AttrsXmlParser inheritableAttributes) { - mOsAttrsXmlPath = osAttrsXmlPath; - - // styles are not inheritable. - mStyleMap = new HashMap(); - - if (inheritableAttributes == null) { - mAttributeMap = new HashMap(); - mEnumFlagValues = new HashMap>(); - } else { - mAttributeMap = new HashMap(inheritableAttributes.mAttributeMap); - mEnumFlagValues = new HashMap>( - inheritableAttributes.mEnumFlagValues); - } - } - - /** - * @return The OS path of the attrs.xml file parsed - */ - public String getOsAttrsXmlPath() { - return mOsAttrsXmlPath; - } - - /** - * Preloads the document, parsing all attributes and declared styles. - * - * @return Self, for command chaining. - */ - public AttrsXmlParser preload() { - Document doc = getDocument(); - - if (doc == null) { - CommonPlugin.log(IStatus.WARNING, "Failed to find %1$s", //$NON-NLS-1$ - mOsAttrsXmlPath); - return this; - } - - Node res = doc.getFirstChild(); - while (res != null && - res.getNodeType() != Node.ELEMENT_NODE && - !res.getNodeName().equals("resources")) { //$NON-NLS-1$ - res = res.getNextSibling(); - } - - if (res == null) { - CommonPlugin.log(IStatus.WARNING, "Failed to find a node in %1$s", //$NON-NLS-1$ - mOsAttrsXmlPath); - return this; - } - - parseResources(res); - return this; - } - - /** - * Loads all attributes & javadoc for the view class info based on the class name. - */ - public void loadViewAttributes(ViewClassInfo info) { - if (getDocument() != null) { - String xmlName = info.getShortClassName(); - DeclareStyleableInfo style = mStyleMap.get(xmlName); - if (style != null) { - info.setAttributes(style.getAttributes()); - info.setJavaDoc(style.getJavaDoc()); - } - } - } - - /** - * Loads all attributes for the layout data info based on the class name. - */ - public void loadLayoutParamsAttributes(LayoutParamsInfo info) { - if (getDocument() != null) { - // Transforms "LinearLayout" and "LayoutParams" into "LinearLayout_Layout". - String xmlName = String.format("%1$s_%2$s", //$NON-NLS-1$ - info.getViewLayoutClass().getShortClassName(), - info.getShortClassName()); - xmlName = xmlName.replaceFirst("Params$", ""); //$NON-NLS-1$ //$NON-NLS-2$ - - DeclareStyleableInfo style = mStyleMap.get(xmlName); - if (style != null) { - info.setAttributes(style.getAttributes()); - } - } - } - - /** - * Returns a list of all decleare-styleable found in the xml file. - */ - public Map getDeclareStyleableList() { - return Collections.unmodifiableMap(mStyleMap); - } - - /** - * Returns a map of all enum and flag constants sorted by parent attribute name. - * The map is attribute_name => (constant_name => integer_value). - */ - public Map> getEnumFlagValues() { - return mEnumFlagValues; - } - - //------------------------- - - /** - * Creates an XML document from the attrs.xml OS path. - * May return null if the file doesn't exist or cannot be parsed. - */ - private Document getDocument() { - if (mDocument == null) { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setIgnoringComments(false); - try { - DocumentBuilder builder = factory.newDocumentBuilder(); - mDocument = builder.parse(new File(mOsAttrsXmlPath)); - } catch (ParserConfigurationException e) { - CommonPlugin.log(e, "Failed to create XML document builder for %1$s", //$NON-NLS-1$ - mOsAttrsXmlPath); - } catch (SAXException e) { - CommonPlugin.log(e, "Failed to parse XML document %1$s", //$NON-NLS-1$ - mOsAttrsXmlPath); - } catch (IOException e) { - CommonPlugin.log(e, "Failed to read XML document %1$s", //$NON-NLS-1$ - mOsAttrsXmlPath); - } - } - return mDocument; - } - - /** - * Finds all the and nodes in the top node. - */ - private void parseResources(Node res) { - Node lastComment = null; - for (Node node = res.getFirstChild(); node != null; node = node.getNextSibling()) { - switch (node.getNodeType()) { - case Node.COMMENT_NODE: - lastComment = node; - break; - case Node.ELEMENT_NODE: - if (node.getNodeName().equals("declare-styleable")) { //$NON-NLS-1$ - Node nameNode = node.getAttributes().getNamedItem("name"); //$NON-NLS-1$ - if (nameNode != null) { - String name = nameNode.getNodeValue(); - - Node parentNode = node.getAttributes().getNamedItem("parent"); //$NON-NLS-1$ - String parents = parentNode == null ? null : parentNode.getNodeValue(); - - if (name != null && !mStyleMap.containsKey(name)) { - DeclareStyleableInfo style = parseDeclaredStyleable(name, node); - if (parents != null) { - style.setParents(parents.split("[ ,|]")); //$NON-NLS-1$ - } - mStyleMap.put(name, style); - if (lastComment != null) { - style.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); - } - } - } - } else if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ - parseAttr(node, lastComment); - } - lastComment = null; - break; - } - } - } - - /** - * Parses an node and convert it into an {@link AttributeInfo} if it is valid. - */ - private AttributeInfo parseAttr(Node attrNode, Node lastComment) { - AttributeInfo info = null; - Node nameNode = attrNode.getAttributes().getNamedItem("name"); //$NON-NLS-1$ - if (nameNode != null) { - String name = nameNode.getNodeValue(); - if (name != null) { - info = mAttributeMap.get(name); - // If the attribute is unknown yet, parse it. - // If the attribute is know but its format is unknown, parse it too. - if (info == null || info.getFormats().length == 0) { - info = parseAttributeTypes(attrNode, name); - if (info != null) { - mAttributeMap.put(name, info); - } - } else if (lastComment != null) { - info = new AttributeInfo(info); - } - if (info != null) { - if (lastComment != null) { - info.setJavaDoc(formatJavadoc(lastComment.getNodeValue())); - } - } - } - } - return info; - } - - /** - * Finds all the attributes for a particular style node, - * e.g. a declare-styleable of name "TextView" or "LinearLayout_Layout". - * - * @param styleName The name of the declare-styleable node - * @param declareStyleableNode The declare-styleable node itself - */ - private DeclareStyleableInfo parseDeclaredStyleable(String styleName, - Node declareStyleableNode) { - ArrayList attrs = new ArrayList(); - Node lastComment = null; - for (Node node = declareStyleableNode.getFirstChild(); - node != null; - node = node.getNextSibling()) { - - switch (node.getNodeType()) { - case Node.COMMENT_NODE: - lastComment = node; - break; - case Node.ELEMENT_NODE: - if (node.getNodeName().equals("attr")) { //$NON-NLS-1$ - AttributeInfo info = parseAttr(node, lastComment); - if (info != null) { - attrs.add(info); - } - } - lastComment = null; - break; - } - - } - - return new DeclareStyleableInfo(styleName, attrs.toArray(new AttributeInfo[attrs.size()])); - } - - /** - * Returns the {@link AttributeInfo} for a specific XML node. - * This gets the javadoc, the type, the name and the enum/flag values if any. - *

              - * The XML node is expected to have the following attributes: - *

                - *
              • "name", which is mandatory. The node is skipped if this is missing.
              • - *
              • "format".
              • - *
              - * The format may be one type or two types (e.g. "reference|color"). - * An extra format can be implied: "enum" or "flag" are not specified in the "format" attribute, - * they are implicitely stated by the presence of sub-nodes or . - *

              - * By design, nodes of the same name MUST have the same type. - * Attribute nodes are thus cached by name and reused as much as possible. - * When reusing a node, it is duplicated and its javadoc reassigned. - */ - private AttributeInfo parseAttributeTypes(Node attrNode, String name) { - TreeSet formats = new TreeSet(); - String[] enumValues = null; - String[] flagValues = null; - - Node attrFormat = attrNode.getAttributes().getNamedItem("format"); //$NON-NLS-1$ - if (attrFormat != null) { - for (String f : attrFormat.getNodeValue().split("\\|")) { //$NON-NLS-1$ - try { - Format format = AttributeInfo.Format.valueOf(f.toUpperCase()); - // enum and flags are handled differently right below - if (format != null && - format != AttributeInfo.Format.ENUM && - format != AttributeInfo.Format.FLAG) { - formats.add(format); - } - } catch (IllegalArgumentException e) { - CommonPlugin.log(e, "Unknown format name '%s' in , file '%s'.", //$NON-NLS-1$ - f, name, getOsAttrsXmlPath()); - } - } - } - - // does this have children? - enumValues = parseEnumFlagValues(attrNode, "enum", name); //$NON-NLS-1$ - if (enumValues != null) { - formats.add(AttributeInfo.Format.ENUM); - } - - // does this have children? - flagValues = parseEnumFlagValues(attrNode, "flag", name); //$NON-NLS-1$ - if (flagValues != null) { - formats.add(AttributeInfo.Format.FLAG); - } - - AttributeInfo info = new AttributeInfo(name, - formats.toArray(new AttributeInfo.Format[formats.size()])); - info.setEnumValues(enumValues); - info.setFlagValues(flagValues); - return info; - } - - /** - * Given an XML node that represents an node, this method searches - * if the node has any children nodes named "target" (e.g. "enum" or "flag"). - * Such nodes must have a "name" attribute. - *

              - * If "attrNode" is null, look for any that has the given attrNode - * and the requested children nodes. - *

              - * This method collects all the possible names of these children nodes and - * return them. - * - * @param attrNode The XML node - * @param filter The child node to look for, either "enum" or "flag". - * @param attrName The value of the name attribute of - * - * @return Null if there are no such children nodes, otherwise an array of length >= 1 - * of all the names of these children nodes. - */ - private String[] parseEnumFlagValues(Node attrNode, String filter, String attrName) { - ArrayList names = null; - for (Node child = attrNode.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child.getNodeType() == Node.ELEMENT_NODE && child.getNodeName().equals(filter)) { - Node nameNode = child.getAttributes().getNamedItem("name"); //$NON-NLS-1$ - if (nameNode == null) { - CommonPlugin.log(IStatus.WARNING, - "Missing name attribute in <%s>", //$NON-NLS-1$ - attrName, filter); - } else { - if (names == null) { - names = new ArrayList(); - } - String name = nameNode.getNodeValue(); - names.add(name); - - Node valueNode = child.getAttributes().getNamedItem("value"); //$NON-NLS-1$ - if (valueNode == null) { - CommonPlugin.log(IStatus.WARNING, - "Missing value attribute in <%s name=\"%s\">", //$NON-NLS-1$ - attrName, filter, name); - } else { - String value = valueNode.getNodeValue(); - try { - int i = value.startsWith("0x") ? - Integer.parseInt(value.substring(2), 16 /* radix */) : - Integer.parseInt(value); - - Map map = mEnumFlagValues.get(attrName); - if (map == null) { - map = new HashMap(); - mEnumFlagValues.put(attrName, map); - } - map.put(name, Integer.valueOf(i)); - - } catch(NumberFormatException e) { - CommonPlugin.log(e, - "Value in <%s name=\"%s\" value=\"%s\"> is not a valid decimal or hexadecimal", //$NON-NLS-1$ - attrName, filter, name, value); - } - } - } - } - } - return names == null ? null : names.toArray(new String[names.size()]); - } - - /** - * Formats the javadoc. - * Only keeps the first sentence. - * Removes and simplifies links and references. - */ - private String formatJavadoc(String comment) { - if (comment == null) { - return null; - } - // sanitize & collapse whitespace - comment = comment.replaceAll("\\s+", " "); //$NON-NLS-1$ //$NON-NLS-2$ - // take everything up to the first dot that is followed by a space or the end of the line. - // I love regexps :-). For the curious, the regexp is: - // - start of line - // - ignore whitespace - // - group: - // - everything, not greedy - // - non-capturing group (?: ) - // - end of string - // or - // - not preceded by a letter, a dot and another letter (for "i.e" and "e.g" ) - // ( mResourcesChangeListeners = new ArrayList(); - - private Hashtable mAttributeValues; - - private IResourceRepository mSystemResourceRepository; - - private ViewClassInfo[] mLayoutViewsInfo; - private ViewClassInfo[] mLayoutGroupsInfo; - private ViewClassInfo[] mPreferencesInfo; - private ViewClassInfo[] mPreferenceGroupsInfo; - - private Map mXmlMenuMap; - private Map mXmlSearchableMap; - private Map mManifestMap; - - /** Flags indicating whether we have some resources */ - private boolean mHasResources = false; - - private String mLayoutLibLocation; - - private String mFrameworkResourcesLocation; - - private Map> mEnumValueMap; - - private String mFrameworkFontsLocation; - - private String mDocBaseUrl; - - /** - * Creates a new Framework Resource Manager. - * - * mAttributeValues is a map { key => list [ values ] }. - * The key for the map is "(element-xml-name,attribute-namespace:attribute-xml-local-name)". - * The attribute namespace prefix must be: - * - "android" for AndroidConstants.NS_RESOURCES - * - "xmlns" for the XMLNS URI. - */ - private FrameworkResourceManager() { - /* TODO Attempt to load those values from android.jar */ - mAttributeValues = new Hashtable(); - mAttributeValues.put("(manifest,xmlns:android)", new String[] { //$NON-NLS-1$ - AndroidConstants.NS_RESOURCES - }); - mAttributeValues.put("(permission,android:protectionLevel)", new String[] { //$NON-NLS-1$ - "application", //$NON-NLS-1$ - "system" //$NON-NLS-1$ - }); - mAttributeValues.put("(application,android:persistent)", new String[] { //$NON-NLS-1$ - "true", //$NON-NLS-1$ - "false", //$NON-NLS-1$ - }); - mAttributeValues.put("(activity,android:clearOnBackground)", sBooleanValues); //$NON-NLS-1$ - mAttributeValues.put("(activity,android:configChanges)", new String[] { //$NON-NLS-1$ - "fontScale", //$NON-NLS-1$ - "mcc", //$NON-NLS-1$ - "mnc", //$NON-NLS-1$ - "locale", //$NON-NLS-1$ - "touchscreen", //$NON-NLS-1$ - "keyboard", //$NON-NLS-1$ - "keyboardHidden", //$NON-NLS-1$ - "navigation", //$NON-NLS-1$ - "orientation", //$NON-NLS-1$ - }); - mAttributeValues.put("(activity,android:launchMode)", new String[] { //$NON-NLS-1$ - "multiple", //$NON-NLS-1$ - "singleTop", //$NON-NLS-1$ - "singleTask", //$NON-NLS-1$ - "singleInstance" //$NON-NLS-1$ - }); - mAttributeValues.put("(activity,android:stateNotNeeded)", sBooleanValues); //$NON-NLS-1$ - mAttributeValues.put("(provider,android:syncable)", sBooleanValues); //$NON-NLS-1$ - mAttributeValues.put("(provider,android:multiprocess)", sBooleanValues); //$NON-NLS-1$ - mAttributeValues.put("(instrumentation,android:functionalTest)", sBooleanValues); //$NON-NLS-1$ - mAttributeValues.put("(instrumentation,android:handleProfiling)", sBooleanValues); //$NON-NLS-1$ - - } - - /** - * Returns the {@link FrameworkResourceManager} instance. - */ - public static FrameworkResourceManager getInstance() { - return sThis; - } - - /** - * Sets the resources and notifies the listeners - * @param documentationBaseUrl - */ - public synchronized void setResources(IResourceRepository systemResourceRepository, - ViewClassInfo[] layoutViewsInfo, - ViewClassInfo[] layoutGroupsInfo, - ViewClassInfo[] preferencesInfo, - ViewClassInfo[] preferenceGroupsInfo, - Map xmlMenuMap, - Map xmlSearchableMap, - Map manifestMap, - Map> enumValueMap, - String[] permissionValues, - String[] activityIntentActionValues, - String[] broadcastIntentActionValues, - String[] serviceIntentActionValues, - String[] intentCategoryValues, - String documentationBaseUrl) { - mSystemResourceRepository = systemResourceRepository; - - mLayoutViewsInfo = layoutViewsInfo; - mLayoutGroupsInfo = layoutGroupsInfo; - - mPreferencesInfo = preferencesInfo; - mPreferenceGroupsInfo = preferenceGroupsInfo; - - mXmlMenuMap = xmlMenuMap; - mXmlSearchableMap = xmlSearchableMap; - mManifestMap = manifestMap; - mEnumValueMap = enumValueMap; - mDocBaseUrl = documentationBaseUrl; - - setPermissions(permissionValues); - setIntentFilterActionsAndCategories(activityIntentActionValues, broadcastIntentActionValues, - serviceIntentActionValues, intentCategoryValues); - - mHasResources = true; - - notifyFrameworkResourcesChangeListeners(); - } - - public synchronized IResourceRepository getSystemResources() { - return mSystemResourceRepository; - } - - public synchronized String[] getValues(String elementName, String attributeName) { - String key = String.format("(%1$s,%2$s)", elementName, attributeName); //$NON-NLS-1$ - return mAttributeValues.get(key); - } - - public synchronized String[] getValues(String elementName, String attributeName, - String greatGrandParentElementName) { - if (greatGrandParentElementName != null) { - String key = String.format("(%1$s,%2$s,%3$s)", greatGrandParentElementName, //$NON-NLS-1$ - elementName, attributeName); - String[] values = mAttributeValues.get(key); - if (values != null) { - return values; - } - } - - return getValues(elementName, attributeName); - } - - public synchronized String[] getValues(String key) { - return mAttributeValues.get(key); - } - - public synchronized ViewClassInfo[] getLayoutViewsInfo() { - return mLayoutViewsInfo; - } - - public synchronized ViewClassInfo[] getLayoutGroupsInfo() { - return mLayoutGroupsInfo; - } - - public synchronized ViewClassInfo[] getPreferencesInfo() { - return mPreferencesInfo; - } - - public synchronized ViewClassInfo[] getPreferenceGroupsInfo() { - return mPreferenceGroupsInfo; - } - - public synchronized Map getXmlMenuDefinitions() { - return mXmlMenuMap; - } - - public synchronized Map getXmlSearchableDefinitions() { - return mXmlSearchableMap; - } - - public synchronized Map getManifestDefinitions() { - return mManifestMap; - } - - public String getDocumentationBaseUrl() { - return mDocBaseUrl == null ? AndroidConstants.CODESITE_BASE_URL : mDocBaseUrl; - } - - /** - * Sets the permission values - * @param permissionValues the list of permissions - */ - private void setPermissions(String[] permissionValues) { - setValues("(uses-permission,android:name)", permissionValues); //$NON-NLS-1$ - setValues("(application,android:permission)", permissionValues); //$NON-NLS-1$ - setValues("(activity,android:permission)", permissionValues); //$NON-NLS-1$ - setValues("(receiver,android:permission)", permissionValues); //$NON-NLS-1$ - setValues("(service,android:permission)", permissionValues); //$NON-NLS-1$ - setValues("(provider,android:permission)", permissionValues); //$NON-NLS-1$ - } - - private void setIntentFilterActionsAndCategories(String[] activityIntentActions, - String[] broadcastIntentActions, String[] serviceIntentActions, - String[] intentCategoryValues) { - setValues("(activity,action,android:name)", activityIntentActions); //$NON-NLS-1$ - setValues("(receiver,action,android:name)", broadcastIntentActions); //$NON-NLS-1$ - setValues("(service,action,android:name)", serviceIntentActions); //$NON-NLS-1$ - setValues("(category,android:name)", intentCategoryValues); //$NON-NLS-1$ - } - - /** - * Sets a (name, values) pair in the hash map. - *

              - * If the name is already present in the map, it is first removed. - * @param name the name associated with the values. - * @param values The values to add. - */ - private void setValues(String name, String[] values) { - mAttributeValues.remove(name); - mAttributeValues.put(name, values); - } - - - /** - * Called by the ADT plugin when the SDK path has changed. - * This stores the path locally and then notifies all attached listeners. - */ - private void notifyFrameworkResourcesChangeListeners() { - for (Runnable listener : mResourcesChangeListeners) { - try { - listener.run(); - } catch (Exception e) { - CommonPlugin.log(e, "IPathChangedListener failed."); //$NON-NLS-1$ - } - } - } - - /** Adds a new listener that listens to framework resources changes. - *

              If resources have already been set, then the listener is automatically notified. */ - public synchronized void addFrameworkResourcesChangeListener(Runnable listener) { - if (listener != null && mResourcesChangeListeners.indexOf(listener) == -1) { - mResourcesChangeListeners.add(listener); - - if (mHasResources) { - listener.run(); - } - } - } - - /** Removes a framework resources changes listener. - *

              Safe to call with null or with the same value. */ - public synchronized void removeFrameworkResourcesChangeListener(Runnable listener) { - mResourcesChangeListeners.remove(listener); - } - - public void setLayoutLibLocation(String osLocation) { - mLayoutLibLocation = osLocation; - } - - public String getLayoutLibLocation() { - return mLayoutLibLocation; - } - - public void setFrameworkResourcesLocation(String osLocation) { - mFrameworkResourcesLocation = osLocation; - } - - public String getFrameworkResourcesLocation() { - return mFrameworkResourcesLocation; - } - - public Map> getEnumValueMap() { - return mEnumValueMap; - } - - public void setFrameworkFontLocation(String osLocation) { - mFrameworkFontsLocation = osLocation; - } - - public String getFrameworkFontLocation() { - return mFrameworkFontsLocation; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IIdResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IIdResourceItem.java deleted file mode 100644 index 38b7e03..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IIdResourceItem.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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.common.resources; - -/** - * Classes which implements this interface provides a method indicating the state of a resource of - * type {@link ResourceType#ID}. - */ -public interface IIdResourceItem { - /** - * Returns whether the ID resource has been declared inline inside another resource XML file. - */ - public boolean isDeclaredInline(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IPathChangedListener.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IPathChangedListener.java deleted file mode 100644 index 53d9077..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IPathChangedListener.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.common.resources; - -/** - * Classes which implement this interface provide a method that deals with - * a path change. - */ -public interface IPathChangedListener { - /** - * Sent when the location of the android sdk directory changed. - * @param osPath The new android sdk directory location. - */ - public void pathChanged(String osPath); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IResourceRepository.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IResourceRepository.java deleted file mode 100644 index 3819997..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/IResourceRepository.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2007 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.common.resources; - -/** - * A repository of resources. This allows access to the resource by {@link ResourceType}. - */ -public interface IResourceRepository { - - /** - * Returns the present {@link ResourceType}s in the project. - * @return an array containing all the type of resources existing in the project. - */ - public abstract ResourceType[] getAvailableResourceTypes(); - - /** - * Returns an array of the existing resource for the specified type. - * @param type the type of the resources to return - */ - public abstract ResourceItem[] getResources(ResourceType type); - - /** - * Returns whether resources of the specified type are present. - * @param type the type of the resources to check. - */ - public abstract boolean hasResources(ResourceType type); - - /** - * Returns whether the repository is a system repository. - */ - public abstract boolean isSystemRepository(); - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceItem.java deleted file mode 100644 index 83527f3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceItem.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.common.resources; - -/** - * Base class representing a Resource Item, as returned by a {@link IResourceRepository}. - */ -public class ResourceItem implements Comparable { - - private final String mName; - - /** - * Constructs a new ResourceItem - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ResourceItem(String name) { - mName = name; - } - - /** - * Returns the name of the resource item. - */ - public final String getName() { - return mName; - } - - /** - * Compares the {@link ResourceItem} to another. - * @param other the ResourceItem to be compared to. - */ - public int compareTo(ResourceItem other) { - return mName.compareTo(other.mName); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceType.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceType.java deleted file mode 100644 index 3d64e5d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ResourceType.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2007 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.common.resources; - -/** - * Enum representing a type of compiled resource. - */ -public enum ResourceType { - ANIM("anim", "Animation"), //$NON-NLS-1$ - ARRAY("array", "Array"), //$NON-NLS-1$ - ATTR("attr", "Attr"), //$NON-NLS-1$ - COLOR("color", "Color"), //$NON-NLS-1$ - DIMEN("dimen", "Dimension"), //$NON-NLS-1$ - DRAWABLE("drawable", "Drawable"), //$NON-NLS-1$ - ID("id", "ID"), //$NON-NLS-1$ - LAYOUT("layout", "Layout"), //$NON-NLS-1$ - MENU("menu", "Menu"), //$NON-NLS-1$ - RAW("raw", "Raw"), //$NON-NLS-1$ - STRING("string", "String"), //$NON-NLS-1$ - STYLE("style", "Style"), //$NON-NLS-1$ - STYLEABLE("styleable", "Styleable"), //$NON-NLS-1$ - XML("xml", "XML"); //$NON-NLS-1$ - - private String mName; - private String mDisplayName; - - ResourceType(String name, String displayName) { - mName = name; - mDisplayName = displayName; - } - - /** - * Returns the resource type name, as used by XML files. - */ - public String getName() { - return mName; - } - - /** - * Returns a translated display name for the resource type. - */ - public String getDisplayName() { - return mDisplayName; - } - - /** - * Returns the enum by its name as it appears in the XML or the R class. - * @param name name of the resource - * @return the matching {@link ResourceType} or null if no match was found. - */ - public static ResourceType getEnum(String name) { - for (ResourceType rType : values()) { - if (rType.mName.equals(name)) { - return rType; - } - } - return null; - } - - /** - * Returns a formatted string usable in an XML to use the specified {@link ResourceItem}. - * @param resourceItem The resource item. - * @param system Whether this is a system resource or a project resource. - * @return a string in the format @[type]/[name] - */ - public String getXmlString(ResourceItem resourceItem, boolean system) { - if (this == ID && resourceItem instanceof IIdResourceItem) { - IIdResourceItem idResource = (IIdResourceItem)resourceItem; - if (idResource.isDeclaredInline()) { - return (system?"@android:":"@+") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - } - - return (system?"@android:":"@") + mName + "/" + resourceItem.getName(); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ - } - - /** - * Returns an array with all the names defined by this enum. - */ - public static String[] getNames() { - ResourceType[] values = values(); - String[] names = new String[values.length]; - for (int i = values.length - 1; i >= 0; --i) { - names[i] = values[i].getName(); - } - return names; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java b/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java deleted file mode 100644 index 619e3cc..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.common/src/com/android/ide/eclipse/common/resources/ViewClassInfo.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.common.resources; - -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; - -/** - * Information needed to represent a View or ViewGroup (aka Layout) item - * in the layout hierarchy, as extracted from the main android.jar and the - * associated attrs.xml. - */ -public class ViewClassInfo { - /** Is this a layout class (i.e. ViewGroup) or just a view? */ - private boolean mIsLayout; - /** FQCN e.g. android.view.View, never null. */ - private String mCanonicalClassName; - /** Short class name, e.g. View, never null. */ - private String mShortClassName; - /** Super class. Can be null. */ - private ViewClassInfo mSuperClass; - /** Short javadoc. Can be null. */ - private String mJavaDoc; - /** Attributes for this view or view group. Can be empty but never null. */ - private AttributeInfo[] mAttributes; - - public static class LayoutParamsInfo { - /** Short class name, e.g. LayoutData, never null. */ - private String mShortClassName; - /** ViewLayout class info owning this layout data */ - private ViewClassInfo mViewLayoutClass; - /** Super class. Can be null. */ - private LayoutParamsInfo mSuperClass; - /** Layout Data Attributes for layout classes. Can be empty but not null. */ - private AttributeInfo[] mAttributes; - - public LayoutParamsInfo(ViewClassInfo enclosingViewClassInfo, - String shortClassName, LayoutParamsInfo superClassInfo) { - mShortClassName = shortClassName; - mViewLayoutClass = enclosingViewClassInfo; - mSuperClass = superClassInfo; - mAttributes = new AttributeInfo[0]; - } - - /** Returns short class name, e.g. "LayoutData" */ - public String getShortClassName() { - return mShortClassName; - } - /** Returns the ViewLayout class info enclosing this layout data. Cannot null. */ - public ViewClassInfo getViewLayoutClass() { - return mViewLayoutClass; - } - /** Returns the super class info. Can be null. */ - public LayoutParamsInfo getSuperClass() { - return mSuperClass; - } - /** Returns the LayoutData attributes. Can be empty but not null. */ - public AttributeInfo[] getAttributes() { - return mAttributes; - } - /** Sets the LayoutData attributes. Can be empty but not null. */ - public void setAttributes(AttributeInfo[] attributes) { - mAttributes = attributes; - } - } - - /** Layout data info for a layout class. Null for all non-layout classes and always - * non-null for a layout class. */ - public LayoutParamsInfo mLayoutData; - - // -------- - - public ViewClassInfo(boolean isLayout, String canonicalClassName, String shortClassName) { - mIsLayout = isLayout; - mCanonicalClassName = canonicalClassName; - mShortClassName = shortClassName; - mAttributes = new AttributeInfo[0]; - } - - /** Returns whether this is a layout class (i.e. ViewGroup) or just a View */ - public boolean isLayout() { - return mIsLayout; - } - - /** Returns FQCN e.g. "android.view.View" */ - public String getCanonicalClassName() { - return mCanonicalClassName; - } - - /** Returns short class name, e.g. "View" */ - public String getShortClassName() { - return mShortClassName; - } - - /** Returns the super class. Can be null. */ - public ViewClassInfo getSuperClass() { - return mSuperClass; - } - - /** Returns a short javadoc */ - public String getJavaDoc() { - return mJavaDoc; - } - - /** Returns the attributes for this view or view group. Maybe empty but not null. */ - public AttributeInfo[] getAttributes() { - return mAttributes; - } - - /** Returns the LayoutData info for layout classes. Null for non-layout view classes. */ - public LayoutParamsInfo getLayoutData() { - return mLayoutData; - } - - /** - * Sets a link on the info of the super class of this View or ViewGroup. - *

              - * The super class info must be of the same kind (i.e. group to group or view to view) - * except for the top ViewGroup which links to the View info. - *

              - * The super class cannot be null except for the top View info. - */ - public void setSuperClass(ViewClassInfo superClass) { - mSuperClass = superClass; - } - - /** Sets the javadoc for this View or ViewGroup. */ - public void setJavaDoc(String javaDoc) { - mJavaDoc = javaDoc; - } - - /** Sets the list of attributes for this View or ViewGroup. */ - public void setAttributes(AttributeInfo[] attributes) { - mAttributes = attributes; - } - - /** - * Sets the {@link LayoutParamsInfo} for layout classes. - * Does nothing for non-layout view classes. - */ - public void setLayoutParams(LayoutParamsInfo layoutData) { - if (mIsLayout) { - mLayoutData = layoutData; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF index 2f315c5..09b8085 100644 --- a/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.ddms/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Dalvik Debug Monitor Service Bundle-SymbolicName: com.android.ide.eclipse.ddms;singleton:=true -Bundle-Version: 0.8.1.qualifier +Bundle-Version: 0.9.0.qualifier Bundle-Activator: com.android.ide.eclipse.ddms.DdmsPlugin Bundle-Vendor: The Android Open Source Project Bundle-Localization: plugin @@ -10,11 +10,13 @@ Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.ui.console Eclipse-LazyStart: true -Export-Package: com.android.ide.eclipse.ddms, - com.android.ide.eclipse.ddms.views, - com.android.ddmlib, +Export-Package: com.android.ddmlib, + com.android.ddmlib.log, + com.android.ddmlib.testrunner, com.android.ddmuilib, - com.android.ddmuilib.console + com.android.ddmuilib.console, + com.android.ide.eclipse.ddms, + com.android.ide.eclipse.ddms.views Bundle-ClassPath: libs/jcommon-1.0.12.jar, libs/jfreechart-1.0.9.jar, libs/jfreechart-1.0.9-swt.jar, diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/.classpath b/eclipse/plugins/com.android.ide.eclipse.editors/.classpath deleted file mode 100644 index 66dbeb3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/.classpath +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/.project b/eclipse/plugins/com.android.ide.eclipse.editors/.project deleted file mode 100644 index 292c698..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - editors - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.editors/META-INF/MANIFEST.MF deleted file mode 100644 index 0bfaff9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/META-INF/MANIFEST.MF +++ /dev/null @@ -1,56 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Android Editors -Bundle-SymbolicName: com.android.ide.eclipse.editors;singleton:=true -Bundle-Version: 0.8.1.qualifier -Bundle-Activator: com.android.ide.eclipse.editors.EditorsPlugin -Bundle-Vendor: The Android Open Source Project -Require-Bundle: com.android.ide.eclipse.common, - org.eclipse.ui, - org.eclipse.core.runtime, - org.eclipse.core.resources, - org.eclipse.ui.editors, - org.eclipse.jface.text, - org.eclipse.ui.ide, - org.eclipse.wst.sse.ui, - org.eclipse.wst.xml.ui, - org.eclipse.wst.xml.core, - org.eclipse.wst.sse.core, - org.eclipse.ui.forms, - org.eclipse.jdt.core, - org.eclipse.ui.browser, - org.eclipse.jdt.ui, - org.eclipse.gef, - org.eclipse.ui.views, - org.eclipse.ui.console -Eclipse-LazyStart: true -Export-Package: com.android.ide.eclipse.editors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.descriptors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.layout;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.layout.descriptors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.layout.parts;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.layout.uimodel;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.manifest;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.manifest.descriptors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.manifest.model;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.manifest.pages;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.menu;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.menu.descriptors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.configurations;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.descriptors;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.explorer;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.manager;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.manager.files;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.resources.uimodel;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.ui;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.ui.tree;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.uimodel;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.wizards;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.xml;x-friends:="com.android.ide.eclipse.tests", - com.android.ide.eclipse.editors.xml.descriptors;x-friends:="com.android.ide.eclipse.tests" -Bundle-ClassPath: kxml2-2.3.0.jar, - ., - layoutlib_api.jar, - ninepatch.jar, - layoutlib_utils.jar diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/MODULE_LICENSE_EPL b/eclipse/plugins/com.android.ide.eclipse.editors/MODULE_LICENSE_EPL deleted file mode 100644 index e69de29..0000000 diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/NOTICE b/eclipse/plugins/com.android.ide.eclipse.editors/NOTICE deleted file mode 100644 index 49c101d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/NOTICE +++ /dev/null @@ -1,224 +0,0 @@ -*Eclipse Public License - v 1.0* - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE -PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF -THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -*1. DEFINITIONS* - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and -documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: - -i) changes to the Program, and - -ii) additions to the Program; - -where such changes and/or additions to the Program originate from and -are distributed by that particular Contributor. A Contribution -'originates' from a Contributor if it was added to the Program by such -Contributor itself or anyone acting on such Contributor's behalf. -Contributions do not include additions to the Program which: (i) are -separate modules of software distributed in conjunction with the Program -under their own license agreement, and (ii) are not derivative works of -the Program. - -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents " mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, -including all Contributors. - -*2. GRANT OF RIGHTS* - -a) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free copyright -license to reproduce, prepare derivative works of, publicly display, -publicly perform, distribute and sublicense the Contribution of such -Contributor, if any, and such derivative works, in source code and -object code form. - -b) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free patent license -under Licensed Patents to make, use, sell, offer to sell, import and -otherwise transfer the Contribution of such Contributor, if any, in -source code and object code form. This patent license shall apply to the -combination of the Contribution and the Program if, at the time the -Contribution is added by the Contributor, such addition of the -Contribution causes such combination to be covered by the Licensed -Patents. The patent license shall not apply to any other combinations -which include the Contribution. No hardware per se is licensed hereunder. - -c) Recipient understands that although each Contributor grants the -licenses to its Contributions set forth herein, no assurances are -provided by any Contributor that the Program does not infringe the -patent or other intellectual property rights of any other entity. Each -Contributor disclaims any liability to Recipient for claims brought by -any other entity based on infringement of intellectual property rights -or otherwise. As a condition to exercising the rights and licenses -granted hereunder, each Recipient hereby assumes sole responsibility to -secure any other intellectual property rights needed, if any. For -example, if a third party patent license is required to allow Recipient -to distribute the Program, it is Recipient's responsibility to acquire -that license before distributing the Program. - -d) Each Contributor represents that to its knowledge it has sufficient -copyright rights in its Contribution, if any, to grant the copyright -license set forth in this Agreement. - -*3. REQUIREMENTS* - -A Contributor may choose to distribute the Program in object code form -under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and - -b) its license agreement: - -i) effectively disclaims on behalf of all Contributors all warranties -and conditions, express and implied, including warranties or conditions -of title and non-infringement, and implied warranties or conditions of -merchantability and fitness for a particular purpose; - -ii) effectively excludes on behalf of all Contributors all liability for -damages, including direct, indirect, special, incidental and -consequential damages, such as lost profits; - -iii) states that any provisions which differ from this Agreement are -offered by that Contributor alone and not by any other party; and - -iv) states that source code for the Program is available from such -Contributor, and informs licensees how to obtain it in a reasonable -manner on or through a medium customarily used for software exchange. - -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and - -b) a copy of this Agreement must be included with each copy of the Program. - -Contributors may not remove or alter any copyright notices contained -within the Program. - -Each Contributor must identify itself as the originator of its -Contribution, if any, in a manner that reasonably allows subsequent -Recipients to identify the originator of the Contribution. - -*4. COMMERCIAL DISTRIBUTION* - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, the -Contributor who includes the Program in a commercial product offering -should do so in a manner which does not create potential liability for -other Contributors. Therefore, if a Contributor includes the Program in -a commercial product offering, such Contributor ("Commercial -Contributor") hereby agrees to defend and indemnify every other -Contributor ("Indemnified Contributor") against any losses, damages and -costs (collectively "Losses") arising from claims, lawsuits and other -legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the -Program in a commercial product offering. The obligations in this -section do not apply to any claims or Losses relating to any actual or -alleged intellectual property infringement. In order to qualify, an -Indemnified Contributor must: a) promptly notify the Commercial -Contributor in writing of such claim, and b) allow the Commercial -Contributor to control, and cooperate with the Commercial Contributor -in, the defense and any related settlement negotiations. The Indemnified -Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those -performance claims and warranties, and if a court requires any other -Contributor to pay any damages as a result, the Commercial Contributor -must pay those damages. - -*5. NO WARRANTY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED -ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES -OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR -A PARTICULAR PURPOSE. Each Recipient is solely responsible for -determining the appropriateness of using and distributing the Program -and assumes all risks associated with its exercise of rights under this -Agreement , including but not limited to the risks and costs of program -errors, compliance with applicable laws, damage to or loss of data, -programs or equipment, and unavailability or interruption of operations. - -*6. DISCLAIMER OF LIABILITY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR -ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING -WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR -DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -*7. GENERAL* - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further action -by the parties hereto, such provision shall be reformed to the minimum -extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including -a cross-claim or counterclaim in a lawsuit) alleging that the Program -itself (excluding combinations of the Program with other software or -hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails -to comply with any of the material terms or conditions of this Agreement -and does not cure such failure in a reasonable period of time after -becoming aware of such noncompliance. If all Recipient's rights under -this Agreement terminate, Recipient agrees to cease use and distribution -of the Program as soon as reasonably practicable. However, Recipient's -obligations under this Agreement and any licenses granted by Recipient -relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and may -only be modified in the following manner. The Agreement Steward reserves -the right to publish new versions (including revisions) of this -Agreement from time to time. No one other than the Agreement Steward has -the right to modify this Agreement. The Eclipse Foundation is the -initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is -published, Contributor may elect to distribute the Program (including -its Contributions) under the new version. Except as expressly stated in -Sections 2(a) and 2(b) above, Recipient receives no rights or licenses -to the intellectual property of any Contributor under this Agreement, -whether expressly, by implication, estoppel or otherwise. All rights in -the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the -intellectual property laws of the United States of America. No party to -this Agreement will bring a legal action under this Agreement more than -one year after the cause of action arose. Each party waives its rights -to a jury trial in any resulting litigation. - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/build.properties b/eclipse/plugins/com.android.ide.eclipse.editors/build.properties deleted file mode 100644 index 0a7bc7d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/build.properties +++ /dev/null @@ -1,10 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - plugin.xml,\ - icons/,\ - layoutlib_api.jar,\ - kxml2-2.3.0.jar,\ - ninepatch.jar,\ - layoutlib_utils.jar diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/add.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/add.png deleted file mode 100644 index eefc2ca..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/add.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/android.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/android.png deleted file mode 100644 index 3779d4d..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/android.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/android_large.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/android_large.png deleted file mode 100644 index 64e3601..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/android_large.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/az_sort.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/az_sort.png deleted file mode 100644 index 5d92f76..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/az_sort.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/delete.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/delete.png deleted file mode 100644 index db5fab8..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/delete.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/dimension.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/dimension.png deleted file mode 100644 index 10057c8..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/dimension.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/down.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/down.png deleted file mode 100644 index 36cd223..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/down.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/dpi.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/dpi.png deleted file mode 100644 index fae5e96..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/dpi.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/error.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/error.png deleted file mode 100644 index 1eecf2c..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/error.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/keyboard.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/keyboard.png deleted file mode 100644 index 7911a85..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/keyboard.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/language.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/language.png deleted file mode 100644 index a727dd5..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/language.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/match.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/match.png deleted file mode 100644 index 7e939c2..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/match.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/mnc.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/mnc.png deleted file mode 100644 index aefffe4..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/mnc.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/navpad.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/navpad.png deleted file mode 100644 index c2bb79a..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/navpad.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/orientation.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/orientation.png deleted file mode 100644 index 423c3cd..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/orientation.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/region.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/region.png deleted file mode 100644 index 9cfb53f..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/region.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/text_input.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/text_input.png deleted file mode 100644 index b4ddc87..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/text_input.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/touch.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/touch.png deleted file mode 100644 index 6536576..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/touch.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/up.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/up.png deleted file mode 100644 index 35b9a46..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/up.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/warning.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/warning.png deleted file mode 100644 index ca3b6ed..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/warning.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/icons/world.png b/eclipse/plugins/com.android.ide.eclipse.editors/icons/world.png deleted file mode 100644 index afdc16c..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.editors/icons/world.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.editors/plugin.xml deleted file mode 100644 index 2678a5d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/plugin.xml +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidContentAssist.java deleted file mode 100644 index 1dd0c27..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidContentAssist.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (C) 2007 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.editors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; - -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextViewer; -import org.eclipse.jface.text.TextSelection; -import org.eclipse.jface.text.contentassist.CompletionProposal; -import org.eclipse.jface.text.contentassist.ICompletionProposal; -import org.eclipse.jface.text.contentassist.IContentAssistProcessor; -import org.eclipse.jface.text.contentassist.IContextInformation; -import org.eclipse.jface.text.contentassist.IContextInformationValidator; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.graphics.Image; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PlatformUI; -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.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.regex.Pattern; - -/** - * Content Assist Processor for Android XML files - */ -public abstract class AndroidContentAssist implements IContentAssistProcessor { - - /** Regexp to detect a full attribute after an element tag. - *

              Syntax:
              -     *    name = "..." quoted string with all but < and "
              -     * or:
              -     *    name = '...' quoted string with all but < and '
              -     * 
              - */ - private static Pattern sFirstAttribute = Pattern.compile( - "^ *[a-zA-Z_:]+ *= *(?:\"[^<\"]*\"|'[^<']*')"); //$NON-NLS-1$ - - /** Regexp to detect an element tag name */ - private static Pattern sFirstElementWord = Pattern.compile("^[a-zA-Z0-9_:]+"); //$NON-NLS-1$ - - /** Regexp to detect whitespace */ - private static Pattern sWhitespace = Pattern.compile("\\s+"); //$NON-NLS-1$ - - protected final static String ROOT_ELEMENT = ""; - - /** Descriptor of the root of the XML hierarchy. This a "fake" ElementDescriptor which - * is used to list all the possible roots given by actual implementations. */ - private ElementDescriptor mRootDescriptor; - - /** - * Constructor for AndroidContentAssist - * @param rootElementDescriptors The valid root elements of the XML hierarchy - */ - public AndroidContentAssist(ElementDescriptor[] rootElementDescriptors) { - mRootDescriptor = new ElementDescriptor("", rootElementDescriptors); - } - - /** - * Returns a list of completion proposals based on the - * specified location within the document that corresponds - * to the current cursor position within the text viewer. - * - * @param viewer the viewer whose document is used to compute the proposals - * @param offset an offset within the document for which completions should be computed - * @return an array of completion proposals or null if no proposals are possible - * - * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) - */ - public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { - - AndroidEditor editor = getAndroidEditor(viewer); - UiElementNode rootUiNode = editor.getUiRootNode(); - - Object[] choices = null; /* An array of ElementDescriptor, or AttributeDescriptor - or String or null */ - String parent = ""; //$NON-NLS-1$ - String wordPrefix = extractElementPrefix(viewer, offset); - char needTag = 0; - boolean isElement = false; - boolean isAttribute = false; - - Node currentNode = getNode(viewer, offset); - if (currentNode == null) - return null; - - // check to see if we can find a UiElementNode matching this XML node - UiElementNode currentUiNode = - rootUiNode == null ? null : rootUiNode.findXmlNode(currentNode); - - if (currentNode.getNodeType() == Node.ELEMENT_NODE) { - parent = currentNode.getNodeName(); - - if (wordPrefix.equals(parent)) { - // We are still editing the element's tag name, not the attributes - // (the element's tag name may not even be complete) - isElement = true; - choices = getChoicesForElement(parent, currentNode); - } else { - // We're not editing the current node name, so we might be editing its - // attributes instead... - isAttribute = true; - AttribInfo info = parseAttributeInfo(viewer, offset); - if (info != null) { - // We're editing attributes in an element node (either the attributes' names - // or their values). - choices = getChoicesForAttribute(parent, currentNode, currentUiNode, info); - - if (info.correctedPrefix != null) { - wordPrefix = info.correctedPrefix; - } - needTag = info.needTag; - } - } - } else if (currentNode.getNodeType() == Node.TEXT_NODE) { - isElement = true; - // Examine the parent of the text node. - choices = getChoicesForTextNode(currentNode); - } - - // Abort if we can't recognize the context or there are no completion choices - if (choices == null || choices.length == 0) return null; - - if (isElement) { - // If we found some suggestions, do we need to add an opening "<" bracket - // for the element? We don't if the cursor is right after "<" or " visited = new HashSet(); - - String prefix = null; - for (; prefix == null && - node != null && - node.getNodeType() == Node.ELEMENT_NODE; - node = node.getParentNode()) { - NamedNodeMap attrs = node.getAttributes(); - for (int n = attrs.getLength() - 1; n >= 0; --n) { - Node attr = attrs.item(n); - if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$ - String uri = attr.getNodeValue(); - if (AndroidConstants.NS_RESOURCES.equals(uri)) { - return attr.getLocalName(); - } - visited.add(uri); - } - } - } - - // Use a sensible default prefix if we can't find one. - // We need to make sure the prefix is not one that was declared in the scope - // visited above. - prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$ - String base = prefix; - for (int i = 1; visited.contains(prefix); i++) { - prefix = base + Integer.toString(i); - } - return prefix; - } - - /** - * Gets the choices when the user is editing the name of an XML element. - *

              - * The user is editing the name of an element (the "parent"). - * Find the grand-parent and if one is found, return its children element list. - * The name which is being edited should be one of those. - *

              - * Example: returns the list of all elements that - * can be found under , of which is one of the choices. - * - * @return an ElementDescriptor[] or null if no valid element was found. - */ - private Object[] getChoicesForElement(String parent, Node current_node) { - ElementDescriptor grandparent = null; - if (current_node.getParentNode().getNodeType() == Node.ELEMENT_NODE) { - grandparent = getDescriptor(current_node.getParentNode().getNodeName()); - } else if (current_node.getParentNode().getNodeType() == Node.DOCUMENT_NODE) { - grandparent = mRootDescriptor; - } - if (grandparent != null) { - for (ElementDescriptor e : grandparent.getChildren()) { - if (e.getXmlName().startsWith(parent)) { - return grandparent.getChildren(); - } - } - } - - return null; - } - - /** - * Gets the choices when the user is editing an XML attribute. - *

              - * In input, attrInfo contains details on the analyzed context, namely whether the - * user is editing an attribute value (isInValue) or an attribute name. - *

              - * In output, attrInfo also contains two possible new values (this is a hack to circumvent - * the lack of out-parameters in Java): - * - AttribInfo.correctedPrefix if the user has been editing an attribute value and it has - * been detected that what the user typed is different from what extractElementPrefix() - * predicted. This happens because extractElementPrefix() stops when a character that - * cannot be an element name appears whereas parseAttributeInfo() uses a grammar more - * lenient as suitable for attribute values. - * - AttribInfo.needTag will be non-zero if we find that the attribute completion proposal - * must be double-quoted. - * @param currentUiNode - * - * @return an AttributeDescriptor[] if the user is editing an attribute name. - * a String[] if the user is editing an attribute value with some known values, - * or null if nothing is known about the context. - */ - private Object[] getChoicesForAttribute(String parent, - Node currentNode, UiElementNode currentUiNode, AttribInfo attrInfo) { - Object[] choices = null; - if (attrInfo.isInValue) { - // Editing an attribute's value... Get the attribute name and then the - // possible choice for the tuple(parent,attribute) - String value = attrInfo.value; - if (value.startsWith("'") || value.startsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$ - value = value.substring(1); - // The prefix that was found at the beginning only scan for characters - // valid of tag name. We now know the real prefix for this attribute's - // value, which is needed to generate the completion choices below. - attrInfo.correctedPrefix = value; - } else { - attrInfo.needTag = '"'; - } - - if (currentUiNode != null) { - // look for an UI attribute matching the current attribute name - String attrName = attrInfo.name; - - UiAttributeNode currAttrNode = null; - for (UiAttributeNode attrNode : currentUiNode.getUiAttributes()) { - if (attrNode.getDescriptor().getXmlLocalName().equals(attrName)) { - currAttrNode = attrNode; - break; - } - } - - if (currAttrNode != null) { - choices = currAttrNode.getPossibleValues(); - - if (currAttrNode instanceof UiFlagAttributeNode) { - // A "flag" can consist of several values separated by "or" (|). - // If the correct prefix contains such a pipe character, we change - // it so that only the currently edited value is completed. - int pos = value.indexOf('|'); - if (pos >= 0) { - attrInfo.correctedPrefix = value = value.substring(pos + 1); - attrInfo.needTag = 0; - } - } - } - } - - if (choices == null) { - // fallback on the older descriptor-only based lookup. - - // in order to properly handle the special case of the name attribute in - // the action tag, we need the grandparent of the action node, to know - // what type of actions we need. - // e.g. activity -> intent-filter -> action[@name] - String greatGrandParentName = null; - Node grandParent = currentNode.getParentNode(); - if (grandParent != null) { - Node greatGrandParent = grandParent.getParentNode(); - if (greatGrandParent != null) { - greatGrandParentName = greatGrandParent.getLocalName(); - } - } - choices = FrameworkResourceManager.getInstance().getValues( - parent, attrInfo.name, greatGrandParentName); - } - } else { - // Editing an attribute's name... Get attributes valid for the parent node. - if (currentUiNode != null) { - choices = currentUiNode.getAttributeDescriptors(); - } else { - ElementDescriptor parent_desc = getDescriptor(parent); - choices = parent_desc.getAttributes(); - } - } - return choices; - } - - /** - * Gets the choices when the user is editing an XML text node. - *

              - * This means the user is editing outside of any XML element or attribute. - * Simply return the list of XML elements that can be present there, based on the - * parent of the current node. - * - * @return An ElementDescriptor[] or null. - */ - private Object[] getChoicesForTextNode(Node currentNode) { - Object[] choices = null; - String parent; - Node parent_node = currentNode.getParentNode(); - if (parent_node.getNodeType() == Node.ELEMENT_NODE) { - // We're editing a text node which parent is an element node. Limit - // content assist to elements valid for the parent. - parent = parent_node.getNodeName(); - ElementDescriptor desc = getDescriptor(parent); - if (desc != null) { - choices = desc.getChildren(); - } - } else if (parent_node.getNodeType() == Node.DOCUMENT_NODE) { - // We're editing a text node at the first level (i.e. root node). - // Limit content assist to the only valid root elements. - choices = mRootDescriptor.getChildren(); - } - return choices; - } - - /** - * Given a list of choices found, generates the proposals to be displayed to the user. - *

              - * Choices is an object array. Items of the array can be: - * - ElementDescriptor: a possible element descriptor which XML name should be completed. - * - AttributeDescriptor: a possible attribute descriptor which XML name should be completed. - * - String: string values to display as-is to the user. Typically those are possible - * values for a given attribute. - * - * @return The ICompletionProposal[] to display to the user. - */ - private ICompletionProposal[] computeProposals(int offset, Node currentNode, - Object[] choices, String wordPrefix, char need_tag, - boolean is_attribute, int selectionLength) { - ArrayList proposals = new ArrayList(); - HashMap nsUriMap = new HashMap(); - - for (Object choice : choices) { - String keyword = null; - String nsPrefix = null; - Image icon = null; - String tooltip = null; - if (choice instanceof ElementDescriptor) { - keyword = ((ElementDescriptor)choice).getXmlName(); - icon = ((ElementDescriptor)choice).getIcon(); - tooltip = DescriptorsUtils.formatTooltip(((ElementDescriptor)choice).getTooltip()); - } else if (choice instanceof TextValueDescriptor) { - continue; // Value nodes are not part of the completion choices - } else if (choice instanceof SeparatorAttributeDescriptor) { - continue; // not real attribute descriptors - } else if (choice instanceof AttributeDescriptor) { - keyword = ((AttributeDescriptor)choice).getXmlLocalName(); - icon = ((AttributeDescriptor)choice).getIcon(); - if (choice instanceof TextAttributeDescriptor) { - tooltip = ((TextAttributeDescriptor) choice).getTooltip(); - } - - String nsUri = ((AttributeDescriptor)choice).getNamespaceUri(); - nsPrefix = nsUriMap.get(nsUri); - if (nsPrefix == null) { - nsPrefix = lookupNamespacePrefix(currentNode, nsUri); - nsUriMap.put(nsUri, nsPrefix); - } - if (nsPrefix != null) { - nsPrefix += ":"; //$NON-NLS-1$ - } - - } else if (choice instanceof String) { - keyword = (String) choice; - } else { - continue; // discard unknown choice - } - - if (keyword.startsWith(wordPrefix) || - (nsPrefix != null && keyword.startsWith(nsPrefix))) { - if (nsPrefix != null) { - keyword = nsPrefix + keyword; - } - String end_tag = ""; //$NON-NLS-1$ - if (need_tag != 0) { - if (need_tag == '"') { - keyword = need_tag + keyword; - end_tag = String.valueOf(need_tag); - } else if (need_tag == '<') { - if (elementCanHaveChildren(choice)) { - end_tag = String.format(">", keyword); //$NON-NLS-1$ - keyword = need_tag + keyword; - } else { - keyword = need_tag + keyword; - end_tag = "/>"; //$NON-NLS-1$ - } - } - } - CompletionProposal proposal = new CompletionProposal( - keyword + end_tag, // String replacementString - offset - wordPrefix.length(), // int replacementOffset - wordPrefix.length() + selectionLength, // int replacementLength - keyword.length(), // int cursorPosition (rel. to rplcmntOffset) - icon, // Image image - null, // String displayString - null, // IContextInformation contextInformation - tooltip // String additionalProposalInfo - ); - - proposals.add(proposal); - } - } - - return proposals.toArray(new ICompletionProposal[proposals.size()]); - } - - /** - * Indicates whether this descriptor describes an element that can potentially - * have children (either sub-elements or text value). If an element can have children, - * we want to explicitly write an opening and a separate closing tag. - *

              - * Elements can have children if the descriptor has children element descriptors - * or if one of the attributes is a TextValueDescriptor. - * - * @param descriptor An ElementDescriptor or an AttributeDescriptor - * @return True if the descriptor is an ElementDescriptor that can have children or a text value - */ - private boolean elementCanHaveChildren(Object descriptor) { - if (descriptor instanceof ElementDescriptor) { - ElementDescriptor desc = (ElementDescriptor) descriptor; - if (desc.hasChildren()) { - return true; - } - for (AttributeDescriptor attr_desc : desc.getAttributes()) { - if (attr_desc instanceof TextValueDescriptor) { - return true; - } - } - } - return false; - } - - /** - * Returns the element descriptor matching a given XML node name or null if it can't be - * found. - *

              - * This is simplistic; ideally we should consider the parent's chain to make sure we - * can differentiate between different hierarchy trees. Right now the first match found - * is returned. - */ - private ElementDescriptor getDescriptor(String nodeName) { - return mRootDescriptor.findChildrenDescriptor(nodeName, true /* recursive */); - } - - public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset) { - return null; - } - - /** - * Returns the characters which when entered by the user should - * automatically trigger the presentation of possible completions. - * - * In our case, we auto-activate on opening tags and attributes namespace. - * - * @return the auto activation characters for completion proposal or null - * if no auto activation is desired - */ - public char[] getCompletionProposalAutoActivationCharacters() { - return new char[]{ '<', ':', '=' }; - } - - public char[] getContextInformationAutoActivationCharacters() { - return null; - } - - public IContextInformationValidator getContextInformationValidator() { - return null; - } - - public String getErrorMessage() { - return null; - } - - /** - * Heuristically extracts the prefix used for determining template relevance - * from the viewer's document. The default implementation returns the String from - * offset backwards that forms a potential XML element name. - * - * Code extracted from org.eclipse.jface.text.templatesTemplateCompletionProcessor - * and adapted to our needs. - * - * @param viewer the viewer - * @param offset offset into document - * @return the prefix to consider - */ - protected String extractElementPrefix(ITextViewer viewer, int offset) { - int i = offset; - IDocument document = viewer.getDocument(); - if (i > document.getLength()) return ""; //$NON-NLS-1$ - - try { - for (; i > 0; --i) { - char ch = document.getChar(i - 1); - // accepted characters are a-z and : (for attributes' namespace) - if (ch != ':' && (ch < 'a' || ch > 'z')) break; - } - - return document.get(i, offset - i); - } catch (BadLocationException e) { - return ""; //$NON-NLS-1$ - } - } - - /** - * Extracts the character at the given offset. - * Returns 0 if the offset is invalid. - */ - protected char extractChar(ITextViewer viewer, int offset) { - IDocument document = viewer.getDocument(); - if (offset > document.getLength()) return 0; - - try { - return document.getChar(offset); - } catch (BadLocationException e) { - return 0; - } - } - - /** - * Information about the current edit of an attribute as reported by parseAttributeInfo. - */ - private class AttribInfo { - /** True if the cursor is located in an attribute's value, false if in an attribute name */ - public boolean isInValue = false; - /** The attribute name. Null when not set. */ - public String name = null; - /** The attribute value. Null when not set. The value *may* start with a quote - * (' or "), in which case we know we don't need to quote the string for the user */ - public String value = null; - /** String typed by the user so far (i.e. right before requesting code completion), - * which will be corrected if we find a possible completion for an attribute value. - * See the long comment in getChoicesForAttribute(). */ - public String correctedPrefix = null; - /** Non-zero if an attribute value need a start/end tag (i.e. quotes or brackets) */ - public char needTag = 0; - } - - - /** - * Try to guess if the cursor is editing an element's name or an attribute following an - * element. If it's an attribute, try to find if an attribute name is being defined or - * its value. - *
              - * This is currently *only* called when we know the cursor is after a complete element - * tag name, so it should never return null. - *
              - * Reference for XML syntax: http://www.w3.org/TR/2006/REC-xml-20060816/#sec-starttags - *
              - * @return An AttribInfo describing which attribute is being edited or null if the cursor is - * not editing an attribute (in which case it must be an element's name). - */ - private AttribInfo parseAttributeInfo(ITextViewer viewer, int offset) { - AttribInfo info = new AttribInfo(); - - IDocument document = viewer.getDocument(); - int n = document.getLength(); - if (offset <= n) { - try { - n = offset; - for (;offset > 0; --offset) { - char ch = document.getChar(offset - 1); - if (ch == '<') break; - } - - // text will contain the full string of the current element, - // i.e. whatever is after the "<" to the current cursor - String text = document.get(offset, n - offset); - - // Normalize whitespace to single spaces - text = sWhitespace.matcher(text).replaceAll(" "); //$NON-NLS-1$ - - // Remove the leading element name. By spec, it must be after the < without - // any whitespace. If there's nothing left, no attribute has been defined yet. - // Be sure to keep any whitespace after the initial word if any, as it matters. - text = sFirstElementWord.matcher(text).replaceFirst(""); //$NON-NLS-1$ - - // There MUST be space after the element name. If not, the cursor is still - // defining the element name. - if (!text.startsWith(" ")) { //$NON-NLS-1$ - return null; - } - - // Remove full attributes: - // Syntax: - // name = "..." quoted string with all but < and " - // or: - // name = '...' quoted string with all but < and ' - String temp; - do { - temp = text; - text = sFirstAttribute.matcher(temp).replaceFirst(""); //$NON-NLS-1$ - } while(!temp.equals(text)); - - // Now we're left with 3 cases: - // - nothing: either there is no attribute definition or the cursor located after - // a completed attribute definition. - // - a string with no =: the user is writing an attribute name. This case can be - // merged with the previous one. - // - string with an = sign, optionally followed by a quote (' or "): the user is - // writing the value of the attribute. - int pos_equal = text.indexOf('='); - if (pos_equal == -1) { - info.isInValue = false; - info.name = text.trim(); - } else { - info.isInValue = true; - info.name = text.substring(0, pos_equal).trim(); - info.value = text.substring(pos_equal + 1).trim(); - } - return info; - } catch (BadLocationException e) { - // pass - } - } - - return null; - } - - - /** - * Returns the XML DOM node corresponding to the given offset of the given document. - */ - protected Node getNode(ITextViewer viewer, int offset) { - Node node = null; - try { - IModelManager mm = StructuredModelManager.getModelManager(); - if (mm != null) { - IStructuredModel model = mm.getExistingModelForRead(viewer.getDocument()); - if (model != null) { - for(; offset >= 0 && node == null; --offset) { - node = (Node) model.getIndexedRegion(offset); - } - } - } - } catch (Exception e) { - // Ignore exceptions. - } - - return node; - } - - /** - * Returns the active {@link AndroidEditor} matching this source viewer. - */ - private AndroidEditor getAndroidEditor(ITextViewer viewer) { - IWorkbenchWindow wwin = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (wwin != null) { - IWorkbenchPage page = wwin.getActivePage(); - if (page != null) { - IEditorPart editor = page.getActiveEditor(); - if (editor instanceof AndroidEditor) { - ISourceViewer ssviewer = ((AndroidEditor) editor).getStructuredSourceViewer(); - if (ssviewer == viewer) { - return (AndroidEditor) editor; - } - } - } - } - - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidEditor.java deleted file mode 100644 index 74eca96..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidEditor.java +++ /dev/null @@ -1,655 +0,0 @@ -/* - * Copyright (C) 2007 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.editors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.jface.dialogs.ErrorDialog; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.swt.widgets.Display; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IEditorSite; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.browser.IWorkbenchBrowserSupport; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormEditor; -import org.eclipse.ui.forms.editor.IFormPage; -import org.eclipse.ui.forms.events.HyperlinkAdapter; -import org.eclipse.ui.forms.events.HyperlinkEvent; -import org.eclipse.ui.forms.events.IHyperlinkListener; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport; -import org.eclipse.ui.part.FileEditorInput; -import org.eclipse.ui.part.MultiPageEditorPart; -import org.eclipse.ui.part.WorkbenchPart; -import org.eclipse.wst.sse.core.StructuredModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IModelManager; -import org.eclipse.wst.sse.core.internal.provisional.IModelStateListener; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.ui.StructuredTextEditor; -import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; -import org.w3c.dom.Document; - -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Multi-page form editor for Android XML files. - *

              - * It is designed to work with a {@link StructuredTextEditor} that will display an XML file. - *
              - * Derived classes must implement createFormPages to create the forms before the - * source editor. This can be a no-op if desired. - */ -public abstract class AndroidEditor extends FormEditor implements IResourceChangeListener { - - /** Preference name for the current page of this file */ - private static final String PREF_CURRENT_PAGE = "_current_page"; - - /** Id string used to create the Android SDK browser */ - private static String BROWSER_ID = "android"; // $NON-NLS-1$ - - /** Page id of the XML source editor, used for switching tabs programmatically */ - public final static String TEXT_EDITOR_ID = "editor_part"; //$NON-NLS-1$ - - /** Width hint for text fields. Helps the grid layout resize properly on smaller screens */ - public static final int TEXT_WIDTH_HINT = 50; - - /** Page index of the text editor (always the last page) */ - private int mTextPageIndex; - /** The text editor */ - private StructuredTextEditor mEditor; - /** Listener for the XML model from the StructuredEditor */ - private XmlModelStateListener mXmlModelStateListener; - - /** - * Creates a form editor. - */ - public AndroidEditor() { - super(); - ResourcesPlugin.getWorkspace().addResourceChangeListener(this); - } - - // ---- Abstract Methods ---- - - /** - * Returns the root node of the UI element hierarchy manipulated by the current - * UI node editor. - */ - abstract public UiElementNode getUiRootNode(); - - /** - * Creates the various form pages. - *

              - * Derived classes must implement this to add their own specific tabs. - */ - abstract protected void createFormPages(); - - /** - * Subclasses should override this method to process the new XML Model, which XML - * root node is given. - * - * The base implementation is empty. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - protected void xmlModelChanged(Document xml_doc) { - // pass - } - - // ---- Base Class Overrides, Interfaces Implemented ---- - - /** - * Creates the pages of the multi-page editor. - */ - @Override - protected void addPages() { - createAndroidPages(); - selectDefaultPage(null /* defaultPageId */); - } - - /** - * Creates the page for the Android Editors - */ - protected void createAndroidPages() { - createFormPages(); - createTextEditor(); - } - - /** - * Selects the default active page. - * @param defaultPageId the id of the page to show. If null the editor attempts to - * find the default page in the properties of the {@link IResource} object being edited. - */ - protected void selectDefaultPage(String defaultPageId) { - if (defaultPageId == null) { - if (getEditorInput() instanceof IFileEditorInput) { - IFile file = ((IFileEditorInput) getEditorInput()).getFile(); - - QualifiedName qname = new QualifiedName(AndroidConstants.EDITORS_PLUGIN_ID, - getClass().getSimpleName() + PREF_CURRENT_PAGE); - String pageId; - try { - pageId = file.getPersistentProperty(qname); - if (pageId != null) { - defaultPageId = pageId; - } - } catch (CoreException e) { - // ignored - } - } - } - - if (defaultPageId != null) { - try { - setActivePage(Integer.parseInt(defaultPageId)); - } catch (Exception e) { - // We can get NumberFormatException from parseInt but also - // AssertionError from setActivePage when the index is out of bounds. - // Generally speaking we just want to ignore any exception and fall back on the - // first page rather than crash the editor load. Logging the error is enough. - EditorsPlugin.log(e, "Selecting page '%s' in AndroidEditor failed", defaultPageId); - } - } - } - - /** - * Removes all the pages from the editor. - */ - protected void removePages() { - int count = getPageCount(); - for (int i = count - 1 ; i >= 0 ; i--) { - removePage(i); - } - } - - /** - * Overrides the parent's setActivePage to be able to switch to the xml editor. - * - * If the special pageId TEXT_EDITOR_ID is given, switches to the mTextPageIndex page. - * This is needed because the editor doesn't actually derive from IFormPage and thus - * doesn't have the get-by-page-id method. In this case, the method returns null since - * IEditorPart does not implement IFormPage. - */ - @Override - public IFormPage setActivePage(String pageId) { - if (pageId.equals(TEXT_EDITOR_ID)) { - super.setActivePage(mTextPageIndex); - return null; - } else { - return super.setActivePage(pageId); - } - } - - - /** - * Notifies this multi-page editor that the page with the given id has been - * activated. This method is called when the user selects a different tab. - * - * @see MultiPageEditorPart#pageChange(int) - */ - @Override - protected void pageChange(int newPageIndex) { - super.pageChange(newPageIndex); - - if (getEditorInput() instanceof IFileEditorInput) { - IFile file = ((IFileEditorInput) getEditorInput()).getFile(); - - QualifiedName qname = new QualifiedName(AndroidConstants.EDITORS_PLUGIN_ID, - getClass().getSimpleName() + PREF_CURRENT_PAGE); - try { - file.setPersistentProperty(qname, Integer.toString(newPageIndex)); - } catch (CoreException e) { - // ignore - } - } - } - - /** - * Notifies this listener that some resource changes - * are happening, or have already happened. - * - * Closes all project files on project close. - * @see IResourceChangeListener - */ - public void resourceChanged(final IResourceChangeEvent event) { - if (event.getType() == IResourceChangeEvent.PRE_CLOSE) { - Display.getDefault().asyncExec(new Runnable() { - public void run() { - IWorkbenchPage[] pages = getSite().getWorkbenchWindow() - .getPages(); - for (int i = 0; i < pages.length; i++) { - if (((FileEditorInput)mEditor.getEditorInput()) - .getFile().getProject().equals( - event.getResource())) { - IEditorPart editorPart = pages[i].findEditor(mEditor - .getEditorInput()); - pages[i].closeEditor(editorPart, true); - } - } - } - }); - } - } - - /** - * Initializes the editor part with a site and input. - *

              - * Checks that the input is an instance of {@link IFileEditorInput}. - * - * @see FormEditor - */ - @Override - public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException { - if (!(editorInput instanceof IFileEditorInput)) - throw new PartInitException("Invalid Input: Must be IFileEditorInput"); - super.init(site, editorInput); - } - - /** - * Removes attached listeners. - * - * @see WorkbenchPart - */ - @Override - public void dispose() { - IStructuredModel xml_model = getModelForRead(); - if (xml_model != null) { - try { - if (mXmlModelStateListener != null) { - xml_model.removeModelStateListener(mXmlModelStateListener); - } - - } finally { - xml_model.releaseFromRead(); - } - } - ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); - super.dispose(); - } - - /** - * Commit all dirty pages then saves the contents of the text editor. - *

              - * This works by committing all data to the XML model and then - * asking the Structured XML Editor to save the XML. - * - * @see IEditorPart - */ - @Override - public void doSave(IProgressMonitor monitor) { - commitPages(true /* onSave */); - - // The actual "save" operation is done by the Structured XML Editor - getEditor(mTextPageIndex).doSave(monitor); - } - - /* (non-Javadoc) - * Saves the contents of this editor to another object. - *

              - * Subclasses must override this method to implement the open-save-close lifecycle - * for an editor. For greater details, see IEditorPart - *

              - * - * @see IEditorPart - */ - @Override - public void doSaveAs() { - commitPages(true /* onSave */); - - IEditorPart editor = getEditor(mTextPageIndex); - editor.doSaveAs(); - setPageText(mTextPageIndex, editor.getTitle()); - setInput(editor.getEditorInput()); - } - - /** - * Commits all dirty pages in the editor. This method should - * be called as a first step of a 'save' operation. - *

              - * This is the same implementation as in {@link FormEditor} - * except it fixes two bugs: a cast to IFormPage is done - * from page.get(i) before being tested with instanceof. - * Another bug is that the last page might be a null pointer. - *

              - * The incorrect casting makes the original implementation crash due - * to our {@link StructuredTextEditor} not being an {@link IFormPage} - * so we have to override and duplicate to fix it. - * - * @param onSave true if commit is performed as part - * of the 'save' operation, false otherwise. - * @since 3.3 - */ - @Override - public void commitPages(boolean onSave) { - if (pages != null) { - for (int i = 0; i < pages.size(); i++) { - Object page = pages.get(i); - if (page != null && page instanceof IFormPage) { - IFormPage form_page = (IFormPage) page; - IManagedForm managed_form = form_page.getManagedForm(); - if (managed_form != null && managed_form.isDirty()) { - managed_form.commit(onSave); - } - } - } - } - } - - /* (non-Javadoc) - * Returns whether the "save as" operation is supported by this editor. - *

              - * Subclasses must override this method to implement the open-save-close lifecycle - * for an editor. For greater details, see IEditorPart - *

              - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return false; - } - - // ---- Local methods ---- - - - /** - * Helper method that creates a new hyper-link Listener. - * Used by derived classes which need active links in {@link FormText}. - *

              - * This link listener handles two kinds of URLs: - *

                - *
              • Links starting with "http" are simply sent to a local browser. - *
              • Links starting with "file:/" are simply sent to a local browser. - *
              • Links starting with "page:" are expected to be an editor page id to switch to. - *
              • Other links are ignored. - *
              - * - * @return A new hyper-link listener for FormText to use. - */ - public final IHyperlinkListener createHyperlinkListener() { - return new HyperlinkAdapter() { - /** - * Switch to the page corresponding to the link that has just been clicked. - * For this purpose, the HREF of the <a> tags above is the page ID to switch to. - */ - @Override - public void linkActivated(HyperlinkEvent e) { - super.linkActivated(e); - String link = e.data.toString(); - if (link.startsWith("http") || //$NON-NLS-1$ - link.startsWith("file:/")) { //$NON-NLS-1$ - openLinkInBrowser(link); - } else if (link.startsWith("page:")) { //$NON-NLS-1$ - // Switch to an internal page - setActivePage(link.substring(5 /* strlen("page:") */)); - } - } - }; - } - - /** - * Open the http link into a browser - * - * @param link The URL to open in a browser - */ - private void openLinkInBrowser(String link) { - try { - IWorkbenchBrowserSupport wbs = WorkbenchBrowserSupport.getInstance(); - wbs.createBrowser(BROWSER_ID).openURL(new URL(link)); - } catch (PartInitException e1) { - // pass - } catch (MalformedURLException e1) { - // pass - } - } - - /** - * Creates the XML source editor. - *

              - * Memorizes the index page of the source editor (it's always the last page, but the number - * of pages before can change.) - *
              - * Retrieves the underlying XML model from the StructuredEditor and attaches a listener to it. - * Finally triggers modelChanged() on the model listener -- derived classes can use this - * to initialize the model the first time. - *

              - * Called only once after createFormPages. - */ - private void createTextEditor() { - try { - mEditor = new StructuredTextEditor(); - int index = addPage(mEditor, getEditorInput()); - mTextPageIndex = index; - setPageText(index, mEditor.getTitle()); - - if (!(mEditor.getTextViewer().getDocument() instanceof IStructuredDocument)) { - Status status = new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID, - "Error opening the Android XML editor. Is the document an XML file?"); - throw new RuntimeException("Android XML Editor Error", new CoreException(status)); - } - - IStructuredModel xml_model = getModelForRead(); - if (xml_model != null) { - try { - mXmlModelStateListener = new XmlModelStateListener(); - xml_model.addModelStateListener(mXmlModelStateListener); - mXmlModelStateListener.modelChanged(xml_model); - } finally { - xml_model.releaseFromRead(); - } - } - } catch (PartInitException e) { - ErrorDialog.openError(getSite().getShell(), - "Android XML Editor Error", null, e.getStatus()); - } - } - - /** - * Returns the ISourceViewer associated with the Structured Text editor. - */ - public final ISourceViewer getStructuredSourceViewer() { - if (mEditor != null) { - // We can't access mEditor.getSourceViewer() because it is protected, - // however getTextViewer simply returns the SourceViewer casted, so we - // can use it instead. - return mEditor.getTextViewer(); - } - return null; - } - - /** - * Returns the {@link IStructuredDocument} used by the StructuredTextEditor (aka Source - * Editor) or null if not available. - */ - public final IStructuredDocument getStructuredDocument() { - if (mEditor != null && mEditor.getTextViewer() != null) { - return (IStructuredDocument) mEditor.getTextViewer().getDocument(); - } - return null; - } - - /** - * Returns a version of the model that has been shared for read. - *

              - * Callers must call model.releaseFromRead() when done, typically - * in a try..finally clause. - * - * @return The model for the XML document or null if cannot be obtained from the editor - */ - public final IStructuredModel getModelForRead() { - IStructuredDocument document = getStructuredDocument(); - if (document != null) { - IModelManager mm = StructuredModelManager.getModelManager(); - if (mm != null) { - return mm.getModelForRead(document); - } - } - return null; - } - - /** - * Returns a version of the model that has been shared for edit. - *

              - * Callers must call model.releaseFromEdit() when done, typically - * in a try..finally clause. - * - * @return The model for the XML document or null if cannot be obtained from the editor - */ - public final IStructuredModel getModelForEdit() { - IStructuredDocument document = getStructuredDocument(); - if (document != null) { - IModelManager mm = StructuredModelManager.getModelManager(); - if (mm != null) { - return mm.getModelForEdit(document); - } - } - return null; - } - - /** - * Helper class to perform edits on the XML model whilst making sure the - * model has been prepared to be changed. - * - * It first gets a model for edition, then calls aboutToChangeModel, then performs the - * requested action and finally calls changedModel and releaseFromEdit. - * - * The method is synchronous. As soon as the changedModel method is called, XML model - * listeners will be triggered. - * - * @param edit_action Something that will change - */ - public final void editXmlModel(Runnable edit_action) { - IStructuredModel model = getModelForEdit(); - try { - model.aboutToChangeModel(); - edit_action.run(); - } finally { - // Notify the model we're done modifying it. This must *always* be executed. - model.changedModel(); - model.releaseFromEdit(); - } - } - - /** - * Returns the XML {@link Document} or null if we can't get it - */ - protected final Document getXmlDocument(IStructuredModel model) { - if (model == null) { - EditorsPlugin.log(IStatus.WARNING, "Android Editor: No XML model for root node."); //$NON-NLS-1$ - return null; - } - - if (model instanceof IDOMModel) { - IDOMModel dom_model = (IDOMModel) model; - return dom_model.getDocument(); - } - return null; - } - - /** - * Listen to changes in the underlying XML model in the structured editor. - */ - private class XmlModelStateListener implements IModelStateListener { - - /** - * A model is about to be changed. This typically is initiated by one - * client of the model, to signal a large change and/or a change to the - * model's ID or base Location. A typical use might be if a client might - * want to suspend processing until all changes have been made. - *

              - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelAboutToBeChanged(IStructuredModel model) { - // pass - } - - /** - * Signals that the changes foretold by modelAboutToBeChanged have been - * made. A typical use might be to refresh, or to resume processing that - * was suspended as a result of modelAboutToBeChanged. - *

              - * This AndroidEditor implementation calls the xmlModelChanged callback. - */ - public void modelChanged(IStructuredModel model) { - xmlModelChanged(getXmlDocument(model)); - } - - /** - * Notifies that a model's dirty state has changed, and passes that state - * in isDirty. A model becomes dirty when any change is made, and becomes - * not-dirty when the model is saved. - *

              - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelDirtyStateChanged(IStructuredModel model, boolean isDirty) { - // pass - } - - /** - * A modelDeleted means the underlying resource has been deleted. The - * model itself is not removed from model management until all have - * released it. Note: baseLocation is not (necessarily) changed in this - * event, but may not be accurate. - *

              - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelResourceDeleted(IStructuredModel model) { - // pass - } - - /** - * A model has been renamed or copied (as in saveAs..). In the renamed - * case, the two paramenters are the same instance, and only contain the - * new info for id and base location. - *

              - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelResourceMoved(IStructuredModel oldModel, IStructuredModel newModel) { - // pass - } - - /** - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelAboutToBeReinitialized(IStructuredModel structuredModel) { - // pass - } - - /** - * This AndroidEditor implementation of IModelChangedListener is empty. - */ - public void modelReinitialized(IStructuredModel structuredModel) { - // pass - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java deleted file mode 100644 index ab17bef..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/AndroidSourceViewerConfig.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2007 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.editors; - - -import org.eclipse.jface.text.IAutoEditStrategy; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.ITextHover; -import org.eclipse.jface.text.contentassist.IContentAssistProcessor; -import org.eclipse.jface.text.contentassist.IContentAssistant; -import org.eclipse.jface.text.formatter.IContentFormatter; -import org.eclipse.jface.text.source.ISourceViewer; -import org.eclipse.jface.viewers.IInputProvider; -import org.eclipse.wst.sse.core.text.IStructuredPartitions; -import org.eclipse.wst.xml.core.text.IXMLPartitions; -import org.eclipse.wst.xml.ui.StructuredTextViewerConfigurationXML; - -import java.util.ArrayList; - -/** - * Base Source Viewer Configuration for Android resources. - */ -public class AndroidSourceViewerConfig extends StructuredTextViewerConfigurationXML { - - /** Content Assist Processor to use for all handled partitions. */ - private IContentAssistProcessor mProcessor; - - public AndroidSourceViewerConfig(IContentAssistProcessor processor) { - super(); - mProcessor = processor; - } - - @Override - public IContentAssistant getContentAssistant(ISourceViewer sourceViewer) { - return super.getContentAssistant(sourceViewer); - } - - /** - * Returns the content assist processors that will be used for content - * assist in the given source viewer and for the given partition type. - * - * @param sourceViewer the source viewer to be configured by this - * configuration - * @param partitionType the partition type for which the content assist - * processors are applicable - * @return IContentAssistProcessors or null if should not be supported - */ - @Override - protected IContentAssistProcessor[] getContentAssistProcessors( - ISourceViewer sourceViewer, String partitionType) { - ArrayList processors = new ArrayList(); - if (partitionType == IStructuredPartitions.UNKNOWN_PARTITION || - partitionType == IStructuredPartitions.DEFAULT_PARTITION || - partitionType == IXMLPartitions.XML_DEFAULT) { - if (sourceViewer instanceof IInputProvider) { - IInputProvider input = (IInputProvider) sourceViewer; - Object a = input.getInput(); - if (a != null) - a.toString(); - } - - IDocument doc = sourceViewer.getDocument(); - if (doc != null) - doc.toString(); - - processors.add(mProcessor); - } - - IContentAssistProcessor[] others = super.getContentAssistProcessors(sourceViewer, - partitionType); - if (others != null && others.length > 0) { - for (IContentAssistProcessor p : others) { - processors.add(p); - } - } - - if (processors.size() > 0) { - return processors.toArray(new IContentAssistProcessor[processors.size()]); - } else { - return null; - } - } - - @Override - public ITextHover getTextHover(ISourceViewer sourceViewer, String contentType) { - // TODO text hover for android xml - return super.getTextHover(sourceViewer, contentType); - } - - @Override - public IAutoEditStrategy[] getAutoEditStrategies( - ISourceViewer sourceViewer, String contentType) { - // TODO auto edit strategies for android xml - return super.getAutoEditStrategies(sourceViewer, contentType); - } - - @Override - public IContentFormatter getContentFormatter(ISourceViewer sourceViewer) { - // TODO content formatter for android xml - return super.getContentFormatter(sourceViewer); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/EditorsPlugin.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/EditorsPlugin.java deleted file mode 100644 index 354276a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/EditorsPlugin.java +++ /dev/null @@ -1,672 +0,0 @@ -/* - * Copyright (C) 2007 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.editors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.CommonPlugin; -import com.android.ide.eclipse.common.SdkStatsHelper; -import com.android.ide.eclipse.common.StreamHelper; -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.EditorsPlugin.LayoutBridge.LoadStatus; -import com.android.ide.eclipse.editors.layout.LayoutEditor; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.menu.MenuEditor; -import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; -import com.android.ide.eclipse.editors.resources.ResourcesEditor; -import com.android.ide.eclipse.editors.resources.manager.ProjectResources; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; -import com.android.ide.eclipse.editors.xml.XmlEditor; -import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; -import com.android.layoutlib.api.ILayoutBridge; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.SubMonitor; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IEditorDescriptor; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.console.MessageConsole; -import org.eclipse.ui.console.MessageConsoleStream; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.part.FileEditorInput; -import org.eclipse.ui.plugin.AbstractUIPlugin; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.Version; - -import java.io.File; -import java.io.OutputStream; -import java.lang.reflect.Constructor; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; - -/** - * The activator class controls the plug-in life cycle - */ -public class EditorsPlugin extends AbstractUIPlugin { - // The shared instance - private static EditorsPlugin sPlugin; - - private static Image sAndroidLogo; - private static ImageDescriptor sAndroidLogoDesc; - - private ResourceMonitor mResourceMonitor; - private SdkPathChangedListener mSdkPathChangedListener; - private ArrayList mResourceRefreshListener = new ArrayList(); - - private MessageConsoleStream mAndroidConsoleStream; - /** Stream to write error messages to the android console */ - private MessageConsoleStream mAndroidConsoleErrorStream; - - public final static class LayoutBridge { - public enum LoadStatus { LOADING, LOADED, FAILED } - - /** Link to the layout bridge */ - public ILayoutBridge bridge; - - public LoadStatus status = LoadStatus.LOADING; - } - - private final LayoutBridge mLayoutBridge = new LayoutBridge(); - - private boolean mLayoutBridgeInit; - - private ClassLoader mBridgeClassLoader; - - private Color mRed; - - - /** - * The constructor - */ - public EditorsPlugin() { - } - - /** - * The AbstractUIPlugin implementation of this Plugin - * method refreshes the plug-in actions. Subclasses may extend this method, - * but must send super first. - * {@inheritDoc} - * - * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) - */ - @Override - public void start(BundleContext context) throws Exception { - super.start(context); - sPlugin = this; - sAndroidLogoDesc = imageDescriptorFromPlugin(AndroidConstants.EDITORS_PLUGIN_ID, - "/icons/android.png"); //$NON-NLS-1$ - sAndroidLogo = sAndroidLogoDesc.createImage(); - - // get the stream to write in the android console. - MessageConsole androidConsole = CommonPlugin.getDefault().getAndroidConsole(); - mAndroidConsoleStream = androidConsole.newMessageStream(); - - mAndroidConsoleErrorStream = androidConsole.newMessageStream(); - mRed = new Color(getDisplay(), 0xFF, 0x00, 0x00); - - // because this can be run, in some cases, by a non ui thread, and beccause - // changing the console properties update the ui, we need to make this change - // in the ui thread. - getDisplay().asyncExec(new Runnable() { - public void run() { - mAndroidConsoleErrorStream.setColor(mRed); - } - }); - - // Add a resource listener to handle compiled resources. - IWorkspace ws = ResourcesPlugin.getWorkspace(); - mResourceMonitor = ResourceMonitor.startMonitoring(ws); - - if (mResourceMonitor != null) { - setupDefaultEditor(mResourceMonitor); - ResourceManager.setup(mResourceMonitor); - } - - // Setup the sdk location changed listener and invoke it the first time - mSdkPathChangedListener = new SdkPathChangedListener(); - FrameworkResourceManager.getInstance().addFrameworkResourcesChangeListener( - mSdkPathChangedListener); - - // ping the usage server - pingUsageServer(); - } - - /** - * The AbstractUIPlugin implementation of this Plugin - * method saves this plug-in's preference and dialog stores and shuts down - * its image registry (if they are in use). Subclasses may extend this - * method, but must send super last. A try-finally statement should - * be used where necessary to ensure that super.shutdown() is - * always done. - * - * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) - */ - @Override - public void stop(BundleContext context) throws Exception { - sPlugin = null; - sAndroidLogo.dispose(); - - IconFactory.getInstance().Dispose(); - - // Remove the resource listener that handles compiled resources. - IWorkspace ws = ResourcesPlugin.getWorkspace(); - ResourceMonitor.stopMonitoring(ws); - - FrameworkResourceManager.getInstance().removeFrameworkResourcesChangeListener( - mSdkPathChangedListener); - mSdkPathChangedListener = null; - - mRed.dispose(); - - super.stop(context); - } - - /** - * Returns the shared instance - * - * @return the shared instance - */ - public static EditorsPlugin getDefault() { - return sPlugin; - } - - /** - * Returns an Image for the small Android logo. - * - * Callers should not dispose it. - */ - public static Image getAndroidLogo() { - return sAndroidLogo; - } - - /** - * Returns an {@link ImageDescriptor} for the small Android logo. - * - * Callers should not dispose it. - */ - public static ImageDescriptor getAndroidLogoDesc() { - return sAndroidLogoDesc; - } - - /** - * Logs a message to the default Eclipse log. - * - * @param severity One of IStatus' severity codes: OK, ERROR, INFO, WARNING or CANCEL. - * @param format The format string, like for String.format(). - * @param args The arguments for the format string, like for String.format(). - */ - public static void log(int severity, String format, Object ... args) { - String message = String.format(format, args); - Status status = new Status(severity, AndroidConstants.EDITORS_PLUGIN_ID, message); - getDefault().getLog().log(status); - } - - /** - * Logs an exception to the default Eclipse log. - *

              - * The status severity is always set to ERROR. - * - * @param exception The exception to log. Its call trace will be recorded. - * @param format The format string, like for String.format(). - * @param args The arguments for the format string, like for String.format(). - */ - public static void log(Throwable exception, String format, Object ... args) { - String message = String.format(format, args); - Status status = new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID, - message, exception); - getDefault().getLog().log(status); - } - - /** - * Returns the ResourceMonitor object. - */ - public ResourceMonitor getResourceMonitor() { - return mResourceMonitor; - } - - - /** - * Sets up the editor to register default editors for resource files when needed. - * - * This is called by the {@link EditorsPlugin} during initialization. - * - * @param monitor The main Resource Monitor object. - */ - public void setupDefaultEditor(ResourceMonitor monitor) { - monitor.addFileListener(new IFileListener() { - - private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$ - - /* (non-Javadoc) - * Sent when a file changed. - * @param file The file that changed. - * @param markerDeltas The marker deltas for the file. - * @param kind The change kind. This is equivalent to - * {@link IResourceDelta#accept(IResourceDeltaVisitor)} - * - * @see IFileListener#fileChanged - */ - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { - // The resources files must have a file path similar to - // project/res/.../*.xml - // There is no support for sub folders, so the segment count must be 4 - if (file.getFullPath().segmentCount() == 4) { - // check if we are inside the res folder. - String segment = file.getFullPath().segment(1); - if (segment.equalsIgnoreCase(AndroidConstants.FD_RESOURCES)) { - // we are inside a res/ folder, get the actual ResourceFolder - ProjectResources resources = ResourceManager.getInstance(). - getProjectResources(file.getProject()); - - // This happens when importing old Android projects in Eclipse - // that lack the container (probably because resources fail to build - // properly.) - if (resources == null) { - log(IStatus.INFO, - "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$ - file.getFullPath().toOSString(), - file.getProject().getName()); - return; - } - - ResourceFolder resFolder = resources.getResourceFolder( - (IFolder)file.getParent()); - - if (resFolder != null) { - if (kind == IResourceDelta.ADDED) { - resourceAdded(file, resFolder.getType()); - } else if (kind == IResourceDelta.CHANGED) { - resourceChanged(file, resFolder.getType()); - } - } else { - // if the res folder is null, this means the name is invalid, - // in this case we remove whatever android editors that was set - // as the default editor. - IEditorDescriptor desc = IDE.getDefaultEditor(file); - String editorId = desc.getId(); - if (editorId.startsWith(AndroidConstants.EDITORS_PLUGIN_ID)) { - // reset the default editor. - IDE.setDefaultEditor(file, null); - } - } - } - } - } - } - - private void resourceAdded(IFile file, ResourceFolderType type) { - // set the default editor based on the type. - if (type == ResourceFolderType.LAYOUT) { - IDE.setDefaultEditor(file, LayoutEditor.ID); - } else if (type == ResourceFolderType.DRAWABLE - || type == ResourceFolderType.VALUES) { - IDE.setDefaultEditor(file, ResourcesEditor.ID); - } else if (type == ResourceFolderType.MENU) { - IDE.setDefaultEditor(file, MenuEditor.ID); - } else if (type == ResourceFolderType.XML) { - if (XmlEditor.canHandleFile(file)) { - IDE.setDefaultEditor(file, XmlEditor.ID); - } else { - // set a property to determine later if the XML can be handled - QualifiedName qname = new QualifiedName( - AndroidConstants.EDITORS_PLUGIN_ID, - UNKNOWN_EDITOR); - try { - file.setPersistentProperty(qname, "1"); - } catch (CoreException e) { - // pass - } - } - } - } - - private void resourceChanged(IFile file, ResourceFolderType type) { - if (type == ResourceFolderType.XML) { - IEditorDescriptor ed = IDE.getDefaultEditor(file); - if (ed == null || ed.getId() != XmlEditor.ID) { - QualifiedName qname = new QualifiedName( - AndroidConstants.EDITORS_PLUGIN_ID, - UNKNOWN_EDITOR); - String prop = null; - try { - prop = file.getPersistentProperty(qname); - } catch (CoreException e) { - // pass - } - if (prop != null && XmlEditor.canHandleFile(file)) { - try { - // remove the property & set editor - file.setPersistentProperty(qname, null); - IWorkbenchPage page = PlatformUI.getWorkbench(). - getActiveWorkbenchWindow().getActivePage(); - - IEditorPart oldEditor = page.findEditor(new FileEditorInput(file)); - if (oldEditor != null && - CommonPlugin.displayPrompt("Android XML Editor", - String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?", - file.getFullPath()))) { - IDE.setDefaultEditor(file, XmlEditor.ID); - IEditorPart newEditor = page.openEditor( - new FileEditorInput(file), - XmlEditor.ID, - true, /* activate */ - IWorkbenchPage.MATCH_NONE); - - if (newEditor != null) { - page.closeEditor(oldEditor, true /* save */); - } - } - } catch (CoreException e) { - // setPersistentProperty or page.openEditor may have failed - } - } - } - } - } - - }, IResourceDelta.ADDED | IResourceDelta.CHANGED); - } - - /** - * Respond to notifications from the resource manager than the SDK resources have been updated. - * It gets the new resources from the {@link FrameworkResourceManager} and then try to update - * the layout descriptors from the new layout data. - */ - private class SdkPathChangedListener implements Runnable { - public void run() { - - // Perform the update in a thread (here an Eclipse runtime job) - // since this should never block the caller (especially the start method) - new Job("Editors: Load Framework Resource Parser") { - - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - SubMonitor progress = SubMonitor.convert(monitor, "Update Description", - 60); - - FrameworkResourceManager resourceManager = FrameworkResourceManager.getInstance(); - - AndroidManifestDescriptors.updateDescriptors( - resourceManager.getManifestDefinitions()); - progress.worked(10); - - if (progress.isCanceled()) { - return Status.CANCEL_STATUS; - } - - LayoutDescriptors.getInstance().updateDescriptors( - resourceManager.getLayoutViewsInfo(), - resourceManager.getLayoutGroupsInfo()); - progress.worked(10); - - if (progress.isCanceled()) { - return Status.CANCEL_STATUS; - } - - MenuDescriptors.getInstance().updateDescriptors( - resourceManager.getXmlMenuDefinitions()); - progress.worked(10); - - if (progress.isCanceled()) { - return Status.CANCEL_STATUS; - } - - XmlDescriptors.getInstance().updateDescriptors( - resourceManager.getXmlSearchableDefinitions(), - resourceManager.getPreferencesInfo(), - resourceManager.getPreferenceGroupsInfo()); - progress.worked(10); - - // load the layout lib bridge. - if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) { - loadLayoutBridge(); - FrameworkResourceManager frMgr = FrameworkResourceManager.getInstance(); - ResourceManager rMgr = ResourceManager.getInstance(); - rMgr.loadFrameworkResources(frMgr.getFrameworkResourcesLocation()); - } - progress.worked(10); - - // Notify resource changed listeners - progress.subTask("Refresh UI"); - progress.setWorkRemaining(mResourceRefreshListener.size()); - - // Clone the list before iterating, to avoid Concurrent Modification - // exceptions - @SuppressWarnings("unchecked") - ArrayList listeners = (ArrayList) - mResourceRefreshListener.clone(); - for (Runnable listener : listeners) { - try { - getDisplay().syncExec(listener); - } catch (Exception e) { - log(e, "ResourceRefreshListener Failed"); //$NON-NLS-1$ - } finally { - progress.worked(1); - } - } - } catch (Throwable e) { - EditorsPlugin.log(e, "Load Framework Resource Parser failed"); //$NON-NLS-1$ - EditorsPlugin.printToConsole("Framework Resource Parser", - "Failed to parse"); - } finally { - if (monitor != null) { - monitor.done(); - } - } - return Status.OK_STATUS; - } - }.schedule(); - } - } - - public void addResourceChangedListener(Runnable resourceRefreshListener) { - mResourceRefreshListener.add(resourceRefreshListener); - } - - public void removeResourceChangedListener(Runnable resourceRefreshListener) { - mResourceRefreshListener.remove(resourceRefreshListener); - } - - public static Display getDisplay() { - IWorkbench bench = sPlugin.getWorkbench(); - if (bench != null) { - return bench.getDisplay(); - } - return null; - } - - /** - * Pings the usage start server. - */ - private void pingUsageServer() { - // In order to not block the plugin loading, so we spawn another thread. - new Thread("Ping!") { //$NON-NLS-1$ - @Override - public void run() { - // get the version of the plugin - String versionString = (String) getBundle().getHeaders().get( - Constants.BUNDLE_VERSION); - Version version = new Version(versionString); - - SdkStatsHelper.pingUsageServer("editors", version); //$NON-NLS-1$ - } - }.start(); - - } - - /** - * Prints one or more message to the android console. - * @param tag The tag to be associated with the message. Can be null. - * @param objects the objects to print through their toString method. - */ - public static synchronized void printToConsole(String tag, Object... objects) { - StreamHelper.printToStream(sPlugin.mAndroidConsoleStream, tag, objects); - } - - /** - * Prints one or more error messages to the android console. - * @param tag The tag to be associated with the message. Can be null. - * @param objects the objects to print through their toString method. - */ - public static synchronized void printErrorToConsole(String tag, Object... objects) { - StreamHelper.printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects); - } - - public static synchronized OutputStream getErrorStream() { - return sPlugin.mAndroidConsoleErrorStream; - } - - /** - * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread, - * therefore this method can be called from any thread. - * @param title The title of the dialog box - * @param message The error message - */ - public final static void displayError(final String title, final String message) { - // get the current Display - final Display display = getDisplay(); - - // dialog box only run in ui thread.. - display.asyncExec(new Runnable() { - public void run() { - Shell shell = display.getActiveShell(); - MessageDialog.openError(shell, title, message); - } - }); - } - - /** - * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread, - * therefore this message can be called from any thread. - * @param title The title of the dialog box - * @param message The error message - * @return true if OK was clicked. - */ - public final static boolean displayPrompt(final String title, final String message) { - // get the current Display and Shell - final Display display = getDisplay(); - - // we need to ask the user what he wants to do. - final boolean[] wrapper = new boolean[] { false }; - display.syncExec(new Runnable() { - public void run() { - Shell shell = display.getActiveShell(); - wrapper[0] = MessageDialog.openQuestion(shell, title, message); - } - }); - return wrapper[0]; - } - - /** - * Returns a {@link LayoutBridge} object possibly containing a {@link ILayoutBridge} object. - *

              If {@link LayoutBridge#bridge} is null, {@link LayoutBridge#status} will - * contain the reason (either {@link LoadStatus#LOADING} or {@link LoadStatus#FAILED}). - *

              Valid {@link ILayoutBridge} objects are always initialized before being returned. - */ - public synchronized LayoutBridge getLayoutBridge() { - if (mLayoutBridgeInit == false && mLayoutBridge.bridge != null) { - FrameworkResourceManager manager = FrameworkResourceManager.getInstance(); - mLayoutBridge.bridge.init( - manager.getFrameworkFontLocation() + AndroidConstants.FD_DEFAULT_RES, - manager.getEnumValueMap()); - mLayoutBridgeInit = true; - } - return mLayoutBridge; - } - - /** - * Loads the layout bridge from the dynamically loaded layoutlib.jar - */ - private void loadLayoutBridge() { - try { - // reset to be sure we won't be using an obsolete version if we - // get an exception somewhere. - mLayoutBridge.bridge = null; - mLayoutBridge.status = LayoutBridge.LoadStatus.LOADING; - - // get the URL for the file. - File f = new File( - FrameworkResourceManager.getInstance().getLayoutLibLocation()); - if (f.isFile() == false) { - log(IStatus.ERROR, "layoutlib.jar is missing!"); //$NON-NLS-1$ - } else { - URL url = f.toURL(); - - // create a class loader. Because this jar reference interfaces - // that are in the editors plugin, it's important to provide - // a parent class loader. - mBridgeClassLoader = new URLClassLoader(new URL[] { url }, - this.getClass().getClassLoader()); - - // load the class - Class clazz = mBridgeClassLoader.loadClass(AndroidConstants.CLASS_BRIDGE); - if (clazz != null) { - // instantiate an object of the class. - Constructor constructor = clazz.getConstructor(); - if (constructor != null) { - Object bridge = constructor.newInstance(); - if (bridge instanceof ILayoutBridge) { - mLayoutBridge.bridge = (ILayoutBridge)bridge; - } - } - } - - if (mLayoutBridge.bridge == null) { - mLayoutBridge.status = LayoutBridge.LoadStatus.FAILED; - log(IStatus.ERROR, "Failed to load " + AndroidConstants.CLASS_BRIDGE); //$NON-NLS-1$ - } else { - mLayoutBridge.status = LayoutBridge.LoadStatus.LOADED; - } - } - } catch (Throwable t) { - mLayoutBridge.status = LayoutBridge.LoadStatus.FAILED; - // log the error. - log(t, "Failed to load the LayoutLib"); - } - } - - public ClassLoader getLayoutlibBridgeClassLoader() { - return mBridgeClassLoader; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/FirstElementParser.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/FirstElementParser.java deleted file mode 100644 index bb0996b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/FirstElementParser.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.editors; - -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Quickly parses a (potential) XML file to extract its first element (i.e. the root element) - * and namespace, if any. - *

              - * This is used to determine if a file is an XML document that the XmlEditor can process. - *

              - * TODO use this to remove the hardcoded "android" namespace prefix limitation. - */ -public final class FirstElementParser { - - private static SAXParserFactory sSaxfactory; - - /** - * Result from the XML parsing.
              - * Contains the name of the root XML element.
              - * If an XMLNS URI was specified and found, the XMLNS prefix is recorded. Otherwise it is null. - */ - public static final class Result { - private String mElement; - private String mXmlnsPrefix; - private String mXmlnsUri; - - public String getElement() { - return mElement; - } - - public String getXmlnsPrefix() { - return mXmlnsPrefix; - } - - public String getXmlnsUri() { - return mXmlnsUri; - } - - void setElement(String element) { - mElement = element; - } - - void setXmlnsPrefix(String xmlnsPrefix) { - mXmlnsPrefix = xmlnsPrefix; - } - - void setXmlnsUri(String xmlnsUri) { - mXmlnsUri = xmlnsUri; - } - } - - private static class ResultFoundException extends SAXException { } - - /** - * Parses the given filename. - * - * @param osFilename The file to parse. - * @param xmlnsUri An optional URL of which we want to know the prefix. - * @return The element details found or null if not found. - */ - public static Result parse(String osFilename, String xmlnsUri) { - if (sSaxfactory == null) { - // TODO just create a single factory in CommonPlugin and reuse it - sSaxfactory = SAXParserFactory.newInstance(); - sSaxfactory.setNamespaceAware(true); - } - - Result result = new Result(); - if (xmlnsUri != null && xmlnsUri.length() > 0) { - result.setXmlnsUri(xmlnsUri); - } - - try { - SAXParser parser = sSaxfactory.newSAXParser(); - XmlHandler handler = new XmlHandler(result); - parser.parse(new InputSource(new FileReader(osFilename)), handler); - - } catch(ResultFoundException e) { - // XML handling was aborted because the required element was found. - // Simply return the result. - return result; - } catch (ParserConfigurationException e) { - } catch (SAXException e) { - } catch (FileNotFoundException e) { - } catch (IOException e) { - } - - return null; - } - - /** - * Private constructor. Use the static parse() method instead. - */ - private FirstElementParser() { - // pass - } - - /** - * A specialized SAX handler that captures the arguments of the very first element - * (i.e. the root element) - */ - private static class XmlHandler extends DefaultHandler { - private final Result mResult; - - public XmlHandler(Result result) { - mResult = result; - } - - /** - * Processes a namespace prefix mapping. - * I.e. for xmlns:android="some-uri", this received prefix="android" and uri="some-uri". - *

              - * The prefix is recorded in the result structure if the URI is the one searched for. - *

              - * This event happens before the corresponding startElement event. - */ - @Override - public void startPrefixMapping(String prefix, String uri) { - if (uri.equals(mResult.getXmlnsUri())) { - mResult.setXmlnsPrefix(prefix); - } - } - - /** - * Processes a new element start. - *

              - * This simply records the element name and abort processing by throwing an exception. - */ - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) - throws SAXException { - mResult.setElement(localName); - throw new ResultFoundException(); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/IconFactory.java deleted file mode 100644 index 9e3b733..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/IconFactory.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.editors; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontData; -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.widgets.Display; - -import java.util.HashMap; - -/** - * Factory to generate icons for Android Editors. - *

              - * Icons are kept here and reused. - */ -public class IconFactory { - - public static final int COLOR_RED = SWT.COLOR_DARK_RED; - public static final int COLOR_GREEN = SWT.COLOR_DARK_GREEN; - public static final int COLOR_BLUE = SWT.COLOR_DARK_BLUE; - public static final int COLOR_DEFAULT = SWT.COLOR_BLACK; - - public static final int SHAPE_CIRCLE = 'C'; - public static final int SHAPE_RECT = 'R'; - public static final int SHAPE_DEFAULT = SHAPE_CIRCLE; - - private static IconFactory sInstance; - - private HashMap mIconMap = new HashMap(); - private HashMap mImageDescMap = new HashMap(); - - private IconFactory() { - } - - public static synchronized IconFactory getInstance() { - if (sInstance == null) { - sInstance = new IconFactory(); - } - return sInstance; - } - - public void Dispose() { - // Dispose icons - for (Image icon : mIconMap.values()) { - // The map can contain null values - if (icon != null) { - icon.dispose(); - } - } - mIconMap.clear(); - } - - /** - * Returns an Image for a given icon name. - *

              - * Callers should not dispose it. - * - * @param osName The leaf name, without the extension, of an existing icon in the - * editor's "icons" directory. If it doesn't exists, a default icon will be - * generated automatically based on the name. - */ - public Image getIcon(String osName) { - return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT); - } - - /** - * Returns an Image for a given icon name. - *

              - * Callers should not dispose it. - * - * @param osName The leaf name, without the extension, of an existing icon in the - * editor's "icons" directory. If it doesn't exists, a default icon will be - * generated automatically based on the name. - * @param color The color of the text in the automatically generated icons, - * one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED. - * @param shape The shape of the icon in the automatically generated icons, - * one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT. - */ - public Image getIcon(String osName, int color, int shape) { - EditorsPlugin plugin = EditorsPlugin.getDefault(); - - String key = Character.toString((char) shape) + Integer.toString(color) + osName; - Image icon = mIconMap.get(key); - if (icon == null && !mIconMap.containsKey(key)) { - ImageDescriptor id = getImageDescriptor(osName, color, shape); - if (id != null) { - icon = id.createImage(); - } - // Note that we store null references in the icon map, to avoid looking them - // up every time. If it didn't exist once, it will not exist later. - mIconMap.put(key, icon); - } - return icon; - } - - /** - * Returns an ImageDescriptor for a given icon name. - *

              - * Callers should not dispose it. - * - * @param osName The leaf name, without the extension, of an existing icon in the - * editor's "icons" directory. If it doesn't exists, a default icon will be - * generated automatically based on the name. - */ - public ImageDescriptor getImageDescriptor(String osName) { - return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT); - } - - /** - * Returns an ImageDescriptor for a given icon name. - *

              - * Callers should not dispose it. - * - * @param osName The leaf name, without the extension, of an existing icon in the - * editor's "icons" directory. If it doesn't exists, a default icon will be - * generated automatically based on the name. - * @param color The color of the text in the automatically generated icons. - * one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED. - * @param shape The shape of the icon in the automatically generated icons, - * one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT. - */ - public ImageDescriptor getImageDescriptor(String osName, int color, int shape) { - EditorsPlugin plugin = EditorsPlugin.getDefault(); - - String key = Character.toString((char) shape) + Integer.toString(color) + osName; - ImageDescriptor id = mImageDescMap.get(key); - if (id == null && !mImageDescMap.containsKey(key)) { - id = plugin.imageDescriptorFromPlugin( - AndroidConstants.EDITORS_PLUGIN_ID, - String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$ - - if (id == null) { - id = new LetterImageDescriptor(osName.charAt(0), color, shape); - } - - // Note that we store null references in the icon map, to avoid looking them - // up every time. If it didn't exist once, it will not exist later. - mImageDescMap.put(key, id); - } - return id; - } - - /** - * A simple image description that generates a 16x16 image which consists - * of a colored letter inside a black & white circle. - */ - private static class LetterImageDescriptor extends ImageDescriptor { - - private final char mLetter; - private final int mColor; - private final int mShape; - - public LetterImageDescriptor(char letter, int color, int shape) { - mLetter = letter; - mColor = color; - mShape = shape; - } - - @Override - public ImageData getImageData() { - - final int SX = 15; - final int SY = 15; - final int RX = 4; - final int RY = 4; - - Display display = Display.getCurrent(); - if (display == null) { - return null; - } - - Image image = new Image(display, SX, SY); - - image.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); - - GC gc = new GC(image); - gc.setAdvanced(true); - gc.setAntialias(SWT.ON); - gc.setTextAntialias(SWT.ON); - - gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE)); - if (mShape == SHAPE_CIRCLE) { - gc.fillOval(0, 0, SX - 1, SY - 1); - } else if (mShape == SHAPE_RECT) { - gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY); - } - - gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK)); - gc.setLineWidth(1); - if (mShape == SHAPE_CIRCLE) { - gc.drawOval(0, 0, SX - 1, SY - 1); - } else if (mShape == SHAPE_RECT) { - gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY); - } - - // Get a bold version of the default system font, if possible. - Font font = display.getSystemFont(); - FontData[] fds = font.getFontData(); - fds[0].setStyle(SWT.BOLD); - // use 3/4th of the circle diameter for the font size (in pixels) - // and convert it to "font points" (font points in SWT are hardcoded in an - // arbitrary 72 dpi and then converted in real pixels using whatever is - // indicated by getDPI -- at least that's how it works under Win32). - fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y)); - // Note: win32 implementation always uses fds[0] so we change just that one. - // getFontData indicates that the array of fd is really an unusual thing for X11. - font = new Font(display, fds); - gc.setFont(font); - gc.setForeground(display.getSystemColor(mColor)); - - // Text measurement varies so slightly depending on the platform - int ofx = 0; - int ofy = 0; - if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_WINDOWS) { - ofx = +1; - ofy = -1; - } - - String s = Character.toString(mLetter).toUpperCase(); - Point p = gc.textExtent(s); - int tx = (SX + ofx - p.x) / 2; - int ty = (SY + ofy - p.y) / 2; - gc.drawText(s, tx, ty, true /* isTransparent */); - - font.dispose(); - gc.dispose(); - - ImageData data = image.getImageData(); - image.dispose(); - return data; - } - - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java deleted file mode 100644 index 48fa903..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptor.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.swt.graphics.Image; - -/** - * {@link AttributeDescriptor} describes an XML attribute with its XML attribute name. - *

              - * An attribute descriptor also knows which UI node should be instantiated to represent - * this particular attribute (e.g. text field, icon chooser, class selector, etc.) - * Some attributes may be hidden and have no user interface at all. - *

              - * This is an abstract class. Derived classes must implement data description and return - * the correct UiAttributeNode-derived class. - */ -public abstract class AttributeDescriptor { - private String mXmlLocalName; - private ElementDescriptor mParent; - private final String mNsUri; - - /** - * Creates a new {@link AttributeDescriptor} - * - * @param xmlLocalName The XML name of the attribute (case sensitive) - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - */ - public AttributeDescriptor(String xmlLocalName, String nsUri) { - mXmlLocalName = xmlLocalName; - mNsUri = nsUri; - } - - /** - * Returns the XML local name of the attribute (case sensitive) - */ - public final String getXmlLocalName() { - return mXmlLocalName; - } - - public final String getNamespaceUri() { - return mNsUri; - } - - /** - * Returns the XML qualified name of the attribute (case sensitive, with namespace prefix - * if present) - * - * @deprecated - */ - private final String getXmlName() { - return mXmlLocalName; - } - - /** - * Returns the namespace of the attribute. - * - * @deprecated - */ - private final String getNamespace() { - // For now we hard-code the prefix as being "android" - if (mXmlLocalName.startsWith("android:")) { //$NON-NLs-1$ - return AndroidConstants.NS_RESOURCES; - } - - return ""; //$NON-NLs-1$ - } - - final void setParent(ElementDescriptor parent) { - mParent = parent; - } - - public final ElementDescriptor getParent() { - return mParent; - } - - /** - * Returns an optional icon for the attribute. - *

              - * By default this tries to return an icon based on the XML name of the attribute. - * If this fails, it tries to return the default Android logo as defined in the - * plugin. If all fails, it returns null. - * - * @return An icon for this element or null. - */ - public Image getIcon() { - IconFactory factory = IconFactory.getInstance(); - Image icon; - icon = factory.getIcon(getXmlLocalName(), IconFactory.COLOR_RED, IconFactory.SHAPE_CIRCLE); - return icon != null ? icon : EditorsPlugin.getAndroidLogo(); - } - - /** - * @param uiParent The {@link UiElementNode} parent of this UI attribute. - * @return A new {@link UiAttributeNode} linked to this descriptor or null if this - * attribute has no user interface. - */ - public abstract UiAttributeNode createUiNode(UiElementNode uiParent); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java deleted file mode 100644 index 05ae922..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/AttributeDescriptorLabelProvider.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiAbstractTextAttributeNode; - -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.swt.graphics.Image; - -/** - * Label provider for {@link UiAbstractTextAttributeNode}. - */ -public class AttributeDescriptorLabelProvider implements ILabelProvider { - - private final static AttributeDescriptorLabelProvider sThis = - new AttributeDescriptorLabelProvider(); - - public static ILabelProvider getProvider() { - return sThis; - } - - public Image getImage(Object element) { - return null; - } - - public String getText(Object element) { - if (element instanceof UiAbstractTextAttributeNode) { - return ((UiAbstractTextAttributeNode)element).getCurrentValue(); - } - - return null; - } - - public void addListener(ILabelProviderListener listener) { - // TODO Auto-generated method stub - - } - - public void dispose() { - // TODO Auto-generated method stub - - } - - public boolean isLabelProperty(Object element, String property) { - // TODO Auto-generated method stub - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // TODO Auto-generated method stub - - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java deleted file mode 100644 index 9ebf5f1..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/BooleanAttributeDescriptor.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; - -/** - * Describes a text attribute that can only contain boolean values. - * It is displayed by a {@link UiListAttributeNode}. - */ -public class BooleanAttributeDescriptor extends ListAttributeDescriptor { - - public BooleanAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip, - new String[] { "true", "false" } ); - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java deleted file mode 100644 index f848d79..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DescriptorsUtils.java +++ /dev/null @@ -1,751 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo.Format; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.swt.graphics.Image; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -/** - * Utility methods related to descriptors handling. - */ -public final class DescriptorsUtils { - - private static final int JAVADOC_BREAK_LENGTH = 60; - - /** - * The path in the online documentation for the manifest description. - *

              - * This is NOT a complete URL. To be used, it needs to be appended - * to {@link AndroidConstants#CODESITE_BASE_URL} or to the local SDK - * documentation. - */ - public static final String MANIFEST_SDK_URL = "/reference/android/R.styleable.html#"; //$NON-NLS-1$ - - public static final String IMAGE_KEY = "image"; //$NON-NLS-1$ - - private static final String CODE = "$code"; //$NON-NLS-1$ - private static final String LINK = "$link"; //$NON-NLS-1$ - private static final String ELEM = "$elem"; //$NON-NLS-1$ - private static final String BREAK = "$break"; //$NON-NLS-1$ - - /** - * The {@link ITextAttributeCreator} interface is used by the appendAttribute() method - * to provide a way for caller to override the kind of {@link TextAttributeDescriptor} - * created for a give XML attribute name. - */ - public interface ITextAttributeCreator { - /** - * Creates a new {@link TextAttributeDescriptor} instance for the given XML name, - * UI name and tooltip. - * - * @param xmlName The XML attribute name. - * @param uiName The UI attribute name. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip An optional tooltip. - * @return A new {@link TextAttributeDescriptor} (or derived) instance. - */ - public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, - String tooltip); - } - - /** - * Add all {@link AttributeInfo} to the the array of {@link AttributeDescriptor}. - * - * @param attributes The list of {@link AttributeDescriptor} to append to - * @param elementXmlName Optional XML local name of the element to which attributes are - * being added. When not null, this is used to filter overrides. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param infos The array of {@link AttributeInfo} to read and append to attributes - * @param requiredAttributes An optional list of attributes to mark as "required" (i.e. append - * a "*" to their UI name as a hint for the user.) - * @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator - * can either by a Class or an instance of - * {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor. - */ - public static void appendAttributes(ArrayList attributes, - String elementXmlName, - String nsUri, AttributeInfo[] infos, - String[] requiredAttributes, - Map overrides) { - for (AttributeInfo info : infos) { - boolean required = false; - if (requiredAttributes != null) { - for(String attr_name : requiredAttributes) { - if (attr_name.equals(info.getName())) { - required = true; - break; - } - } - } - appendAttribute(attributes, elementXmlName, nsUri, info, required, overrides); - } - } - - /** - * Add an {@link AttributeInfo} to the the array of {@link AttributeDescriptor}. - * - * @param attributes The list of {@link AttributeDescriptor} to append to - * @param elementXmlName Optional XML local name of the element to which attributes are - * being added. When not null, this is used to filter overrides. - * @param info The {@link AttributeInfo} to append to attributes - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param required True if the attribute is to be marked as "required" (i.e. append - * a "*" to its UI name as a hint for the user.) - * @param overrides A map [attribute name => TextAttributeDescriptor creator]. A creator - * can either by a Class or an instance of - * {@link ITextAttributeCreator} that instantiates the right TextAttributeDescriptor. - */ - public static void appendAttribute(ArrayList attributes, - String elementXmlName, - String nsUri, - AttributeInfo info, boolean required, - Map overrides) { - AttributeDescriptor attr = null; - - String xmlLocalName = info.getName(); - String uiName = prettyAttributeUiName(info.getName()); // ui_name - if (required) { - uiName += "*"; //$NON-NLS-1$ - } - String tooltip = formatTooltip(info.getJavaDoc()); // tooltip - - // Add the known types to the tooltip - Format[] formats_list = info.getFormats(); - int flen = formats_list.length; - if (flen > 0) { - // Fill the formats in a set for faster access - HashSet formats_set = new HashSet(); - - StringBuilder sb = new StringBuilder(); - if (tooltip != null) { - sb.append(tooltip); - sb.append(" "); //$NON-NLS-1$ - } - sb.append("["); //$NON-NLS-1$ - for (int i = 0; i < flen; i++) { - Format f = formats_list[i]; - formats_set.add(f); - - sb.append(f.toString().toLowerCase()); - if (i < flen - 1) { - sb.append(", "); //$NON-NLS-1$ - } - } - // The extra space at the end makes the tooltip more readable on Windows. - sb.append("]"); //$NON-NLS-1$ - - if (required) { - sb.append(". Required."); - } - - // The extra space at the end makes the tooltip more readable on Windows. - sb.append(" "); //$NON-NLS-1$ - - tooltip = sb.toString(); - - // Create a specialized attribute if we can - if (overrides != null) { - for (Entry entry: overrides.entrySet()) { - String key = entry.getKey(); - String elements[] = key.split("/"); - String overrideAttrLocalName = null; - if (elements.length < 1) { - continue; - } else if (elements.length == 1) { - overrideAttrLocalName = elements[0]; - elements = null; - } else { - overrideAttrLocalName = elements[elements.length - 1]; - elements = elements[0].split(","); - } - - if (overrideAttrLocalName == null || - !overrideAttrLocalName.equals(xmlLocalName)) { - continue; - } - - boolean ok_element = elements.length < 1; - if (!ok_element) { - for (String element : elements) { - if (element.equals("*") || element.equals(elementXmlName)) { - ok_element = true; - break; - } - } - } - - if (!ok_element) { - continue; - } - - Object override = entry.getValue(); - if (override instanceof Class) { - try { - // The override is instance of the class to create, which must - // have a constructor compatible with TextAttributeDescriptor. - @SuppressWarnings("unchecked") //$NON-NLS-1$ - Class clazz = - (Class) override; - Constructor cons; - cons = clazz.getConstructor(new Class[] { - String.class, String.class, String.class, String.class } ); - attr = cons.newInstance( - new Object[] { xmlLocalName, uiName, nsUri, tooltip }); - } catch (SecurityException e) { - // ignore - } catch (NoSuchMethodException e) { - // ignore - } catch (IllegalArgumentException e) { - // ignore - } catch (InstantiationException e) { - // ignore - } catch (IllegalAccessException e) { - // ignore - } catch (InvocationTargetException e) { - // ignore - } - } else if (override instanceof ITextAttributeCreator) { - attr = ((ITextAttributeCreator) override).create( - xmlLocalName, uiName, nsUri, tooltip); - } - } - } // if overrides - - // Create a specialized descriptor if we can, based on type - if (attr == null) { - if (formats_set.contains(Format.REFERENCE)) { - // This is either a multi-type reference or a generic reference. - attr = new ReferenceAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); - } else if (formats_set.contains(Format.ENUM)) { - attr = new ListAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip, - info.getEnumValues()); - } else if (formats_set.contains(Format.FLAG)) { - attr = new FlagAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip, - info.getFlagValues()); - } else if (formats_set.contains(Format.BOOLEAN)) { - attr = new BooleanAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); - } else if (formats_set.contains(Format.STRING)) { - attr = new ReferenceAttributeDescriptor(ResourceType.STRING, - xmlLocalName, uiName, nsUri, - tooltip); - } - } - } - - // By default a simple text field is used - if (attr == null) { - attr = new TextAttributeDescriptor(xmlLocalName, uiName, nsUri, tooltip); - } - attributes.add(attr); - } - - /** - * Indicates the the given {@link AttributeInfo} already exists in the ArrayList of - * {@link AttributeDescriptor}. This test for the presence of a descriptor with the same - * XML name. - * - * @param attributes The list of {@link AttributeDescriptor} to compare to. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param info The {@link AttributeInfo} to know whether it is included in the above list. - * @return True if this {@link AttributeInfo} is already present in - * the {@link AttributeDescriptor} list. - */ - public static boolean containsAttribute(ArrayList attributes, - String nsUri, - AttributeInfo info) { - String xmlLocalName = info.getName(); - for (AttributeDescriptor desc : attributes) { - if (desc.getXmlLocalName().equals(xmlLocalName)) { - if (nsUri == desc.getNamespaceUri() || - (nsUri != null && nsUri.equals(desc.getNamespaceUri()))) { - return true; - } - } - } - return false; - } - - /** - * Create a pretty attribute UI name from an XML name. - *

              - * The original xml name starts with a lower case and is camel-case, - * e.g. "maxWidthForView". The pretty name starts with an upper case - * and has space separators, e.g. "Max width for view". - */ - public static String prettyAttributeUiName(String name) { - if (name.length() < 1) { - return name; - } - StringBuffer buf = new StringBuffer(); - - char c = name.charAt(0); - // Use upper case initial letter - buf.append((char)(c >= 'a' && c <= 'z' ? c + 'A' - 'a' : c)); - int len = name.length(); - for (int i = 1; i < len; i++) { - c = name.charAt(i); - if (c >= 'A' && c <= 'Z') { - // Break camel case into separate words - buf.append(' '); - // Use a lower case initial letter for the next word, except if the - // word is solely X, Y or Z. - if (c >= 'X' && c <= 'Z' && - (i == len-1 || - (i < len-1 && name.charAt(i+1) >= 'A' && name.charAt(i+1) <= 'Z'))) { - buf.append(c); - } else { - buf.append((char)(c - 'A' + 'a')); - } - } else if (c == '_') { - buf.append(' '); - } else { - buf.append(c); - } - } - - name = buf.toString(); - - // Replace these acronyms by upper-case versions - // - (?<=^| ) means "if preceded by a space or beginning of string" - // - (?=$| ) means "if followed by a space or end of string" - name = name.replaceAll("(?<=^| )sdk(?=$| )", "SDK"); - name = name.replaceAll("(?<=^| )uri(?=$| )", "URI"); - - return name; - } - - /** - * Capitalizes the string, i.e. transforms the initial [a-z] into [A-Z]. - * Returns the string unmodified if the first character is not [a-z]. - * - * @param str The string to capitalize. - * @return The capitalized string - */ - public static String capitalize(String str) { - if (str == null || str.length() < 1 || str.charAt(0) < 'a' || str.charAt(0) > 'z') { - return str; - } - - StringBuilder sb = new StringBuilder(); - sb.append((char)(str.charAt(0) + 'A' - 'a')); - sb.append(str.substring(1)); - return sb.toString(); - } - - /** - * Formats the javadoc tooltip to be usable in a tooltip. - */ - public static String formatTooltip(String javadoc) { - ArrayList spans = scanJavadoc(javadoc); - - StringBuilder sb = new StringBuilder(); - boolean needBreak = false; - - for (int n = spans.size(), i = 0; i < n; ++i) { - String s = spans.get(i); - if (CODE.equals(s)) { - s = spans.get(++i); - if (s != null) { - sb.append('"').append(s).append('"'); - } - } else if (LINK.equals(s)) { - String base = spans.get(++i); - String anchor = spans.get(++i); - String text = spans.get(++i); - - if (base != null) { - base = base.trim(); - } - if (anchor != null) { - anchor = anchor.trim(); - } - if (text != null) { - text = text.trim(); - } - - // If there's no text, use the anchor if there's one - if (text == null || text.length() == 0) { - text = anchor; - } - - if (base != null && base.length() > 0) { - if (text == null || text.length() == 0) { - // If we still have no text, use the base as text - text = base; - } - } - - if (text != null) { - sb.append(text); - } - - } else if (ELEM.equals(s)) { - s = spans.get(++i); - if (s != null) { - sb.append(s); - } - } else if (BREAK.equals(s)) { - needBreak = true; - } else if (s != null) { - if (needBreak && s.trim().length() > 0) { - sb.append('\r'); - } - sb.append(s); - needBreak = false; - } - } - - return sb.toString(); - } - - /** - * Formats the javadoc tooltip to be usable in a FormText. - *

              - * If the descriptor can provide an icon, the caller should provide - * elementsDescriptor.getIcon() as "image" to FormText, e.g.: - * formText.setImage(IMAGE_KEY, elementsDescriptor.getIcon()); - * - * @param javadoc The javadoc to format. Cannot be null. - * @param elementDescriptor The element descriptor parent of the javadoc. Cannot be null. - * @param androidDocBaseUrl The base URL for the documentation. Cannot be null. Should be - * FrameworkResourceManager.getInstance().getDocumentationBaseUrl() - */ - public static String formatFormText(String javadoc, - ElementDescriptor elementDescriptor, - String androidDocBaseUrl) { - ArrayList spans = scanJavadoc(javadoc); - - String fullSdkUrl = androidDocBaseUrl + MANIFEST_SDK_URL; - String sdkUrl = elementDescriptor.getSdkUrl(); - if (sdkUrl != null && sdkUrl.startsWith(MANIFEST_SDK_URL)) { - fullSdkUrl = androidDocBaseUrl + sdkUrl; - } - - StringBuilder sb = new StringBuilder(); - - Image icon = elementDescriptor.getIcon(); - if (icon != null) { - sb.append("

            • "); //$NON-NLS-1$ //$NON-NLS-2$ - } else { - sb.append("

              "); //$NON-NLS-1$ - } - - for (int n = spans.size(), i = 0; i < n; ++i) { - String s = spans.get(i); - if (CODE.equals(s)) { - s = spans.get(++i); - if (elementDescriptor.getXmlName().equals(s) && fullSdkUrl != null) { - sb.append(""); //$NON-NLS-1$ - sb.append(s); - sb.append(""); //$NON-NLS-1$ - } else if (s != null) { - sb.append('"').append(s).append('"'); - } - } else if (LINK.equals(s)) { - String base = spans.get(++i); - String anchor = spans.get(++i); - String text = spans.get(++i); - - if (base != null) { - base = base.trim(); - } - if (anchor != null) { - anchor = anchor.trim(); - } - if (text != null) { - text = text.trim(); - } - - // If there's no text, use the anchor if there's one - if (text == null || text.length() == 0) { - text = anchor; - } - - // TODO specialize with a base URL for views, menus & other resources - // Base is empty for a local page anchor, in which case we'll replace it - // by the element SDK URL if it exists. - if ((base == null || base.length() == 0) && fullSdkUrl != null) { - base = fullSdkUrl; - } - - String url = null; - if (base != null && base.length() > 0) { - if (base.startsWith("http")) { //$NON-NLS-1$ - // If base looks an URL, use it, with the optional anchor - url = base; - if (anchor != null && anchor.length() > 0) { - // If the base URL already has an anchor, it needs to be - // removed first. If there's no anchor, we need to add "#" - int pos = url.lastIndexOf('#'); - if (pos < 0) { - url += "#"; //$NON-NLS-1$ - } else if (pos < url.length() - 1) { - url = url.substring(0, pos + 1); - } - - url += anchor; - } - } else if (text == null || text.length() == 0) { - // If we still have no text, use the base as text - text = base; - } - } - - if (url != null && text != null) { - sb.append(""); //$NON-NLS-1$ - sb.append(text); - sb.append(""); //$NON-NLS-1$ - } else if (text != null) { - sb.append("").append(text).append(""); //$NON-NLS-1$ //$NON-NLS-2$ - } - - } else if (ELEM.equals(s)) { - s = spans.get(++i); - if (sdkUrl != null && s != null) { - sb.append(""); //$NON-NLS-1$ - sb.append(s); - sb.append(""); //$NON-NLS-1$ - } else if (s != null) { - sb.append("").append(s).append(""); //$NON-NLS-1$ //$NON-NLS-2$ - } - } else if (BREAK.equals(s)) { - // ignore line breaks in pseudo-HTML rendering - } else if (s != null) { - sb.append(s); - } - } - - if (icon != null) { - sb.append("

            • "); //$NON-NLS-1$ - } else { - sb.append("

              "); //$NON-NLS-1$ - } - return sb.toString(); - } - - private static ArrayList scanJavadoc(String javadoc) { - ArrayList spans = new ArrayList(); - - // Standardize all whitespace in the javadoc to single spaces. - if (javadoc != null) { - javadoc = javadoc.replaceAll("[ \t\f\r\n]+", " "); //$NON-NLS-1$ //$NON-NLS-2$ - } - - // Detects {@link # } where all 3 are optional - Pattern p_link = Pattern.compile("\\{@link\\s+([^#\\}\\s]*)(?:#([^\\s\\}]*))?(?:\\s*([^\\}]*))?\\}(.*)"); //$NON-NLS-1$ - // Detects blah - Pattern p_code = Pattern.compile("(.+?)(.*)"); //$NON-NLS-1$ - // Detects @blah@, used in hard-coded tooltip descriptors - Pattern p_elem = Pattern.compile("@([\\w -]+)@(.*)"); //$NON-NLS-1$ - // Detects a buffer that starts by @ < or { (one that was not matched above) - Pattern p_open = Pattern.compile("([@<\\{])(.*)"); //$NON-NLS-1$ - // Detects everything till the next potential separator, i.e. @ < or { - Pattern p_text = Pattern.compile("([^@<\\{]+)(.*)"); //$NON-NLS-1$ - - int currentLength = 0; - String text = null; - - while(javadoc != null && javadoc.length() > 0) { - Matcher m; - String s = null; - if ((m = p_code.matcher(javadoc)).matches()) { - spans.add(CODE); - spans.add(text = cleanupJavadocHtml(m.group(1))); // text - javadoc = m.group(2); - if (text != null) { - currentLength += text.length(); - } - } else if ((m = p_link.matcher(javadoc)).matches()) { - spans.add(LINK); - spans.add(m.group(1)); // @link base - spans.add(m.group(2)); // @link anchor - spans.add(text = cleanupJavadocHtml(m.group(3))); // @link text - javadoc = m.group(4); - if (text != null) { - currentLength += text.length(); - } - } else if ((m = p_elem.matcher(javadoc)).matches()) { - spans.add(ELEM); - spans.add(text = cleanupJavadocHtml(m.group(1))); // @text@ - javadoc = m.group(2); - if (text != null) { - currentLength += text.length() - 2; - } - } else if ((m = p_open.matcher(javadoc)).matches()) { - s = m.group(1); - javadoc = m.group(2); - } else if ((m = p_text.matcher(javadoc)).matches()) { - s = m.group(1); - javadoc = m.group(2); - } else { - // This is not supposed to happen. In case of, just use everything. - s = javadoc; - javadoc = null; - } - if (s != null && s.length() > 0) { - s = cleanupJavadocHtml(s); - - if (currentLength >= JAVADOC_BREAK_LENGTH) { - spans.add(BREAK); - currentLength = 0; - } - while (currentLength + s.length() > JAVADOC_BREAK_LENGTH) { - int pos = s.indexOf(' ', JAVADOC_BREAK_LENGTH - currentLength); - if (pos <= 0) { - break; - } - spans.add(s.substring(0, pos + 1)); - spans.add(BREAK); - currentLength = 0; - s = s.substring(pos + 1); - } - - spans.add(s); - currentLength += s.length(); - } - } - - return spans; - } - - /** - * Remove anything that looks like HTML from a javadoc snippet, as it is supported - * neither by FormText nor a standard text tooltip. - */ - private static String cleanupJavadocHtml(String s) { - if (s != null) { - s = s.replaceAll("<", "\""); //$NON-NLS-1$ $NON-NLS-2$ - s = s.replaceAll(">", "\""); //$NON-NLS-1$ $NON-NLS-2$ - s = s.replaceAll("<[^>]+>", ""); //$NON-NLS-1$ $NON-NLS-2$ - } - return s; - } - - /** - * Sets the default layout attributes for the a new UiElementNode. - *

              - * Note that ideally the node should already be part of a hierarchy so that its - * parent layout and previous sibling can be determined, if any. - *

              - * This does not override attributes which are not empty. - */ - public static void setDefaultLayoutAttributes(UiElementNode ui_node) { - ui_node.setAttributeValue("layout_width", "wrap_content", false /* override */); //$NON-NLS-1$ $NON-NLS-2$ - ui_node.setAttributeValue("layout_height", "wrap_content", false /* override */); //$NON-NLS-1$ $NON-NLS-2$ - - String widget_id = getFreeWidgetId(ui_node.getUiRoot(), - new Object[] {ui_node.getDescriptor().getXmlLocalName(), 1, null }); - if (widget_id != null) { - ui_node.setAttributeValue("id", "@+id/" + widget_id, false /* override */); //$NON-NLS-1$ $NON-NLS-2$ - } - - UiElementNode ui_parent = ui_node.getUiParent(); - if (ui_parent != null && ui_parent.getDescriptor().getXmlLocalName().equals("RelativeLayout")) { //$NON-NLS-1$ - UiElementNode ui_previous = ui_node.getUiPreviousSibling(); - if (ui_previous != null) { - String id = ui_previous.getAttributeValue("id"); //$NON-NLS-1$ - if (id != null && id.length() > 0) { - id = id.replace("@+", "@"); - ui_node.setAttributeValue("layout_below", id, false /* override */); - } - } - } - - } - - /** - * Given a UI root node, returns the first available id that matches the - * pattern "prefix%02d". - * - * For recursion purposes, a "context" is given. Since Java doesn't have in-out parameters - * in methods and we're not going to do a dedicated type, we just use an object array which - * must contain the following items: - * - prefix(String): The prefix of the generated id, i.e. "widget" - * - index(Integer): The minimum index of the generated id - * - generated(String) The generated widget currently being searched. Must start with null. - * - * @param uiRoot The Ui root node where to start searching recusrively. - * @param params An in-out context of parameters used during recursion, as explaine above. - * @return A suitable generated id - */ - private static String getFreeWidgetId(UiElementNode uiRoot, Object[] params) { - String generated = (String) params[2]; - if (generated == null) { - String prefix = (String) params[0]; - int pos = prefix.indexOf('.'); - if (pos >= 0) { - prefix = prefix.substring(pos + 1); - } - pos = prefix.indexOf('$'); - if (pos >= 0) { - prefix = prefix.substring(pos + 1); - } - prefix = prefix.replaceAll("[^a-zA-Z]", ""); //$NON-NLS-1$ $NON-NLS-2$ - if (prefix.length() == 0) { - prefix = "widget"; - } - generated = String.format("%1$s%2$02d", prefix, ((Integer)params[1]).intValue()); - params[0] = prefix; - params[2] = generated; - } - - String id = uiRoot.getAttributeValue("id"); //$NON-NLS-1$ - if (id != null) { - id = id.replace("@+id/", ""); //$NON-NLS-1$ $NON-NLS-2$ - id = id.replace("@id/", ""); //$NON-NLS-1$ $NON-NLS-2$ - if (id.equals(generated)) { - // switch to next value - int num = ((Integer)params[1]).intValue() + 1; - generated = String.format("%1$s%2$02d", params[0], num); - params[1] = num; - params[2] = generated; - } - } - - for (UiElementNode uiChild : uiRoot.getUiChildren()) { - getFreeWidgetId(uiChild, params); - } - - return (String) params[2]; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java deleted file mode 100644 index 7d296f7..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/DocumentDescriptor.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * {@link DocumentDescriptor} describes the properties expected for an XML document node. - * - * Compared to ElementDescriptor, {@link DocumentDescriptor} does not have XML name nor UI name, - * tooltip, SDK url and attributes list. - *

              - * It has a children list which represent all the possible roots of the document. - *

              - * The document nodes are "mandatory", meaning the UI node is never deleted and it may lack - * an actual XML node attached. - */ -public class DocumentDescriptor extends ElementDescriptor { - - /** - * Constructs a new {@link DocumentDescriptor} based on its XML name and children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - *

              - * The XML name is never shown in the UI directly. It is however used when an icon - * needs to be found for the node. - * - * @param xml_name The XML element node name. Case sensitive. - * @param children The list of allowed children. Can be null or empty. - */ - public DocumentDescriptor(String xml_name, ElementDescriptor[] children) { - super(xml_name, children, true /* mandatory */); - } - - /** - * @return A new {@link UiElementNode} linked to this descriptor. - */ - @Override - public UiElementNode createUiNode() { - return new UiDocumentNode(this); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java deleted file mode 100644 index 12ebc38..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ElementDescriptor.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; - -import java.util.HashSet; -import java.util.Set; - -/** - * {@link ElementDescriptor} describes the properties expected for a given XML element node. - * - * {@link ElementDescriptor} have an XML name, UI name, a tooltip, an SDK url, - * an attributes list and a children list. - * - * An UI node can be "mandatory", meaning the UI node is never deleted and it may lack - * an actual XML node attached. A non-mandatory UI node MUST have an XML node attached - * and it will cease to exist when the XML node ceases to exist. - */ -public class ElementDescriptor { - /** The XML element node name. Case sensitive. */ - private String mXmlName; - /** The XML element name for the user interface, typically capitalized. */ - private String mUiName; - /** The list of allowed attributes. */ - private AttributeDescriptor[] mAttributes; - /** The list of allowed children */ - private ElementDescriptor[] mChildren; - /* An optional tooltip. Can be empty. */ - private String mTooltip; - /** An optional SKD URL. Can be empty. */ - private String mSdkUrl; - /** Whether this UI node must always exist (even for empty models). */ - private boolean mMandatory; - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name, UI name, - * tooltip, SDK url, attributes list, children list and mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param ui_name The XML element name for the user interface, typically capitalized. - * @param tooltip An optional tooltip. Can be null or empty. - * @param sdk_url An optional SKD URL. Can be null or empty. - * @param attributes The list of allowed attributes. Can be null or empty. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - */ - public ElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, - AttributeDescriptor[] attributes, - ElementDescriptor[] children, - boolean mandatory) { - mMandatory = mandatory; - mXmlName = xml_name; - mUiName = ui_name; - mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; - mSdkUrl = (sdk_url != null && sdk_url.length() > 0) ? sdk_url : null; - setAttributes(attributes != null ? attributes : new AttributeDescriptor[]{}); - mChildren = children != null ? children : new ElementDescriptor[]{}; - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name and children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - */ - public ElementDescriptor(String xml_name, ElementDescriptor[] children, boolean mandatory) { - this(xml_name, prettyName(xml_name), null, null, null, children, mandatory); - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name and children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param children The list of allowed children. Can be null or empty. - */ - public ElementDescriptor(String xml_name, ElementDescriptor[] children) { - this(xml_name, prettyName(xml_name), null, null, null, children, false); - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - */ - public ElementDescriptor(String xml_name) { - this(xml_name, prettyName(xml_name), null, null, null, null, false); - } - - /** Returns whether this node must always exist (even for empty models) */ - public boolean isMandatory() { - return mMandatory; - } - - /** - * Returns the XML element node local name (case sensitive) - */ - public final String getXmlLocalName() { - int pos = mXmlName.indexOf(':'); - if (pos != -1) { - return mXmlName.substring(pos+1); - } - return mXmlName; - } - - /** Returns the XML element node name. Case sensitive. */ - public String getXmlName() { - return mXmlName; - } - - /** - * Returns the namespace of the attribute. - */ - public final String getNamespace() { - // For now we hard-code the prefix as being "android" - if (mXmlName.startsWith("android:")) { //$NON-NLs-1$ - return AndroidConstants.NS_RESOURCES; - } - - return ""; //$NON-NLs-1$ - } - - - /** Returns the XML element name for the user interface, typically capitalized. */ - public String getUiName() { - return mUiName; - } - - /** - * Returns an optional icon for the element. - *

              - * By default this tries to return an icon based on the XML name of the element. - * If this fails, it tries to return the default Android logo as defined in the - * plugin. If all fails, it returns null. - * - * @return An icon for this element or null. - */ - public Image getIcon() { - IconFactory factory = IconFactory.getInstance(); - int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; - int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; - Image icon = factory.getIcon(mXmlName, color, shape); - return icon != null ? icon : EditorsPlugin.getAndroidLogo(); - } - - /** - * Returns an optional ImageDescriptor for the element. - *

              - * By default this tries to return an image based on the XML name of the element. - * If this fails, it tries to return the default Android logo as defined in the - * plugin. If all fails, it returns null. - * - * @return An ImageDescriptor for this element or null. - */ - public ImageDescriptor getImageDescriptor() { - IconFactory factory = IconFactory.getInstance(); - int color = hasChildren() ? IconFactory.COLOR_BLUE : IconFactory.COLOR_GREEN; - int shape = hasChildren() ? IconFactory.SHAPE_RECT : IconFactory.SHAPE_CIRCLE; - ImageDescriptor id = factory.getImageDescriptor(mXmlName, color, shape); - return id != null ? id : EditorsPlugin.getAndroidLogoDesc(); - } - - /* Returns the list of allowed attributes. */ - public AttributeDescriptor[] getAttributes() { - return mAttributes; - } - - /* Sets the list of allowed attributes. */ - public void setAttributes(AttributeDescriptor[] attributes) { - mAttributes = attributes; - for (AttributeDescriptor attribute : attributes) { - attribute.setParent(this); - } - } - - /** Returns the list of allowed children */ - public ElementDescriptor[] getChildren() { - return mChildren; - } - - /** @return True if this descriptor has children available */ - public boolean hasChildren() { - return mChildren.length > 0; - } - - /** Sets the list of allowed children. */ - public void setChildren(ElementDescriptor[] newChildren) { - mChildren = newChildren; - } - - /** - * Returns an optional tooltip. Will be null if not present. - *

              - * The tooltip is based on the Javadoc of the element and already processed via - * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as - * a UI tooltip. - */ - public String getTooltip() { - return mTooltip; - } - - /** Returns an optional SKD URL. Will be null if not present. */ - public String getSdkUrl() { - return mSdkUrl; - } - - /** Sets the optional tooltip. Can be null or empty. */ - public void setTooltip(String tooltip) { - mTooltip = tooltip; - } - - /** Sets the optional SDK URL. Can be null or empty. */ - public void setSdkUrl(String sdkUrl) { - mSdkUrl = sdkUrl; - } - - /** - * @return A new {@link UiElementNode} linked to this descriptor. - */ - public UiElementNode createUiNode() { - return new UiElementNode(this); - } - - /** - * Returns the first children of this descriptor that describes the given XML element name. - *

              - * In recursive mode, searches the direct children first before descending in the hierarchy. - * - * @return The ElementDescriptor matching the requested XML node element name or null. - */ - public ElementDescriptor findChildrenDescriptor(String element_name, boolean recursive) { - return findChildrenDescriptorInternal(element_name, recursive, null); - } - - private ElementDescriptor findChildrenDescriptorInternal(String element_name, - boolean recursive, - Set visited) { - if (recursive && visited == null) { - visited = new HashSet(); - } - - for (ElementDescriptor e : getChildren()) { - if (e.getXmlName().equals(element_name)) { - return e; - } - } - - if (visited != null) { - visited.add(this); - } - - if (recursive) { - for (ElementDescriptor e : getChildren()) { - if (visited != null) { - if (!visited.add(e)) { // Set.add() returns false if element is already present - continue; - } - } - ElementDescriptor f = e.findChildrenDescriptorInternal(element_name, - recursive, visited); - if (f != null) { - return f; - } - } - } - - return null; - } - - /** - * Utility helper than pretty-formats an XML Name for the UI. - * This is used by the simplified constructor that takes only an XML element name. - * - * @param xml_name The XML name to convert. - * @return The XML name with dashes replaced by spaces and capitalized. - */ - private static String prettyName(String xml_name) { - char c[] = xml_name.toCharArray(); - if (c.length > 0) { - c[0] = Character.toUpperCase(c[0]); - } - return new String(c).replace("-", " "); //$NON-NLS-1$ //$NON-NLS-2$ - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java deleted file mode 100644 index cab9883..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/EnumAttributeDescriptor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; - -/** - * Describes a text attribute that can only contains some predefined values. - * It is displayed by a {@link UiListAttributeNode}. - */ -public class EnumAttributeDescriptor extends ListAttributeDescriptor { - - public EnumAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * @return A new {@link UiListAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiListAttributeNode(this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java deleted file mode 100644 index 903417b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/FlagAttributeDescriptor.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.editors.ui.FlagValueCellEditor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; - -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.swt.widgets.Composite; - -/** - * Describes a text attribute that can only contains some predefined values. - * It is displayed by a {@link UiListAttributeNode}. - * - * Note: in Android resources, a "flag" is a list of fixed values where one or - * more values can be selected using an "or", e.g. "align='left|top'". - * By contrast, an "enum" is a list of fixed values of which only one can be - * selected at a given time, e.g. "gravity='right'". - *

              - * This class handles the "flag" case. - * The "enum" case is done using {@link ListAttributeDescriptor}. - */ -public class FlagAttributeDescriptor extends TextAttributeDescriptor { - - private String[] mNames; - - /** - * Creates a new {@link FlagAttributeDescriptor} which automatically gets its - * values from the FrameworkResourceManager. - */ - public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * Creates a new {@link FlagAttributeDescriptor} which uses the provided values. - */ - public FlagAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip, String[] names) { - super(xmlLocalName, uiName, nsUri, tooltip); - mNames = names; - } - - /** - * @return The initial names of the flags. Can be null, in which case the Framework - * resource parser should be checked. - */ - public String[] getNames() { - return mNames; - } - - /** - * @return A new {@link UiListAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiFlagAttributeNode(this, uiParent); - } - - // ------- IPropertyDescriptor Methods - - @Override - public CellEditor createPropertyEditor(Composite parent) { - return new FlagValueCellEditor(parent); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java deleted file mode 100644 index 93969e9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ListAttributeDescriptor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.editors.ui.ListValueCellEditor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; - -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.swt.widgets.Composite; - -/** - * Describes a text attribute that can contains some predefined values. - * It is displayed by a {@link UiListAttributeNode}. - */ -public class ListAttributeDescriptor extends TextAttributeDescriptor { - - private String[] mValues = null; - - /** - * Creates a new {@link ListAttributeDescriptor} which automatically gets its - * values from the FrameworkResourceManager. - */ - public ListAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * Creates a new {@link ListAttributeDescriptor} which uses the provided values. - */ - public ListAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip, String[] values) { - super(xmlLocalName, uiName, nsUri, tooltip); - mValues = values; - } - - public String[] getValues() { - return mValues; - } - - /** - * @return A new {@link UiListAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiListAttributeNode(this, uiParent); - } - - // ------- IPropertyDescriptor Methods - - @Override - public CellEditor createPropertyEditor(Composite parent) { - return new ListValueCellEditor(parent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java deleted file mode 100644 index 3d3ff29..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/ReferenceAttributeDescriptor.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.ui.ResourceValueCellEditor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; - -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.swt.widgets.Composite; - -/** - * Describes an XML attribute displayed containing a value or a reference to a resource. - * It is displayed by a {@link UiResourceAttributeNode}. - */ -public final class ReferenceAttributeDescriptor extends TextAttributeDescriptor { - - private ResourceType mResourceType; - - /** - * Creates a reference attributes that can contain any type of resources. - * @param xmlLocalName The XML name of the attribute (case sensitive) - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip A non-empty tooltip string or null - */ - public ReferenceAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * Creates a reference attributes that can contain a reference to a specific - * {@link ResourceType}. - * @param resourceType The specific {@link ResourceType} that this reference attribute supports. - * It can be null, in which case, all resource types are supported. - * @param xmlLocalName The XML name of the attribute (case sensitive) - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip A non-empty tooltip string or null - */ - public ReferenceAttributeDescriptor(ResourceType resourceType, - String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - mResourceType = resourceType; - } - - - /** - * @return A new {@link UiResourceAttributeNode} linked to this reference descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiResourceAttributeNode(mResourceType, this, uiParent); - } - - // ------- IPropertyDescriptor Methods - - @Override - public CellEditor createPropertyEditor(Composite parent) { - return new ResourceValueCellEditor(parent); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java deleted file mode 100644 index 8fb1c7c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/SeparatorAttributeDescriptor.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiSeparatorAttributeNode; - -/** - * {@link SeparatorAttributeDescriptor} does not represent any real attribute. - *

              - * It is used to separate groups of attributes visually. - */ -public class SeparatorAttributeDescriptor extends AttributeDescriptor { - - /** - * Creates a new {@link SeparatorAttributeDescriptor} - */ - public SeparatorAttributeDescriptor(String label) { - super(label /* xmlLocalName */, null /* nsUri */); - } - - /** - * @return A new {@link UiAttributeNode} linked to this descriptor or null if this - * attribute has no user interface. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiSeparatorAttributeNode(this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java deleted file mode 100644 index 632471d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextAttributeDescriptor.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.ui.TextValueCellEditor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; - -import org.eclipse.jface.viewers.CellEditor; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.ui.views.properties.IPropertyDescriptor; - - -/** - * Describes a textual XML attribute. - *

              - * Such an attribute has a tooltip and would typically be displayed by - * {@link UiTextAttributeNode} using a label widget and text field. - *

              - * This is the "default" kind of attribute. If in doubt, use this. - */ -public class TextAttributeDescriptor extends AttributeDescriptor implements IPropertyDescriptor { - private String mUiName; - private String mTooltip; - - /** - * Creates a new {@link TextAttributeDescriptor} - * - * @param xmlLocalName The XML name of the attribute (case sensitive) - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip A non-empty tooltip string or null - */ - public TextAttributeDescriptor(String xmlLocalName, String uiName, - String nsUri, String tooltip) { - super(xmlLocalName, nsUri); - mUiName = uiName; - mTooltip = (tooltip != null && tooltip.length() > 0) ? tooltip : null; - } - - /** - * @return The UI name of the attribute. Cannot be an empty string and cannot be null. - */ - public final String getUiName() { - return mUiName; - } - - /** - * The tooltip string is either null or a non-empty string. - *

              - * The tooltip is based on the Javadoc of the attribute and already processed via - * {@link DescriptorsUtils#formatTooltip(String)} to be displayed right away as - * a UI tooltip. - *

              - * An empty string is converted to null, to match the behavior of setToolTipText() in - * {@link Control}. - * - * @return A non-empty tooltip string or null - */ - public final String getTooltip() { - return mTooltip; - } - - /** - * @return A new {@link UiTextAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiTextAttributeNode(this, uiParent); - } - - // ------- IPropertyDescriptor Methods - - public CellEditor createPropertyEditor(Composite parent) { - return new TextValueCellEditor(parent); - } - - public String getCategory() { - ElementDescriptor parent = getParent(); - if (parent != null) { - return parent.getUiName(); - } - - return null; - } - - public String getDescription() { - return mTooltip; - } - - public String getDisplayName() { - return mUiName; - } - - public String[] getFilterFlags() { - return null; - } - - public Object getHelpContextIds() { - return null; - } - - public Object getId() { - return this; - } - - public ILabelProvider getLabelProvider() { - return AttributeDescriptorLabelProvider.getProvider(); - } - - public boolean isCompatibleWith(IPropertyDescriptor anotherProperty) { - return anotherProperty == this; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java deleted file mode 100644 index 2015d71..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/TextValueDescriptor.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiTextValueNode; - - -/** - * Describes the value of an XML element. - *

              - * The value is a simple text string, displayed by an {@link UiTextValueNode}. - */ -public class TextValueDescriptor extends TextAttributeDescriptor { - - /** - * Creates a new {@link TextValueDescriptor} - * - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param tooltip A non-empty tooltip string or null - */ - public TextValueDescriptor(String uiName, String tooltip) { - super("#text" /* xmlLocalName */, uiName, null /* nsUri */, tooltip); - } - - /** - * @return A new {@link UiTextValueNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiTextValueNode(this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java deleted file mode 100644 index ed9c897..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/descriptors/XmlnsAttributeDescriptor.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.descriptors; - -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - - -/** - * Describes an XMLNS attribute that is hidden. - *

              - * Such an attribute has no user interface and no corresponding {@link UiAttributeNode}. - * It also has a single constant default value. - *

              - * When loading an XML, we'll ignore this attribute. - * However when writing a new XML, we should always write this attribute. - *

              - * Currently this is used for the xmlns:android attribute in the manifest element. - */ -public final class XmlnsAttributeDescriptor extends AttributeDescriptor { - - /** - * URI of the reserved "xmlns" prefix, as described in - * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/namespaces-algorithms.html#normalizeDocumentAlgo - */ - public final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; //$NON-NLS-1$ - - private String mValue; - - - public XmlnsAttributeDescriptor(String defaultPrefix, String value) { - super(defaultPrefix, XMLNS_URI); - mValue = value; - } - - /** - * Returns the value of this specialized attribute descriptor, which is the URI associated - * to the declared namespace prefix. - */ - public String getValue() { - return mValue; - } - - /** - * Returns the "xmlns" prefix that is always used by this node for its namespace URI. - * This is defined by the XML specification. - */ - public String getXmlNsPrefix() { - return "xmlns"; //$NON-NLS-1$ - } - - /** - * Returns the fully-qualified attribute name, namely "xmlns:xxx" where xxx is - * the defaultPrefix passed in the constructor. - */ - public String getXmlNsName() { - return getXmlNsPrefix() + ":" + getXmlLocalName(); //$NON-NLS-1$ - } - - /** - * @return Always returns null. {@link XmlnsAttributeDescriptor} has no user interface. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java deleted file mode 100644 index 278b921..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/GraphicalLayoutEditor.java +++ /dev/null @@ -1,1963 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.EditorsPlugin.LayoutBridge; -import com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener; -import com.android.ide.eclipse.editors.layout.parts.UiElementsEditPartFactory; -import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.editors.resources.configurations.NetworkCodeQualifier; -import com.android.ide.eclipse.editors.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.editors.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier; -import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier.KeyboardState; -import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier.NavigationMethod; -import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier.ScreenOrientation; -import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier.TextInputMethod; -import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier.TouchScreenType; -import com.android.ide.eclipse.editors.resources.manager.ProjectResources; -import com.android.ide.eclipse.editors.resources.manager.ResourceFile; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DensityVerifier; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.DimensionVerifier; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.LanguageRegionVerifier; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.MobileCodeVerifier; -import com.android.layoutlib.api.ILayoutLog; -import com.android.layoutlib.api.ILayoutResult; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.api.IStyleResourceValue; -import com.android.layoutlib.api.ILayoutResult.ILayoutViewInfo; - -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.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.draw2d.geometry.Rectangle; -import org.eclipse.gef.DefaultEditDomain; -import org.eclipse.gef.GraphicalViewer; -import org.eclipse.gef.SelectionManager; -import org.eclipse.gef.dnd.TemplateTransferDragSourceListener; -import org.eclipse.gef.dnd.TemplateTransferDropTargetListener; -import org.eclipse.gef.editparts.ScalableFreeformRootEditPart; -import org.eclipse.gef.palette.PaletteRoot; -import org.eclipse.gef.ui.parts.GraphicalEditor; -import org.eclipse.gef.ui.parts.SelectionSynchronizer; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.Clipboard; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.part.FileEditorInput; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Graphical layout editor, based on GEF. - */ -public class GraphicalLayoutEditor extends GraphicalEditor/*WithPalette*/ - implements ILayoutReloadListener { - - private final static String THEME_SEPARATOR = "----------"; //$NON-NLS-1$ - - /** Reference to the layout editor */ - private final LayoutEditor mLayoutEditor; - - /** reference to the file being edited. */ - private IFile mEditedFile; - - private Clipboard mClipboard; - private Composite mParent; - - private Text mCountry; - private Text mNetwork; - private Combo mLanguage; - private Combo mRegion; - private Combo mOrientation; - private Text mDensity; - private Combo mTouch; - private Combo mKeyboard; - private Combo mTextInput; - private Combo mNavigation; - private Text mSize1; - private Text mSize2; - private Combo mThemeCombo; - private Button mCreateButton; - - private Label mCountryIcon; - private Label mNetworkIcon; - private Label mLanguageIcon; - private Label mRegionIcon; - private Label mOrientationIcon; - private Label mDensityIcon; - private Label mTouchIcon; - private Label mKeyboardIcon; - private Label mTextInputIcon; - private Label mNavigationIcon; - private Label mSizeIcon; - - private Label mCurrentLayoutLabel; - - private Image mWarningImage; - private Image mMatchImage; - private Image mErrorImage; - - /** The {@link FolderConfiguration} representing the state of the UI controls */ - private FolderConfiguration mCurrentConfig = new FolderConfiguration(); - /** The {@link FolderConfiguration} being edited. */ - private FolderConfiguration mEditedConfig; - - private Map> mConfiguredFrameworkRes; - private Map> mConfiguredProjectRes; - private ProjectCallback mProjectCallback; - private ILayoutLog mLogger; - - private boolean mNeedsRecompute = false; - private int mPlatformThemeCount = 0; - private boolean mDisableUpdates = false; - - private Runnable mFrameworkResourceChangeListener = new Runnable() { - public void run() { - // because the SDK changed we must reset the configured framework resource. - mConfiguredFrameworkRes = null; - - updateUIFromResources(); - mThemeCombo.getParent().layout(); - - // updateUiFromFramework will reset language/region combo, so we must call - // setConfiguration after, or the settext on language/region will be lost. - if (mEditedConfig != null) { - setConfiguration(mEditedConfig); - } - - // make sure we remove the custom view loader, since its parent class loader is the - // bridge class loader. - mProjectCallback = null; - - recomputeLayout(); - } - }; - - private final Runnable mConditionalRecomputeRunnable = new Runnable() { - public void run() { - if (mLayoutEditor.isGraphicalEditorActive()) { - recomputeLayout(); - } else { - mNeedsRecompute = true; - } - } - }; - - private final Runnable mUiUpdateFromResourcesRunnable = new Runnable() { - public void run() { - updateUIFromResources(); - mThemeCombo.getParent().layout(); - } - }; - - public GraphicalLayoutEditor(LayoutEditor layoutEditor) { - mLayoutEditor = layoutEditor; - setEditDomain(new DefaultEditDomain(this)); - setPartName("Layout"); - - IconFactory factory = IconFactory.getInstance(); - mWarningImage = factory.getIcon("warning"); //$NON-NLS-1$ - mMatchImage = factory.getIcon("match"); //$NON-NLS-1$ - mErrorImage = factory.getIcon("error"); //$NON-NLS-1$ - - EditorsPlugin.getDefault().addResourceChangedListener(mFrameworkResourceChangeListener); - } - - // ------------------------------------ - // Methods overridden from base classes - //------------------------------------ - - @Override - public void createPartControl(Composite parent) { - mParent = parent; - GridLayout gl; - GridData gd; - - mClipboard = new Clipboard(parent.getDisplay()); - - parent.setLayout(gl = new GridLayout(1, false)); - gl.marginHeight = gl.marginWidth = 0; - - // create the top part for the configuration control - int cols = 10; - - Composite topParent = new Composite(parent, SWT.NONE); - topParent.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - topParent.setLayout(gl = new GridLayout(cols, false)); - - new Label(topParent, SWT.NONE).setText("MCC"); - mCountryIcon = createControlComposite(topParent, true /* grab_horizontal */); - mCountry = new Text(mCountryIcon.getParent(), SWT.BORDER); - mCountry.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mCountry.addVerifyListener(new MobileCodeVerifier()); - mCountry.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetDefaultSelected(SelectionEvent e) { - onCountryCodeChange(); - } - }); - mCountry.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onCountryCodeChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("MNC"); - mNetworkIcon = createControlComposite(topParent, true /* grab_horizontal */); - mNetwork = new Text(mNetworkIcon.getParent(), SWT.BORDER); - mNetwork.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mNetwork.addVerifyListener(new MobileCodeVerifier()); - mNetwork.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetDefaultSelected(SelectionEvent e) { - onNetworkCodeChange(); - } - }); - mNetwork.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onNetworkCodeChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Lang"); - mLanguageIcon = createControlComposite(topParent, true /* grab_horizontal */); - mLanguage = new Combo(mLanguageIcon.getParent(), SWT.DROP_DOWN); - mLanguage.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mLanguage.addVerifyListener(new LanguageRegionVerifier()); - mLanguage.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onLanguageChange(); - } - public void widgetSelected(SelectionEvent e) { - onLanguageChange(); - } - }); - mLanguage.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onLanguageChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Region"); - mRegionIcon = createControlComposite(topParent, true /* grab_horizontal */); - mRegion = new Combo(mRegionIcon.getParent(), SWT.DROP_DOWN); - mRegion.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mRegion.addVerifyListener(new LanguageRegionVerifier()); - mRegion.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onRegionChange(); - } - public void widgetSelected(SelectionEvent e) { - onRegionChange(); - } - }); - mRegion.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onRegionChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Orient"); - mOrientationIcon = createControlComposite(topParent, true /* grab_horizontal */); - mOrientation = new Combo(mOrientationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); - ScreenOrientation[] soValues = ScreenOrientation.values(); - mOrientation.add("(Default)"); - for (ScreenOrientation value : soValues) { - mOrientation.add(value.getDisplayValue()); - } - mOrientation.select(0); - mOrientation.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mOrientation.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onOrientationChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Density"); - mDensityIcon = createControlComposite(topParent, true /* grab_horizontal */); - mDensity = new Text(mDensityIcon.getParent(), SWT.BORDER); - mDensity.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mDensity.addVerifyListener(new DensityVerifier()); - mDensity.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetDefaultSelected(SelectionEvent e) { - onDensityChange(); - } - }); - mDensity.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onDensityChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Touch"); - mTouchIcon = createControlComposite(topParent, true /* grab_horizontal */); - mTouch = new Combo(mTouchIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); - TouchScreenType[] tstValues = TouchScreenType.values(); - mTouch.add("(Default)"); - for (TouchScreenType value : tstValues) { - mTouch.add(value.getDisplayValue()); - } - mTouch.select(0); - mTouch.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mTouch.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onTouchChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Keybrd"); - mKeyboardIcon = createControlComposite(topParent, true /* grab_horizontal */); - mKeyboard = new Combo(mKeyboardIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); - KeyboardState[] ksValues = KeyboardState.values(); - mKeyboard.add("(Default)"); - for (KeyboardState value : ksValues) { - mKeyboard.add(value.getDisplayValue()); - } - mKeyboard.select(0); - mKeyboard.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mKeyboard.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onKeyboardChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Input"); - mTextInputIcon = createControlComposite(topParent, true /* grab_horizontal */); - mTextInput = new Combo(mTextInputIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); - TextInputMethod[] timValues = TextInputMethod.values(); - mTextInput.add("(Default)"); - for (TextInputMethod value : timValues) { - mTextInput.add(value.getDisplayValue()); - } - mTextInput.select(0); - mTextInput.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mTextInput.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onTextInputChange(); - } - }); - - new Label(topParent, SWT.NONE).setText("Nav"); - mNavigationIcon = createControlComposite(topParent, true /* grab_horizontal */); - mNavigation = new Combo(mNavigationIcon.getParent(), SWT.DROP_DOWN | SWT.READ_ONLY); - NavigationMethod[] nValues = NavigationMethod.values(); - mNavigation.add("(Default)"); - for (NavigationMethod value : nValues) { - mNavigation.add(value.getDisplayValue()); - } - mNavigation.select(0); - mNavigation.setLayoutData(new GridData( - GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - mNavigation.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onNavigationChange(); - } - }); - - Composite labelParent = new Composite(topParent, SWT.NONE); - labelParent.setLayout(gl = new GridLayout(8, false)); - gl.marginWidth = gl.marginHeight = 0; - labelParent.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - gd.horizontalSpan = cols; - - new Label(labelParent, SWT.NONE).setText("Editing config:"); - mCurrentLayoutLabel = new Label(labelParent, SWT.NONE); - mCurrentLayoutLabel.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); - gd.widthHint = 50; - - new Label(labelParent, SWT.NONE).setText("Size"); - mSizeIcon = createControlComposite(labelParent, false); - Composite sizeParent = new Composite(mSizeIcon.getParent(), SWT.NONE); - sizeParent.setLayout(gl = new GridLayout(3, false)); - gl.marginWidth = gl.marginHeight = 0; - gl.horizontalSpacing = 0; - - mSize1 = new Text(sizeParent, SWT.BORDER); - mSize1.setLayoutData(gd = new GridData()); - gd.widthHint = 30; - new Label(sizeParent, SWT.NONE).setText("x"); - mSize2 = new Text(sizeParent, SWT.BORDER); - mSize2.setLayoutData(gd = new GridData()); - gd.widthHint = 30; - - DimensionVerifier verifier = new DimensionVerifier(); - mSize1.addVerifyListener(verifier); - mSize2.addVerifyListener(verifier); - - SelectionListener sl = new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onSizeChange(); - } - public void widgetSelected(SelectionEvent e) { - onSizeChange(); - } - }; - - mSize1.addSelectionListener(sl); - mSize2.addSelectionListener(sl); - - ModifyListener sizeModifyListener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - onSizeChange(); - } - }; - - mSize1.addModifyListener(sizeModifyListener); - mSize2.addModifyListener(sizeModifyListener); - - // first separator - Label separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL); - separator.setLayoutData(gd = new GridData( - GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); - gd.heightHint = 0; - - mThemeCombo = new Combo(labelParent, SWT.READ_ONLY | SWT.DROP_DOWN); - mThemeCombo.setEnabled(false); - updateUIFromResources(); - mThemeCombo.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onThemeChange(); - } - }); - - // second separator - separator = new Label(labelParent, SWT.SEPARATOR | SWT.VERTICAL); - separator.setLayoutData(gd = new GridData( - GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL)); - gd.heightHint = 0; - - mCreateButton = new Button(labelParent, SWT.PUSH | SWT.FLAT); - mCreateButton.setText("Create..."); - mCreateButton.setEnabled(false); - mCreateButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - LayoutCreatorDialog dialog = new LayoutCreatorDialog(mCreateButton.getShell(), - mEditedFile.getName(), mCurrentConfig); - if (dialog.open() == Dialog.OK) { - final FolderConfiguration config = new FolderConfiguration(); - dialog.getConfiguration(config); - - createAlternateLayout(config); - } - } - }); - - // create a new composite that will contain the standard editor controls. - Composite editorParent = new Composite(parent, SWT.NONE); - editorParent.setLayoutData(new GridData(GridData.FILL_BOTH)); - editorParent.setLayout(new FillLayout()); - super.createPartControl(editorParent); - } - - @Override - public void dispose() { - if (mFrameworkResourceChangeListener != null) { - EditorsPlugin.getDefault().removeResourceChangedListener( - mFrameworkResourceChangeListener); - mFrameworkResourceChangeListener = null; - } - - LayoutReloadMonitor.getMonitor().removeListener(mEditedFile.getProject(), this); - - if (mClipboard != null) { - mClipboard.dispose(); - mClipboard = null; - } - - super.dispose(); - } - - /* (non-Javadoc) - * Creates the palette root. - */ - protected PaletteRoot getPaletteRoot() { - return PaletteFactory.createPaletteRoot(); - } - - public Clipboard getClipboard() { - return mClipboard; - } - - /** - * Save operation in the Graphical Layout Editor. - *

              - * In our workflow, the model is owned by the Structured XML Editor. - * The graphical layout editor just displays it -- thus we don't really - * save anything here. - *

              - * This must NOT call the parent editor part. At the contrary, the parent editor - * part will call this *after* having done the actual save operation. - *

              - * The only action this editor must do is mark the undo command stack as - * being no longer dirty. - */ - @Override - public void doSave(IProgressMonitor monitor) { - getCommandStack().markSaveLocation(); - firePropertyChange(PROP_DIRTY); - } - - /* (non-javadoc) - * Configure the graphical viewer before it receives its contents. - */ - @Override - protected void configureGraphicalViewer() { - super.configureGraphicalViewer(); - - GraphicalViewer viewer = getGraphicalViewer(); - viewer.setEditPartFactory(new UiElementsEditPartFactory(mParent.getDisplay())); - viewer.setRootEditPart(new ScalableFreeformRootEditPart()); - - // TODO: viewer.setKeyHandler() - // TODO: custom ContextMenuProvider => viewer.setContextMenu & registerContextMenu - - viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer)); - viewer.addDropTargetListener(new TemplateTransferDropTargetListener(viewer)); - } - - /* (non-javadoc) - * Set the contents of the GraphicalViewer after it has been created. - */ - @Override - protected void initializeGraphicalViewer() { - GraphicalViewer viewer = getGraphicalViewer(); - viewer.setContents(getModel()); - - IEditorInput input = getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - mEditedFile = fileInput.getFile(); - - updateUIFromResources(); - - LayoutReloadMonitor.getMonitor().addListener(mEditedFile.getProject(), this); - } else { - // really this shouldn't happen! Log it in case it happens - mEditedFile = null; - EditorsPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s", - input.toString()); - } - } - - //-------------- - // Local methods - //-------------- - - public LayoutEditor getLayoutEditor() { - return mLayoutEditor; - } - - /** - * Sets the UI for the edition of a new file. - * @param configuration the configuration of the new file. - */ - public void editNewFile(FolderConfiguration configuration) { - // update the configuration UI - setConfiguration(configuration); - - // enable the create button if the current and edited config are not equals - mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); - } - - public Rectangle getBounds() { - ScreenOrientation orientation = null; - if (mOrientation.getSelectionIndex() == 0) { - orientation = ScreenOrientation.PORTRAIT; - } else { - orientation = ScreenOrientation.getByIndex( - mOrientation.getSelectionIndex() - 1); - } - - int s1, s2; - - // get the size from the UI controls. If it fails, revert to default values. - try { - s1 = Integer.parseInt(mSize1.getText().trim()); - } catch (NumberFormatException e) { - s1 = 480; - } - - try { - s2 = Integer.parseInt(mSize2.getText().trim()); - } catch (NumberFormatException e) { - s2 = 320; - } - - // make sure s1 is bigger than s2 - if (s1 < s2) { - int tmp = s1; - s1 = s2; - s2 = tmp; - } - - switch (orientation) { - default: - case PORTRAIT: - return new Rectangle(0, 0, s2, s1); - case LANDSCAPE: - return new Rectangle(0, 0, s1, s2); - case SQUARE: - return new Rectangle(0, 0, s1, s1); - } - } - - /** - * Reloads this editor, by getting the new model from the {@link LayoutEditor}. - */ - void reloadEditor() { - GraphicalViewer viewer = getGraphicalViewer(); - viewer.setContents(getModel()); - - IEditorInput input = mLayoutEditor.getEditorInput(); - setInput(input); - - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - mEditedFile = fileInput.getFile(); - } else { - // really this shouldn't happen! Log it in case it happens - mEditedFile = null; - EditorsPlugin.log(IStatus.ERROR, "Input is not of type FileEditorInput: %1$s", - input.toString()); - } - } - - /** - * Update the layout editor when the Xml model is changed. - */ - void onXmlModelChanged() { - GraphicalViewer viewer = getGraphicalViewer(); - - // try to preserve the selection before changing the content - SelectionManager selMan = viewer.getSelectionManager(); - ISelection selection = selMan.getSelection(); - - try { - viewer.setContents(getModel()); - } finally { - selMan.setSelection(selection); - } - - if (mLayoutEditor.isGraphicalEditorActive()) { - recomputeLayout(); - } else { - mNeedsRecompute = true; - } - } - - /** - * Update the UI controls state with a given {@link FolderConfiguration}. - *

              If a qualifier is not present in the {@link FolderConfiguration} object, the UI control - * is not modified. However if the value in the control is not the default value, a warning - * icon is showed. - */ - void setConfiguration(FolderConfiguration config) { - mDisableUpdates = true; // we do not want to trigger onXXXChange when setting new values in the widgets. - - mEditedConfig = config; - mConfiguredFrameworkRes = mConfiguredProjectRes = null; - - mCountryIcon.setImage(mMatchImage); - CountryCodeQualifier countryQualifier = config.getCountryCodeQualifier(); - if (countryQualifier != null) { - mCountry.setText(String.format("%1$d", countryQualifier.getCode())); - mCurrentConfig.setCountryCodeQualifier(countryQualifier); - } else if (mCountry.getText().length() > 0) { - mCountryIcon.setImage(mWarningImage); - } - - mNetworkIcon.setImage(mMatchImage); - NetworkCodeQualifier networkQualifier = config.getNetworkCodeQualifier(); - if (networkQualifier != null) { - mNetwork.setText(String.format("%1$d", networkQualifier.getCode())); - mCurrentConfig.setNetworkCodeQualifier(networkQualifier); - } else if (mNetwork.getText().length() > 0) { - mNetworkIcon.setImage(mWarningImage); - } - - mLanguageIcon.setImage(mMatchImage); - LanguageQualifier languageQualifier = config.getLanguageQualifier(); - if (languageQualifier != null) { - mLanguage.setText(languageQualifier.getValue()); - mCurrentConfig.setLanguageQualifier(languageQualifier); - } else if (mLanguage.getText().length() > 0) { - mLanguageIcon.setImage(mWarningImage); - } - - mRegionIcon.setImage(mMatchImage); - RegionQualifier regionQualifier = config.getRegionQualifier(); - if (regionQualifier != null) { - mRegion.setText(regionQualifier.getValue()); - mCurrentConfig.setRegionQualifier(regionQualifier); - } else if (mRegion.getText().length() > 0) { - mRegionIcon.setImage(mWarningImage); - } - - mOrientationIcon.setImage(mMatchImage); - ScreenOrientationQualifier orientationQualifier = config.getScreenOrientationQualifier(); - if (orientationQualifier != null) { - mOrientation.select( - ScreenOrientation.getIndex(orientationQualifier.getValue()) + 1); - mCurrentConfig.setScreenOrientationQualifier(orientationQualifier); - } else if (mOrientation.getSelectionIndex() != 0) { - mOrientationIcon.setImage(mWarningImage); - } - - mDensityIcon.setImage(mMatchImage); - PixelDensityQualifier densityQualifier = config.getPixelDensityQualifier(); - if (densityQualifier != null) { - mDensity.setText(String.format("%1$d", densityQualifier.getValue())); - mCurrentConfig.setPixelDensityQualifier(densityQualifier); - } else if (mDensity.getText().length() > 0) { - mDensityIcon.setImage(mWarningImage); - } - - mTouchIcon.setImage(mMatchImage); - TouchScreenQualifier touchQualifier = config.getTouchTypeQualifier(); - if (touchQualifier != null) { - mTouch.select(TouchScreenType.getIndex(touchQualifier.getValue()) + 1); - mCurrentConfig.setTouchTypeQualifier(touchQualifier); - } else if (mTouch.getSelectionIndex() != 0) { - mTouchIcon.setImage(mWarningImage); - } - - mKeyboardIcon.setImage(mMatchImage); - KeyboardStateQualifier keyboardQualifier = config.getKeyboardStateQualifier(); - if (keyboardQualifier != null) { - mKeyboard.select(KeyboardState.getIndex(keyboardQualifier.getValue()) + 1); - mCurrentConfig.setKeyboardStateQualifier(keyboardQualifier); - } else if (mKeyboard.getSelectionIndex() != 0) { - mKeyboardIcon.setImage(mWarningImage); - } - - mTextInputIcon.setImage(mMatchImage); - TextInputMethodQualifier inputQualifier = config.getTextInputMethodQualifier(); - if (inputQualifier != null) { - mTextInput.select(TextInputMethod.getIndex(inputQualifier.getValue()) + 1); - mCurrentConfig.setTextInputMethodQualifier(inputQualifier); - } else if (mTextInput.getSelectionIndex() != 0) { - mTextInputIcon.setImage(mWarningImage); - } - - mNavigationIcon.setImage(mMatchImage); - NavigationMethodQualifier navigationQualifiter = config.getNavigationMethodQualifier(); - if (navigationQualifiter != null) { - mNavigation.select( - NavigationMethod.getIndex(navigationQualifiter.getValue()) + 1); - mCurrentConfig.setNavigationMethodQualifier(navigationQualifiter); - } else if (mNavigation.getSelectionIndex() != 0) { - mNavigationIcon.setImage(mWarningImage); - } - - mSizeIcon.setImage(mMatchImage); - ScreenDimensionQualifier sizeQualifier = config.getScreenDimensionQualifier(); - if (sizeQualifier != null) { - mSize1.setText(String.format("%1$d", sizeQualifier.getValue1())); - mSize2.setText(String.format("%1$d", sizeQualifier.getValue2())); - mCurrentConfig.setScreenDimensionQualifier(sizeQualifier); - } else if (mSize1.getText().length() > 0 && mSize2.getText().length() > 0) { - mSizeIcon.setImage(mWarningImage); - } - - // update the string showing the folder name - String current = config.toDisplayString(); - mCurrentLayoutLabel.setText(current != null ? current : "(Default)"); - - mDisableUpdates = false; - } - - /** - * Displays an error icon in front of all the non-null qualifiers. - */ - void displayConfigError() { - mCountryIcon.setImage(mMatchImage); - CountryCodeQualifier countryQualifier = mCurrentConfig.getCountryCodeQualifier(); - if (countryQualifier != null) { - mCountryIcon.setImage(mErrorImage); - } - - mNetworkIcon.setImage(mMatchImage); - NetworkCodeQualifier networkQualifier = mCurrentConfig.getNetworkCodeQualifier(); - if (networkQualifier != null) { - mNetworkIcon.setImage(mErrorImage); - } - - mLanguageIcon.setImage(mMatchImage); - LanguageQualifier languageQualifier = mCurrentConfig.getLanguageQualifier(); - if (languageQualifier != null) { - mLanguageIcon.setImage(mErrorImage); - } - - mRegionIcon.setImage(mMatchImage); - RegionQualifier regionQualifier = mCurrentConfig.getRegionQualifier(); - if (regionQualifier != null) { - mRegionIcon.setImage(mErrorImage); - } - - mOrientationIcon.setImage(mMatchImage); - ScreenOrientationQualifier orientationQualifier = - mCurrentConfig.getScreenOrientationQualifier(); - if (orientationQualifier != null) { - mOrientationIcon.setImage(mErrorImage); - } - - mDensityIcon.setImage(mMatchImage); - PixelDensityQualifier densityQualifier = mCurrentConfig.getPixelDensityQualifier(); - if (densityQualifier != null) { - mDensityIcon.setImage(mErrorImage); - } - - mTouchIcon.setImage(mMatchImage); - TouchScreenQualifier touchQualifier = mCurrentConfig.getTouchTypeQualifier(); - if (touchQualifier != null) { - mTouchIcon.setImage(mErrorImage); - } - - mKeyboardIcon.setImage(mMatchImage); - KeyboardStateQualifier keyboardQualifier = mCurrentConfig.getKeyboardStateQualifier(); - if (keyboardQualifier != null) { - mKeyboardIcon.setImage(mErrorImage); - } - - mTextInputIcon.setImage(mMatchImage); - TextInputMethodQualifier inputQualifier = mCurrentConfig.getTextInputMethodQualifier(); - if (inputQualifier != null) { - mTextInputIcon.setImage(mErrorImage); - } - - mNavigationIcon.setImage(mMatchImage); - NavigationMethodQualifier navigationQualifiter = - mCurrentConfig.getNavigationMethodQualifier(); - if (navigationQualifiter != null) { - mNavigationIcon.setImage(mErrorImage); - } - - mSizeIcon.setImage(mMatchImage); - ScreenDimensionQualifier sizeQualifier = mCurrentConfig.getScreenDimensionQualifier(); - if (sizeQualifier != null) { - mSizeIcon.setImage(mErrorImage); - } - - // update the string showing the folder name - String current = mCurrentConfig.toDisplayString(); - mCurrentLayoutLabel.setText(current != null ? current : "(Default)"); - } - - UiDocumentNode getModel() { - return mLayoutEditor.getUiRootNode(); - } - - private void onCountryCodeChange() { - // because mCountry triggers onCountryCodeChange at each modification, calling setText() - // will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String value = mCountry.getText(); - - // empty string, means no qualifier. - if (value.length() == 0) { - mCurrentConfig.setCountryCodeQualifier(null); - } else { - try { - CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier( - CountryCodeQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mCurrentConfig.setCountryCodeQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - // We do nothing in this case. - mCountryIcon.setImage(mErrorImage); - return; - } - } catch (NumberFormatException e) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - mCurrentConfig.setCountryCodeQualifier(null); - mCountryIcon.setImage(mErrorImage); - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onNetworkCodeChange() { - // because mNetwork triggers onNetworkCodeChange at each modification, calling setText() - // will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String value = mNetwork.getText(); - - // empty string, means no qualifier. - if (value.length() == 0) { - mCurrentConfig.setNetworkCodeQualifier(null); - } else { - try { - NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier( - NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mCurrentConfig.setNetworkCodeQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - // We do nothing in this case. - mNetworkIcon.setImage(mErrorImage); - return; - } - } catch (NumberFormatException e) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - mCurrentConfig.setNetworkCodeQualifier(null); - mNetworkIcon.setImage(mErrorImage); - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - /** - * Call back for language combo selection - */ - private void onLanguageChange() { - // because mLanguage triggers onLanguageChange at each modification, the filling - // of the combo with data will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String value = mLanguage.getText(); - - updateRegionUi(null /* projectResources */, null /* frameworkResources */); - - // empty string, means no qualifier. - if (value.length() == 0) { - mCurrentConfig.setLanguageQualifier(null); - } else { - LanguageQualifier qualifier = null; - String segment = LanguageQualifier.getFolderSegment(value); - if (segment != null) { - qualifier = LanguageQualifier.getQualifier(segment); - } - - if (qualifier != null) { - mCurrentConfig.setLanguageQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - mCurrentConfig.setLanguageQualifier(null); - mLanguageIcon.setImage(mErrorImage); - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onRegionChange() { - // because mRegion triggers onRegionChange at each modification, the filling - // of the combo with data will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String value = mRegion.getText(); - - // empty string, means no qualifier. - if (value.length() == 0) { - mCurrentConfig.setRegionQualifier(null); - } else { - RegionQualifier qualifier = null; - String segment = RegionQualifier.getFolderSegment(value); - if (segment != null) { - qualifier = RegionQualifier.getQualifier(segment); - } - - if (qualifier != null) { - mCurrentConfig.setRegionQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - mCurrentConfig.setRegionQualifier(null); - mRegionIcon.setImage(mErrorImage); - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onOrientationChange() { - // update the current config - int index = mOrientation.getSelectionIndex(); - if (index != 0) { - mCurrentConfig.setScreenOrientationQualifier(new ScreenOrientationQualifier( - ScreenOrientation.getByIndex(index-1))); - } else { - mCurrentConfig.setScreenOrientationQualifier(null); - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onDensityChange() { - // because mDensity triggers onDensityChange at each modification, calling setText() - // will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String value = mDensity.getText(); - - // empty string, means no qualifier. - if (value.length() == 0) { - mCurrentConfig.setPixelDensityQualifier(null); - } else { - try { - PixelDensityQualifier qualifier = PixelDensityQualifier.getQualifier( - PixelDensityQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mCurrentConfig.setPixelDensityQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - // We do nothing in this case. - return; - } - } catch (NumberFormatException e) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - // We do nothing in this case. - mDensityIcon.setImage(mErrorImage); - return; - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onTouchChange() { - // update the current config - int index = mTouch.getSelectionIndex(); - if (index != 0) { - mCurrentConfig.setTouchTypeQualifier(new TouchScreenQualifier( - TouchScreenType.getByIndex(index-1))); - } else { - mCurrentConfig.setTouchTypeQualifier(null); - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onKeyboardChange() { - // update the current config - int index = mKeyboard.getSelectionIndex(); - if (index != 0) { - mCurrentConfig.setKeyboardStateQualifier(new KeyboardStateQualifier( - KeyboardState.getByIndex(index-1))); - } else { - mCurrentConfig.setKeyboardStateQualifier(null); - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onTextInputChange() { - // update the current config - int index = mTextInput.getSelectionIndex(); - if (index != 0) { - mCurrentConfig.setTextInputMethodQualifier(new TextInputMethodQualifier( - TextInputMethod.getByIndex(index-1))); - } else { - mCurrentConfig.setTextInputMethodQualifier(null); - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onNavigationChange() { - // update the current config - int index = mNavigation.getSelectionIndex(); - if (index != 0) { - mCurrentConfig.setNavigationMethodQualifier(new NavigationMethodQualifier( - NavigationMethod.getByIndex(index-1))); - } else { - mCurrentConfig.setNavigationMethodQualifier(null); - } - - // look for a file to open/create - onConfigurationChange(); - } - - private void onSizeChange() { - // because mSize1 and mSize2 trigger onSizeChange at each modification, calling setText() - // will trigger notifications, and we don't want that. - if (mDisableUpdates == true) { - return; - } - - // update the current config - String size1 = mSize1.getText(); - String size2 = mSize2.getText(); - - // if only one of the strings is empty, do nothing - if ((size1.length() == 0) ^ (size2.length() == 0)) { - mSizeIcon.setImage(mErrorImage); - return; - } else if (size1.length() == 0 && size2.length() == 0) { - // both sizes are empty: remove the qualifier. - mCurrentConfig.setScreenDimensionQualifier(null); - } else { - ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1, - size2); - - if (qualifier != null) { - mCurrentConfig.setScreenDimensionQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong. - // we do nothing in this case. - return; - } - } - - // look for a file to open/create - onConfigurationChange(); - } - - - /** - * Looks for a file matching the new {@link FolderConfiguration} and attempts to open it. - *

              If there is no match, notify the user. - */ - private void onConfigurationChange() { - mConfiguredFrameworkRes = mConfiguredProjectRes = null; - - if (mEditedFile == null || mEditedConfig == null) { - return; - } - - // get the resources of the file's project. - ProjectResources resources = ResourceManager.getInstance().getProjectResources( - mEditedFile.getProject()); - - // from the resources, look for a matching file - ResourceFile match = null; - if (resources != null) { - match = resources.getMatchingFile(mEditedFile.getName(), - ResourceFolderType.LAYOUT, - mCurrentConfig); - } - - if (match != null) { - if (match.getFile().equals(mEditedFile) == false) { - try { - IDE.openEditor( - getSite().getWorkbenchWindow().getActivePage(), - match.getFile().getIFile()); - - // we're done! - return; - } catch (PartInitException e) { - // FIXME: do something! - } - } - - // at this point, we have not opened a new file. - - // update the configuration icons with the new edited config. - setConfiguration(mEditedConfig); - - // enable the create button if the current and edited config are not equals - mCreateButton.setEnabled(mEditedConfig.equals(mCurrentConfig) == false); - - // Even though the layout doesn't change, the config changed, and referenced - // resources need to be updated. - recomputeLayout(); - } else { - // update the configuration icons with the new edited config. - displayConfigError(); - - // enable the Create button - mCreateButton.setEnabled(true); - - // display the error. - String message = String.format( - "No resources match the configuration\n \n\t%1$s\n \nChange the configuration or create:\n \n\tres/%2$s/%3$s\n \nYou can also click the 'Create' button above.", - mCurrentConfig.toDisplayString(), - mCurrentConfig.getFolderName(ResourceFolderType.LAYOUT), - mEditedFile.getName()); - showErrorInEditor(message); - } - } - - private void onThemeChange() { - int themeIndex = mThemeCombo.getSelectionIndex(); - if (themeIndex != -1) { - String theme = mThemeCombo.getItem(themeIndex); - - if (theme.equals(THEME_SEPARATOR)) { - mThemeCombo.select(0); - } - - recomputeLayout(); - } - } - - /** - * Creates a composite with no margin/spacing, and puts a {@link Label} in it with the matching - * icon. - * @param parent the parent to receive the composite - * @return the created {@link Label} object. - */ - private Label createControlComposite(Composite parent, boolean grab) { - GridLayout gl; - - Composite composite = new Composite(parent, SWT.NONE); - composite.setLayout(gl = new GridLayout(2, false)); - gl.marginHeight = gl.marginWidth = 0; - gl.horizontalSpacing = 0; - if (grab) { - composite.setLayoutData( - new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL)); - } - - // create the label - Label icon = new Label(composite, SWT.NONE); - icon.setImage(mMatchImage); - - return icon; - } - - /** - * Recomputes the layout with the help of layoutlib. - */ - void recomputeLayout() { - try { - // check that the resource exists. If the file is opened but the project is closed - // or deleted for some reason (changed from outside of eclipse), then this will - // return false; - if (mEditedFile.exists() == false) { - String message = String.format("Resource '%1$s' does not exist.", - mEditedFile.getFullPath().toString()); - - showErrorInEditor(message); - - return; - } - - IProject iProject = mEditedFile.getProject(); - - if (mEditedFile.isSynchronized(IResource.DEPTH_ZERO) == false) { - String message = String.format("%1$s is out of sync. Please refresh.", - mEditedFile.getName()); - - showErrorInEditor(message); - - // also print it in the error console. - EditorsPlugin.printErrorToConsole(iProject.getName(), message); - return; - } - - // check there is actually a model (maybe the file is empty). - UiDocumentNode model = getModel(); - - if (model.getUiChildren().size() == 0) { - showErrorInEditor("No Xml content. Go to the Outline view and add nodes."); - return; - } - - EditorsPlugin plugin = EditorsPlugin.getDefault(); - LayoutBridge bridge = plugin.getLayoutBridge(); - - if (bridge.bridge != null) { // bridge can never be null. - ResourceManager resManager = ResourceManager.getInstance(); - - ProjectResources projectRes = resManager.getProjectResources(iProject); - if (projectRes == null) { - return; - } - - // get the resources of the file's project. - if (mConfiguredProjectRes == null) { - // make sure they are loaded - projectRes.loadAll(); - - // get the project resource values based on the current config - mConfiguredProjectRes = projectRes.getConfiguredResources(mCurrentConfig); - } - - // get the framework resources - Map> frameworkResources = - getConfiguredFrameworkResources(); - - if (mConfiguredProjectRes != null && frameworkResources != null) { - if (mProjectCallback == null) { - mProjectCallback = new ProjectCallback( - plugin.getLayoutlibBridgeClassLoader(), projectRes, iProject); - } - - if (mLogger == null) { - mLogger = new ILayoutLog() { - public void error(String message) { - EditorsPlugin.printErrorToConsole(mEditedFile.getName(), message); - } - - public void error(Throwable error) { - String message = error.getMessage(); - if (message == null) { - message = error.getClass().getName(); - } - - PrintStream ps = new PrintStream(EditorsPlugin.getErrorStream()); - error.printStackTrace(ps); - } - - public void warning(String message) { - EditorsPlugin.printToConsole(mEditedFile.getName(), message); - } - }; - } - - // get the selected theme - int themeIndex = mThemeCombo.getSelectionIndex(); - if (themeIndex != -1) { - String theme = mThemeCombo.getItem(themeIndex); - - // change the string if it's a custom theme to make sure we can - // differentiate them - if (themeIndex >= mPlatformThemeCount) { - theme = "*" + theme; //$NON-NLS-1$ - } - - // Compute the layout - UiElementPullParser parser = new UiElementPullParser(getModel()); - Rectangle rect = getBounds(); - ILayoutResult result = bridge.bridge.computeLayout(parser, iProject, - rect.width, rect.height, theme, - mConfiguredProjectRes, frameworkResources, mProjectCallback, - mLogger); - - // update the UiElementNode with the layout info. - if (result.getSuccess() == ILayoutResult.SUCCESS) { - model.setEditData(result.getImage()); - - updateNodeWithBounds(result.getRootView()); - } else { - String message = result.getErrorMessage(); - - // Reset the edit data for all the nodes. - resetNodeBounds(model); - - if (message != null) { - // set the error in the top element. - model.setEditData(message); - } - } - - model.refreshUi(); - } - } - } else { - String message = null; - - // check whether the bridge managed to load, or not - if (bridge.status == LayoutBridge.LoadStatus.LOADING) { - message = String.format( - "Eclipse is loading framework information and the Layout library from the SDK folder.\n%1$s will refresh automatically once the process is finished.", - mEditedFile.getName()); - } else { - message = String.format("Eclipse failed to load the framework information and the Layout library!"); - } - - showErrorInEditor(message); - } - } finally { - // no matter the result, we are done doing the recompute based on the latest - // resource/code change. - mNeedsRecompute = false; - } - } - - private void showErrorInEditor(String message) { - // get the model to display the error directly in the editor - UiDocumentNode model = getModel(); - - // Reset the edit data for all the nodes. - resetNodeBounds(model); - - if (message != null) { - // set the error in the top element. - model.setEditData(message); - } - - model.refreshUi(); - } - - private void resetNodeBounds(UiElementNode node) { - node.setEditData(null); - - List children = node.getUiChildren(); - for (UiElementNode child : children) { - resetNodeBounds(child); - } - } - - private void updateNodeWithBounds(ILayoutViewInfo r) { - if (r != null) { - // update the node itself, as the viewKey is the XML node in this implementation. - Object viewKey = r.getViewKey(); - if (viewKey instanceof UiElementNode) { - Rectangle bounds = new Rectangle(r.getLeft(), r.getTop(), - r.getRight()-r.getLeft(), r.getBottom() - r.getTop()); - - ((UiElementNode)viewKey).setEditData(bounds); - } - - // and then its children. - ILayoutViewInfo[] children = r.getChildren(); - if (children != null) { - for (ILayoutViewInfo child : children) { - updateNodeWithBounds(child); - } - } - } - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.layout.LayoutReloadMonitor.ILayoutReloadListener#reloadLayout(boolean, boolean, boolean) - * - * Called when the file changes triggered a redraw of the layout - */ - public void reloadLayout(boolean codeChange, boolean rChange, boolean resChange) { - boolean recompute = rChange; - - if (resChange) { - recompute = true; - - // TODO: differentiate between single and multi resource file changed, and whether the resource change affects the cache. - - // force a reparse in case a value XML file changed. - mConfiguredProjectRes = null; - - // clear the cache in the bridge in case a bitmap/9-patch changed. - EditorsPlugin plugin = EditorsPlugin.getDefault(); - LayoutBridge bridge = plugin.getLayoutBridge(); - - if (bridge.bridge != null) { - bridge.bridge.clearCaches(mEditedFile.getProject()); - } - - mParent.getDisplay().asyncExec(mUiUpdateFromResourcesRunnable); - } - - if (codeChange) { - // only recompute if the custom view loader was used to load some code. - if (mProjectCallback != null && mProjectCallback.isUsed()) { - mProjectCallback = null; - recompute = true; - } - } - - if (recompute) { - mParent.getDisplay().asyncExec(mConditionalRecomputeRunnable); - } - } - - /** - * Responds to a page change that made the Graphical editor page the activated page. - */ - void activated() { - if (mNeedsRecompute) { - recomputeLayout(); - } - } - - /** - * Updates the UI from values in the resources, such as languages, regions, themes, etc... - * This must be called from the UI thread. - */ - private void updateUIFromResources() { - - ResourceManager manager = ResourceManager.getInstance(); - - ProjectResources frameworkProject = manager.getFrameworkResources(); - - mDisableUpdates = true; - - // Reset stuff - int selection = mThemeCombo.getSelectionIndex(); - mThemeCombo.removeAll(); - mPlatformThemeCount = 0; - mLanguage.removeAll(); - - Set languages = new HashSet(); - ArrayList themes = new ArrayList(); - - // get the themes, and languages from the Framework. - if (frameworkProject != null) { - // get the configured resources for the framework - Map> frameworResources = - getConfiguredFrameworkResources(); - - if (frameworResources != null) { - // get the styles. - Map styles = frameworResources.get( - ResourceType.STYLE.getName()); - - - // collect the themes out of all the styles. - for (IResourceValue value : styles.values()) { - String name = value.getName(); - if (name.startsWith("Theme.") || name.equals("Theme")) { - themes.add(value.getName()); - mPlatformThemeCount++; - } - } - - // sort them and add them to the combo - Collections.sort(themes); - - for (String theme : themes) { - mThemeCombo.add(theme); - } - - mPlatformThemeCount = themes.size(); - themes.clear(); - } - - // now get the languages from the framework. - Set frameworkLanguages = frameworkProject.getLanguages(); - if (frameworkLanguages != null) { - languages.addAll(frameworkLanguages); - } - } - - // now get the themes and languages from the project. - ProjectResources project = null; - if (mEditedFile != null) { - project = manager.getProjectResources(mEditedFile.getProject()); - - // in cases where the opened file is not linked to a project, this could be null. - if (project != null) { - // get the configured resources for the project - if (mConfiguredProjectRes == null) { - // make sure they are loaded - project.loadAll(); - - // get the project resource values based on the current config - mConfiguredProjectRes = project.getConfiguredResources(mCurrentConfig); - } - - if (mConfiguredProjectRes != null) { - // get the styles. - Map styleMap = mConfiguredProjectRes.get( - ResourceType.STYLE.getName()); - - if (styleMap != null) { - // collect the themes out of all the styles, ie styles that extend, - // directly or indirectly a platform theme. - for (IResourceValue value : styleMap.values()) { - if (isTheme(value, styleMap)) { - themes.add(value.getName()); - } - } - - // sort them and add them the to the combo. - if (mPlatformThemeCount > 0 && themes.size() > 0) { - mThemeCombo.add(THEME_SEPARATOR); - } - - Collections.sort(themes); - - for (String theme : themes) { - mThemeCombo.add(theme); - } - } - } - - // now get the languages from the project. - Set projectLanguages = project.getLanguages(); - if (projectLanguages != null) { - languages.addAll(projectLanguages); - } - } - } - - // add the languages to the Combo - for (String language : languages) { - mLanguage.add(language); - } - - mDisableUpdates = false; - - // and update the Region UI based on the current language - updateRegionUi(project, frameworkProject); - - // handle default selection of themes - if (mThemeCombo.getItemCount() > 0) { - mThemeCombo.setEnabled(true); - if (selection == -1) { - selection = 0; - } - - if (mThemeCombo.getItemCount() <= selection) { - mThemeCombo.select(0); - } else { - mThemeCombo.select(selection); - } - } else { - mThemeCombo.setEnabled(false); - } - } - - /** - * Returns whether the given style is a theme. - * This is done by making sure the parent is a theme. - * @param value the style to check - * @param styleMap the map of styles for the current project. Key is the style name. - * @return - */ - private boolean isTheme(IResourceValue value, Map styleMap) { - if (value instanceof IStyleResourceValue) { - IStyleResourceValue style = (IStyleResourceValue)value; - - boolean frameworkStyle = false; - String parentStyle = style.getParentStyle(); - if (parentStyle == null) { - // if there is no specified parent style we look an implied one. - // For instance 'Theme.light' is implied child style of 'Theme', - // and 'Theme.light.fullscreen' is implied child style of 'Theme.light' - String name = style.getName(); - int index = name.lastIndexOf('.'); - if (index != -1) { - parentStyle = name.substring(0, index); - } - } else { - // remove the useless @ if it's there - if (parentStyle.startsWith("@")) { - parentStyle = parentStyle.substring(1); - } - - // check for framework identifier. - if (parentStyle.startsWith("android:")) { - frameworkStyle = true; - parentStyle = parentStyle.substring("android:".length()); - } - - // at this point we could have the format style/. we want only the name - if (parentStyle.startsWith("style/")) { - parentStyle = parentStyle.substring("style/".length()); - } - } - - if (frameworkStyle) { - // if the parent is a framework style, it has to be 'Theme' or 'Theme.*' - return parentStyle.equals("Theme") || parentStyle.startsWith("Theme."); - } else { - // if it's a project style, we check this is a theme. - value = styleMap.get(parentStyle); - if (value != null) { - return isTheme(value, styleMap); - } - } - } - - return false; - } - - /** - * Update the Region UI widget based on the current language selection - * @param projectResources the project resources or {@code null}. - * @param frameworkResources the framework resource or {@code null} - */ - private void updateRegionUi(ProjectResources projectResources, - ProjectResources frameworkResources) { - if (projectResources == null && mEditedFile != null) { - projectResources = ResourceManager.getInstance().getProjectResources( - mEditedFile.getProject()); - } - - if (frameworkResources == null) { - frameworkResources = ResourceManager.getInstance().getFrameworkResources(); - } - - String currentLanguage = mLanguage.getText(); - - Set set = null; - - if (projectResources != null) { - set = projectResources.getRegions(currentLanguage); - } - - if (frameworkResources != null) { - if (set != null) { - Set set2 = frameworkResources.getRegions(currentLanguage); - set.addAll(set2); - } else { - set = frameworkResources.getRegions(currentLanguage); - } - } - - if (set != null) { - mDisableUpdates = true; - - mRegion.removeAll(); - for (String region : set) { - mRegion.add(region); - } - - mDisableUpdates = false; - } - } - - private Map> getConfiguredFrameworkResources() { - if (mConfiguredFrameworkRes == null) { - ProjectResources frameworkRes = ResourceManager.getInstance().getFrameworkResources(); - - if (frameworkRes == null) { - EditorsPlugin.log(IStatus.ERROR, "Failed to get ProjectResource for the framework"); - } - - // get the framework resource values based on the current config - mConfiguredFrameworkRes = frameworkRes.getConfiguredResources(mCurrentConfig); - } - - return mConfiguredFrameworkRes; - } - - /** - * Returns the selection synchronizer object. - * The synchronizer can be used to sync the selection of 2 or more EditPartViewers. - *

              - * This is changed from protected to public so that the outline can use it. - * - * @return the synchronizer - */ - @Override - public SelectionSynchronizer getSelectionSynchronizer() { - return super.getSelectionSynchronizer(); - } - - /** - * Returns the edit domain. - *

              - * This is changed from protected to public so that the outline can use it. - * - * @return the edit domain - */ - @Override - public DefaultEditDomain getEditDomain() { - return super.getEditDomain(); - } - - /** - * Creates a new layout file from the specificed {@link FolderConfiguration}. - */ - private void createAlternateLayout(final FolderConfiguration config) { - new Job("Create Alternate Resource") { - @Override - protected IStatus run(IProgressMonitor monitor) { - // get the folder name - String folderName = config.getFolderName(ResourceFolderType.LAYOUT); - try { - - // look to see if it exists. - // get the res folder - IFolder res = (IFolder)mEditedFile.getParent().getParent(); - String path = res.getLocation().toOSString(); - - File newLayoutFolder = new File(path + File.separator + folderName); - if (newLayoutFolder.isFile()) { - // this should not happen since aapt would have complained - // before, but if one disable the automatic build, this could - // happen. - String message = String.format("File 'res/%1$s' is in the way!", - folderName); - - EditorsPlugin.displayError("Layout Creation", message); - - return new Status(IStatus.ERROR, - AndroidConstants.EDITORS_PLUGIN_ID, message); - } else if (newLayoutFolder.exists() == false) { - // create it. - newLayoutFolder.mkdir(); - } - - // now create the file - File newLayoutFile = new File(newLayoutFolder.getAbsolutePath() + - File.separator + mEditedFile.getName()); - - newLayoutFile.createNewFile(); - - InputStream input = mEditedFile.getContents(); - - FileOutputStream fos = new FileOutputStream(newLayoutFile); - - byte[] data = new byte[512]; - int count; - while ((count = input.read(data)) != -1) { - fos.write(data, 0, count); - } - - input.close(); - fos.close(); - - // refreshes the res folder to show up the new - // layout folder (if needed) and the file. - // We use a progress monitor to catch the end of the refresh - // to trigger the edit of the new file. - res.refreshLocal(IResource.DEPTH_INFINITE, new IProgressMonitor() { - public void done() { - mCurrentConfig.set(config); - mParent.getDisplay().asyncExec(new Runnable() { - public void run() { - onConfigurationChange(); - }; - }); - } - - public void beginTask(String name, int totalWork) { - // pass - } - - public void internalWorked(double work) { - // pass - } - - public boolean isCanceled() { - // pass - return false; - } - - public void setCanceled(boolean value) { - // pass - } - - public void setTaskName(String name) { - // pass - } - - public void subTask(String name) { - // pass - } - - public void worked(int work) { - // pass - } - }); - } catch (IOException e2) { - String message = String.format( - "Failed to create File 'res/%1$s/%2$s' : %3$s", - folderName, mEditedFile.getName(), e2.getMessage()); - - EditorsPlugin.displayError("Layout Creation", message); - - return new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID, - message, e2); - } catch (CoreException e2) { - String message = String.format( - "Failed to create File 'res/%1$s/%2$s' : %3$s", - folderName, mEditedFile.getName(), e2.getMessage()); - - EditorsPlugin.displayError("Layout Creation", message); - - return e2.getStatus(); - } - - return Status.OK_STATUS; - - } - }.schedule(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java deleted file mode 100644 index dfec17a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutContentAssist.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.layout; - -import com.android.ide.eclipse.editors.AndroidContentAssist; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; - -/** - * Content Assist Processor for /res/layout XML files - */ -class LayoutContentAssist extends AndroidContentAssist { - - /** - * Constructor for LayoutContentAssist - */ - public LayoutContentAssist() { - super(LayoutDescriptors.getInstance().getDescriptor().getChildren()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java deleted file mode 100644 index c4a8f5c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutCreatorDialog.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState; - -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** - * Dialog to choose a non existing {@link FolderConfiguration}. - */ -class LayoutCreatorDialog extends TrayDialog { - - private ConfigurationSelector mSelector; - private Composite mStatusComposite; - private Label mStatusLabel; - private Label mStatusImage; - - private final FolderConfiguration mConfig = new FolderConfiguration(); - private final String mFileName; - - /** - * Creates a dialog, and init the UI from a {@link FolderConfiguration}. - * @param parentShell the parent {@link Shell}. - * @param config The starting configuration. - */ - LayoutCreatorDialog(Shell parentShell, String fileName, FolderConfiguration config) { - super(parentShell); - - mFileName = fileName; - // FIXME: add some data to know what configurations already exist. - mConfig.set(config); - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite top = new Composite(parent, SWT.NONE); - top.setLayoutData(new GridData()); - top.setLayout(new GridLayout(1, false)); - - new Label(top, SWT.NONE).setText( - String.format("Configuration for the alternate version of %1$s", mFileName)); - - mSelector = new ConfigurationSelector(top); - mSelector.setConfiguration(mConfig); - - // parent's layout is a GridLayout as specified in the javadoc. - GridData gd = new GridData(); - gd.widthHint = ConfigurationSelector.WIDTH_HINT; - gd.heightHint = ConfigurationSelector.HEIGHT_HINT; - mSelector.setLayoutData(gd); - - // add a listener to check on the validity of the FolderConfiguration as - // they are built. - mSelector.setOnChangeListener(new Runnable() { - public void run() { - ConfigurationState state = mSelector.getState(); - - switch (state) { - case OK: - mSelector.getConfiguration(mConfig); - - resetStatus(); - mStatusImage.setImage(null); - getButton(IDialogConstants.OK_ID).setEnabled(true); - break; - case INVALID_CONFIG: - ResourceQualifier invalidQualifier = mSelector.getInvalidQualifier(); - mStatusLabel.setText(String.format( - "Invalid Configuration: %1$s has no filter set.", - invalidQualifier.getName())); - mStatusImage.setImage(IconFactory.getInstance().getIcon("warning")); //$NON-NLS-1$ - getButton(IDialogConstants.OK_ID).setEnabled(false); - break; - case REGION_WITHOUT_LANGUAGE: - mStatusLabel.setText( - "The Region qualifier requires the Language qualifier."); - mStatusImage.setImage(IconFactory.getInstance().getIcon("warning")); //$NON-NLS-1$ - getButton(IDialogConstants.OK_ID).setEnabled(false); - break; - } - - // need to relayout, because of the change in size in mErrorImage. - mStatusComposite.layout(); - } - }); - - mStatusComposite = new Composite(top, SWT.NONE); - mStatusComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - GridLayout gl = new GridLayout(2, false); - mStatusComposite.setLayout(gl); - gl.marginHeight = gl.marginWidth = 0; - - mStatusImage = new Label(mStatusComposite, SWT.NONE); - mStatusLabel = new Label(mStatusComposite, SWT.NONE); - mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - resetStatus(); - - return top; - } - - public void getConfiguration(FolderConfiguration config) { - config.set(mConfig); - } - - /** - * resets the status label to show the file that will be created. - */ - private void resetStatus() { - mStatusLabel.setText(String.format("New File: res/%1$s/%2$s", - mConfig.getFolderName(ResourceFolderType.LAYOUT), mFileName)); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java deleted file mode 100644 index f1bedcb..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutEditor.java +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.layout; - -import com.android.ide.eclipse.common.EclipseUiHelper; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.gef.ui.parts.TreeViewer; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IPartListener; -import org.eclipse.ui.IShowEditorInput; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.IWorkbenchPartSite; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; -import org.eclipse.ui.views.contentoutline.IContentOutlinePage; -import org.eclipse.ui.views.properties.IPropertySheetPage; -import org.w3c.dom.Document; - -/** - * Multi-page form editor for /res/layout XML files. - */ -public class LayoutEditor extends AndroidEditor implements IShowEditorInput, IPartListener { - - public static final String ID = "com.android.ide.eclipse.editors.layout.LayoutEditor"; //$NON-NLS-1$ - - /** Root node of the UI element hierarchy */ - private UiDocumentNode mUiRootNode; - /** Listener to update the root node if the resource framework changes */ - private Runnable mResourceRefreshListener; - - private GraphicalLayoutEditor mGraphicalEditor; - private int mGraphicalEditorIndex; - /** Implementation of the {@link IContentOutlinePage} for this editor */ - private UiContentOutlinePage mOutline; - /** Custom implementation of {@link IPropertySheetPage} for this editor */ - private UiPropertySheetPage mPropertyPage; - - - /** - * Creates the form editor for resources XML files. - */ - public LayoutEditor() { - super(); - initUiRootNode(); - } - - /** - * @return The root node of the UI element hierarchy - */ - @Override - public UiDocumentNode getUiRootNode() { - return mUiRootNode; - } - - // ---- Base Class Overrides ---- - - @Override - public void dispose() { - if (mResourceRefreshListener != null) { - EditorsPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener = null; - } - getSite().getPage().removePartListener(this); - - super.dispose(); - } - - /** - * Save the XML. - *

              - * The actual save operation is done in the super class by committing - * all data to the XML model and then having the Structured XML Editor - * save the XML. - *

              - * Here we just need to tell the graphical editor that the model has - * been saved. - */ - @Override - public void doSave(IProgressMonitor monitor) { - super.doSave(monitor); - if (mGraphicalEditor != null) { - mGraphicalEditor.doSave(monitor); - } - } - - /** - * Returns whether the "save as" operation is supported by this editor. - *

              - * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return true; - } - - /** - * Create the various form pages. - */ - @Override - protected void createFormPages() { - try { - // The graphical layout editor is now enabled by default. - // In case there's an issue we provide a way to disable it using an - // env variable. - if (System.getenv("ANDROID_DISABLE_LAYOUT") == null) { - if (mGraphicalEditor == null) { - mGraphicalEditor = new GraphicalLayoutEditor(this); - mGraphicalEditorIndex = addPage(mGraphicalEditor, getEditorInput()); - setPageText(mGraphicalEditorIndex, mGraphicalEditor.getTitle()); - } else { - mGraphicalEditor.reloadEditor(); - } - - // update the config based on the opened file. - IEditorInput input = getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder( - fileInput.getFile()); - if (resFolder != null) { - mGraphicalEditor.editNewFile(resFolder.getConfiguration()); - } - } - - // put in place the listener to handle layout recompute only when needed. - getSite().getPage().addPartListener(this); - } - } catch (PartInitException e) { - EditorsPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ - } - } - - /* (non-java doc) - * Change the tab/title name to include the name of the layout. - */ - @Override - protected void setInput(IEditorInput input) { - super.setInput(input); - handleNewInput(input); - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.part.EditorPart#setInputWithNotify(org.eclipse.ui.IEditorInput) - */ - @Override - protected void setInputWithNotify(IEditorInput input) { - super.setInputWithNotify(input); - handleNewInput(input); - } - - /** - * Called to replace the current {@link IEditorInput} with another one. - *

              This is used when {@link MatchingStrategy} returned true which means we're - * opening a different configuration of the same layout. - */ - public void showEditorInput(IEditorInput editorInput) { - // save the current editor input. - doSave(new NullProgressMonitor()); - - // get the current page - int currentPage = getActivePage(); - - // remove the pages, except for the graphical editor, which will be dynamically adapted - // to the new model. - // page after the graphical editor: - int count = getPageCount(); - for (int i = count - 1 ; i > mGraphicalEditorIndex ; i--) { - removePage(i); - } - // pages before the graphical editor - for (int i = mGraphicalEditorIndex - 1 ; i >= 0 ; i--) { - removePage(i); - } - - // set the current input. - setInputWithNotify(editorInput); - - // re-create or reload the pages with the default page shown as the previous active page. - createAndroidPages(); - selectDefaultPage(Integer.toString(currentPage)); - - // update the outline - if (mOutline != null && mGraphicalEditor != null) { - mOutline.reloadModel(); - } - } - - /** - * Processes the new XML Model, which XML root node is given. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - @Override - protected void xmlModelChanged(Document xml_doc) { - mUiRootNode.loadFromXmlNode(xml_doc); - - // update the model first, since it is used by the viewers. - super.xmlModelChanged(xml_doc); - - if (mGraphicalEditor != null) { - mGraphicalEditor.onXmlModelChanged(); - } - - if (mOutline != null) { - mOutline.reloadModel(); - } - } - - /* (non-java doc) - * Returns the IContentOutlinePage when asked for it. - */ - @SuppressWarnings("unchecked") - @Override - public Object getAdapter(Class adapter) { - // for the outline, force it to come from the Graphical Editor. - // This fixes the case where a layout file is opened in XML view first and the outline - // gets stuck in the XML outline. - if (IContentOutlinePage.class == adapter && mGraphicalEditor != null) { - if (mOutline == null) { - mOutline = new UiContentOutlinePage(mGraphicalEditor, new TreeViewer()); - } - - return mOutline; - } - - if (IPropertySheetPage.class == adapter && mGraphicalEditor != null) { - if (mPropertyPage == null) { - mPropertyPage = new UiPropertySheetPage(); - } - - return mPropertyPage; - } - - // return default - return super.getAdapter(adapter); - } - - @Override - protected void pageChange(int newPageIndex) { - super.pageChange(newPageIndex); - - if (mGraphicalEditor != null && newPageIndex == mGraphicalEditorIndex) { - mGraphicalEditor.activated(); - } - } - - // ----- IPartListener Methods ---- - - public void partActivated(IWorkbenchPart part) { - if (part == this) { - if (mGraphicalEditor != null && getActivePage() == mGraphicalEditorIndex) { - mGraphicalEditor.activated(); - } - } - } - - public void partBroughtToTop(IWorkbenchPart part) { - partActivated(part); - } - - public void partClosed(IWorkbenchPart part) { - // pass - } - - public void partDeactivated(IWorkbenchPart part) { - // pass - } - - public void partOpened(IWorkbenchPart part) { - EclipseUiHelper.showView(EclipseUiHelper.CONTENT_OUTLINE_VIEW_ID, false /* activate */); - EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, false /* activate */); - } - - // ---- Local Methods ---- - - /** - * Returns true if the Graphics editor page is visible. - * This must be called from the UI thread. - */ - boolean isGraphicalEditorActive() { - IWorkbenchPartSite workbenchSite = getSite(); - IWorkbenchPage workbenchPage = workbenchSite.getPage(); - - // check if the editor is visible in the workbench page - if (workbenchPage.isPartVisible(this)) { - // and then if the page of the editor is visible (not to be confused with - // the workbench page) - return mGraphicalEditorIndex == getActivePage(); - } - - return false; - } - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - */ - private void initUiRootNode() { - // The root UI node is always created, even if there's no corresponding XML node. - if (mUiRootNode == null) { - DocumentDescriptor desc = LayoutDescriptors.getInstance().getDescriptor(); - mUiRootNode = (UiDocumentNode) desc.createUiNode(); - mUiRootNode.setEditor(this); - - // Add a listener to refresh the root node if the resource framework changes - // by forcing it to parse its own XML - mResourceRefreshListener = new Runnable() { - public void run() { - commitPages(false /* onSave */); - - mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlDocument()); - - if (mOutline != null) { - mOutline.reloadModel(); - } - - if (mGraphicalEditor != null) { - mGraphicalEditor.recomputeLayout(); - } - } - }; - EditorsPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener.run(); - } - } - - - /** - * Handles a new input, and update the part name. - * @param input the new input. - */ - private void handleNewInput(IEditorInput input) { - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput) input; - IFile file = fileInput.getFile(); - setPartName(String.format("%1$s", - file.getName())); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java deleted file mode 100644 index cf20288..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutReloadMonitor.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResourceDelta; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -/** - * Monitor for file changes triggering a layout redraw. - */ -public final class LayoutReloadMonitor implements IFileListener, IResourceEventListener { - - // singleton, enforced by private constructor. - private final static LayoutReloadMonitor sThis = new LayoutReloadMonitor(); - - /** - * Map of listeners by IProject. - */ - private final Map> mListenerMap = - new HashMap>(); - - private final static int CHANGE_CODE = 0; - private final static int CHANGE_RESOURCES = 1; - private final static int CHANGE_R = 2; - private final static int CHANGE_COUNT = 3; - /** - * List of projects having received a file change. the boolean[] contains 3 values: - *

              • CHANGE_CODE: code change flag.
              • - *
              • CHANGE_RESOURCES: resource change flag.
              • - *
              • CHANGE_R: R clas change flag
              - */ - private final Map mChangedProjects = new HashMap(); - - /** - * Classes which implement this interface provide a method to respond to resource changes - * triggering a layout redraw - */ - public interface ILayoutReloadListener { - /** - * Sent when the layout needs to be redrawn - * @param codeChange The trigger happened due to a code change. - * @param rChange The trigger happened due to a change in the R class. - * @param resChange The trigger happened due to a resource change. - */ - void reloadLayout(boolean codeChange, boolean rChange, boolean resChange); - } - - /** - * Returns the single instance of {@link LayoutReloadMonitor}. - */ - public static LayoutReloadMonitor getMonitor() { - return sThis; - } - - private LayoutReloadMonitor() { - ResourceMonitor monitor = ResourceMonitor.getMonitor(); - monitor.addFileListener(this, IResourceDelta.ADDED | IResourceDelta.CHANGED); - monitor.addResourceEventListener(this); - } - - /** - * Adds a listener for a given {@link IProject}. - * @param project - * @param listener - */ - public void addListener(IProject project, ILayoutReloadListener listener) { - synchronized (mListenerMap) { - List list = mListenerMap.get(project); - if (list == null) { - list = new ArrayList(); - mListenerMap.put(project, list); - } - - list.add(listener); - } - } - - /** - * Removes a listener for a given {@link IProject}. - * @param project - * @param listener - */ - public void removeListener(IProject project, ILayoutReloadListener listener) { - synchronized (mListenerMap) { - List list = mListenerMap.get(project); - if (list != null) { - list.remove(listener); - } - } - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener#fileChanged(org.eclipse.core.resources.IFile, org.eclipse.core.resources.IMarkerDelta[], int) - * - * Callback for ResourceMonitor.IFileListener. Called when a file changed. - * This records the changes for each project, but does not notify listeners. - * @see #resourceChangeEventEnd - */ - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - // get the file project - IProject project = file.getProject(); - - // if this project has already been marked as modified, we do nothing. - boolean[] changeFlags = mChangedProjects.get(project); - if (changeFlags != null && changeFlags[CHANGE_CODE] && changeFlags[CHANGE_RESOURCES] && - changeFlags[CHANGE_R]) { - return; - } - - // now check that the file is *NOT* a layout file (those automatically trigger a layout - // reload and we don't want to do it twice. - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(file); - if (resFolder != null) { - if (resFolder.getType() != ResourceFolderType.LAYOUT) { - // this is a resource change! - if (changeFlags == null) { - changeFlags = new boolean[CHANGE_COUNT]; - mChangedProjects.put(project, changeFlags); - } - - changeFlags[CHANGE_RESOURCES] = true; - } - } else if (AndroidConstants.EXT_CLASS.equals(file.getFileExtension())) { - if (file.getName().matches("R[\\$\\.](.*)")) { - // this is a R change! - if (changeFlags == null) { - changeFlags = new boolean[CHANGE_COUNT]; - mChangedProjects.put(project, changeFlags); - } - - changeFlags[CHANGE_R] = true; - } else { - // this is a code change! - if (changeFlags == null) { - changeFlags = new boolean[CHANGE_COUNT]; - mChangedProjects.put(project, changeFlags); - } - - changeFlags[CHANGE_CODE] = true; - } - } - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventStart() - * - * Callback for ResourceMonitor.IResourceEventListener. Called at the beginning of a resource - * change event. This is called once, while fileChanged can be called several times. - * - */ - public void resourceChangeEventStart() { - // nothing to be done here, it all happens in the resourceChangeEventEnd - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener#resourceChangeEventEnd() - * - * Callback for ResourceMonitor.IResourceEventListener. Called at the end of a resource - * change event. This is where we notify the listeners. - */ - public void resourceChangeEventEnd() { - // for each IProject that was changed, we notify all the listeners. - synchronized (mListenerMap) { - for (Entry project : mChangedProjects.entrySet()) { - List listeners = mListenerMap.get(project.getKey()); - - boolean[] flags = project.getValue(); - - if (listeners != null) { - for (ILayoutReloadListener listener : listeners) { - listener.reloadLayout(flags[CHANGE_CODE], flags[CHANGE_R], - flags[CHANGE_RESOURCES]); - } - } - } - } - - // empty the list. - mChangedProjects.clear(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java deleted file mode 100644 index 1aa1f4c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutSourceViewerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.editors.layout; - - -import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; - -/** - * Source Viewer Configuration that calls in LayoutContentAssist. - */ -public class LayoutSourceViewerConfig extends AndroidSourceViewerConfig { - - public LayoutSourceViewerConfig() { - super(new LayoutContentAssist()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutTreePage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutTreePage.java deleted file mode 100644 index c936be3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/LayoutTreePage.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; -import org.eclipse.ui.part.FileEditorInput; - -/** - * Page for the layout tree-based form editor. - * - * @deprecated This is being phased out. We use the GraphicalLayoutEditor instead. - */ -public final class LayoutTreePage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "layout_tree_page"; //$NON-NLS-1$ - - /** Container editor */ - LayoutEditor mEditor; - - public LayoutTreePage(LayoutEditor editor) { - super(editor, PAGE_ID, "Layout"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - - String configText = null; - IEditorInput input = mEditor.getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - IFile iFile = fileInput.getFile(); - - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile); - if (resFolder != null) { - configText = resFolder.getConfiguration().toDisplayString(); - } - } - - if (configText != null) { - form.setText(String.format("Android Layout (%1$s)", configText)); - } else { - form.setText("Android Layout"); - } - - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode rootNode = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, rootNode, - true /* autoCreateRoot */, - null /* no element filters */, - "Layout Elements", - "List of all layout elements in this XML file."); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java deleted file mode 100644 index bb075c2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/MatchingStrategy.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; - -import org.eclipse.core.resources.IFile; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorMatchingStrategy; -import org.eclipse.ui.IEditorReference; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; - -/** - * Matching strategy for the Layout Editor. This is used to open all configurations of a layout - * in the same editor. - */ -public class MatchingStrategy implements IEditorMatchingStrategy { - - public boolean matches(IEditorReference editorRef, IEditorInput input) { - // first check that the file being opened is a layout file. - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - - // get the IFile object and check it's in one of the layout folders. - IFile iFile = fileInput.getFile(); - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile); - - // if it's a layout, we know check the name of the fileInput against the name of the - // file being currently edited by the editor since those are independent of the config. - if (resFolder != null && resFolder.getType() == ResourceFolderType.LAYOUT) { - try { - IEditorInput editorInput = editorRef.getEditorInput(); - if (editorInput instanceof FileEditorInput) { - FileEditorInput editorFileInput = (FileEditorInput)editorInput; - IFile editorIFile = editorFileInput.getFile(); - - return editorIFile.getProject().equals(iFile.getProject()) - && editorIFile.getName().equals(iFile.getName()); - } - } catch (PartInitException e) { - // we do nothing, we'll just return false. - } - } - } - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java deleted file mode 100644 index 77b1df4..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/PaletteFactory.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; - -import org.eclipse.gef.palette.CombinedTemplateCreationEntry; -import org.eclipse.gef.palette.MarqueeToolEntry; -import org.eclipse.gef.palette.PaletteDrawer; -import org.eclipse.gef.palette.PaletteGroup; -import org.eclipse.gef.palette.PaletteRoot; -import org.eclipse.gef.palette.PanningSelectionToolEntry; -import org.eclipse.gef.requests.CreationFactory; - -import java.util.List; - -/** - * Factory that creates the palette for the {@link GraphicalLayoutEditor}. - */ -public class PaletteFactory { - - private static PaletteRoot sPaletteRoot; - private static Runnable sSdkChangedListener; - - /** Static factory, nothing to instantiate here. */ - private PaletteFactory() { - } - - public static PaletteRoot createPaletteRoot() { - if (sSdkChangedListener == null) { - sSdkChangedListener = new Runnable() { - public void run() { - if (sPaletteRoot != null) { - // The SDK has changed. Remove the drawer groups and rebuild them. - for (int n = sPaletteRoot.getChildren().size() - 1; n >= 0; n--) { - sPaletteRoot.getChildren().remove(n); - } - - addTools(sPaletteRoot); - addViews(sPaletteRoot, "Layouts", - LayoutDescriptors.getInstance().getLayoutDescriptors()); - addViews(sPaletteRoot, "Views", - LayoutDescriptors.getInstance().getViewDescriptors()); - } - } - }; - EditorsPlugin.getDefault().addResourceChangedListener(sSdkChangedListener); - } - - if (sPaletteRoot == null) { - sPaletteRoot = new PaletteRoot(); - sSdkChangedListener.run(); - } - return sPaletteRoot; - } - - private static void addTools(PaletteRoot paletteRoot) { - PaletteGroup group = new PaletteGroup("Tools"); - - // Default tools: selection and marquee selection - PanningSelectionToolEntry entry = new PanningSelectionToolEntry(); - group.add(entry); - paletteRoot.setDefaultEntry(entry); - - group.add(new MarqueeToolEntry()); - - paletteRoot.add(group); - } - - private static void addViews(PaletteRoot paletteRoot, String groupName, - List descriptors) { - PaletteDrawer group = new PaletteDrawer(groupName); - - for (ElementDescriptor desc : descriptors) { - CombinedTemplateCreationEntry entry = new CombinedTemplateCreationEntry( - desc.getUiName(), // label - desc.getTooltip(), // short description - desc.getClass(), // template - new ElementFactory(desc), // factory - desc.getImageDescriptor(), // small icon - desc.getImageDescriptor() // large icon - ); - group.add(entry); - } - - paletteRoot.add(group); - } - - //------------------------------------------ - - public static class ElementFactory implements CreationFactory { - - private final ElementDescriptor mDescriptor; - - public ElementFactory(ElementDescriptor descriptor) { - mDescriptor = descriptor; - } - - public Object getNewObject() { - return mDescriptor; - } - - public Object getObjectType() { - return mDescriptor; - } - - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java deleted file mode 100644 index d91ecbc..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/ProjectCallback.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; -import com.android.ide.eclipse.editors.resources.manager.ProjectClassLoader; -import com.android.ide.eclipse.editors.resources.manager.ProjectResources; -import com.android.layoutlib.api.IProjectCallback; - -import org.eclipse.core.resources.IProject; - -import java.lang.reflect.Constructor; -import java.util.HashMap; - -/** - * Loader for Android Project class in order to use them in the layout editor. - */ -public final class ProjectCallback implements IProjectCallback { - - private final HashMap> mLoadedClasses = new HashMap>(); - private final IProject mProject; - private final ClassLoader mParentClassLoader; - private final ProjectResources mProjectRes; - private boolean mUsed = false; - private String mNamespace; - - ProjectCallback(ClassLoader classLoader, ProjectResources projectRes, IProject project) { - mParentClassLoader = classLoader; - mProjectRes = projectRes; - mProject = project; - } - - - /** - * {@inheritDoc} - * - * This implementation goes through the output directory of the Eclipse project and loads the - * .class file directly. - */ - @SuppressWarnings("unchecked") - public Object loadView(String className, Class[] constructorSignature, - Object[] constructorParameters) - throws ClassNotFoundException, Exception { - - // look for a cached version - Class clazz = mLoadedClasses.get(className); - if (clazz != null) { - return instantiateClass(clazz, constructorSignature, constructorParameters); - } - - // load the class. - ProjectClassLoader loader = new ProjectClassLoader(mParentClassLoader, mProject); - clazz = loader.loadClass(className); - - if (clazz != null) { - mUsed = true; - mLoadedClasses.put(className, clazz); - return instantiateClass(clazz, constructorSignature, constructorParameters); - } - - return null; - } - - /** - * {@inheritDoc} - * - * Returns the namespace for the project. The namespace contains a standard part + the - * application package. - */ - public String getNamespace() { - if (mNamespace == null) { - AndroidManifestHelper manifest = new AndroidManifestHelper(mProject); - String javaPackage = manifest.getPackageName(); - - mNamespace = String.format(AndroidConstants.NS_CUSTOM_RESOURCES, javaPackage); - } - - return mNamespace; - } - - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.IProjectCallback#resolveResourceValue(int) - */ - public String[] resolveResourceValue(int id) { - if (mProjectRes != null) { - return mProjectRes.resolveResourceValue(id); - } - - return null; - } - - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.IProjectCallback#resolveResourceValue(int[]) - */ - public String resolveResourceValue(int[] id) { - if (mProjectRes != null) { - return mProjectRes.resolveResourceValue(id); - } - - return null; - } - - /* - * (non-Javadoc) - * @see com.android.layoutlib.api.IProjectCallback#getResourceValue(java.lang.String, java.lang.String) - */ - public Integer getResourceValue(String type, String name) { - if (mProjectRes != null) { - return mProjectRes.getResourceValue(type, name); - } - - return null; - } - - /** - * Returns whether the loader has received requests to load custom views. - *

              This allows to efficiently only recreate when needed upon code change in the project. - */ - boolean isUsed() { - return mUsed; - } - - /** - * Instantiate a class object, using a specific constructor and parameters. - * @param clazz the class to instantiate - * @param constructorSignature the signature of the constructor to use - * @param constructorParameters the parameters to use in the constructor. - * @return - * @throws Exception - */ - @SuppressWarnings("unchecked") - private Object instantiateClass(Class clazz, Class[] constructorSignature, - Object[] constructorParameters) throws Exception { - Constructor constructor = clazz.getConstructor(constructorSignature); - constructor.setAccessible(true); - return constructor.newInstance(constructorParameters); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java deleted file mode 100644 index 05f8370..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiContentOutlinePage.java +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.editors.layout; - -import com.android.ide.eclipse.common.EclipseUiHelper; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.layout.parts.UiDocumentTreeEditPart; -import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPart; -import com.android.ide.eclipse.editors.layout.parts.UiElementTreeEditPartFactory; -import com.android.ide.eclipse.editors.layout.parts.UiLayoutTreeEditPart; -import com.android.ide.eclipse.editors.layout.parts.UiViewTreeEditPart; -import com.android.ide.eclipse.editors.ui.tree.CopyCutAction; -import com.android.ide.eclipse.editors.ui.tree.PasteAction; -import com.android.ide.eclipse.editors.ui.tree.UiActions; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.gef.EditPartViewer; -import org.eclipse.gef.ui.parts.ContentOutlinePage; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IMenuListener; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.IToolBarManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; -import org.eclipse.ui.IActionBars; - -import java.util.LinkedList; - -/** - * Implementation of the {@link ContentOutlinePage} to display {@link UiElementNode}. - */ -class UiContentOutlinePage extends ContentOutlinePage { - - private GraphicalLayoutEditor mEditor; - - private Action mAddAction; - private Action mDeleteAction; - private Action mUpAction; - private Action mDownAction; - - private UiOutlineActions mUiActions = new UiOutlineActions(); - - public UiContentOutlinePage(GraphicalLayoutEditor editor, final EditPartViewer viewer) { - super(viewer); - mEditor = editor; - IconFactory factory = IconFactory.getInstance(); - - mAddAction = new Action("Add...") { - @Override - public void run() { - UiElementNode node = getModelSelection(); - - mUiActions.doAdd(node, viewer.getControl().getShell()); - } - }; - mAddAction.setToolTipText("Adds a new element."); - mAddAction.setImageDescriptor(factory.getImageDescriptor("add")); //$NON-NLS-1$ - - mDeleteAction = new Action("Remove...") { - @Override - public void run() { - UiElementNode node = getModelSelection(); - - mUiActions.doRemove(node, viewer.getControl().getShell()); - } - }; - mDeleteAction.setToolTipText("Removes an existing selected element."); - mDeleteAction.setImageDescriptor(factory.getImageDescriptor("delete")); //$NON-NLS-1$ - - mUpAction = new Action("Up") { - @Override - public void run() { - UiElementNode node = getModelSelection(); - - mUiActions.doUp(node); - } - }; - mUpAction.setToolTipText("Moves the selected element up"); - mUpAction.setImageDescriptor(factory.getImageDescriptor("up")); //$NON-NLS-1$ - - mDownAction = new Action("Down") { - @Override - public void run() { - UiElementNode node = getModelSelection(); - - mUiActions.doDown(node); - } - }; - mDownAction.setToolTipText("Moves the selected element down"); - mDownAction.setImageDescriptor(factory.getImageDescriptor("down")); //$NON-NLS-1$ - - // all actions disabled by default. - mAddAction.setEnabled(false); - mDeleteAction.setEnabled(false); - mUpAction.setEnabled(false); - mDownAction.setEnabled(false); - - addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - ISelection selection = event.getSelection(); - - // the selection is never empty. The least it'll contain is the - // UiDocumentTreeEditPart object. - if (selection instanceof StructuredSelection) { - StructuredSelection structSel = (StructuredSelection)selection; - - if (structSel.size() == 1 && - structSel.getFirstElement() instanceof UiDocumentTreeEditPart) { - mDeleteAction.setEnabled(false); - mUpAction.setEnabled(false); - mDownAction.setEnabled(false); - } else { - mDeleteAction.setEnabled(true); - mUpAction.setEnabled(true); - mDownAction.setEnabled(true); - } - - // the "add" button is always enabled, in order to be able to set the - // initial root node - mAddAction.setEnabled(true); - } - } - }); - } - - - /* (non-Javadoc) - * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) - */ - @Override - public void createControl(Composite parent) { - // create outline viewer page - getViewer().createControl(parent); - - // configure outline viewer - getViewer().setEditPartFactory(new UiElementTreeEditPartFactory()); - - setupOutline(); - setupContextMenu(); - setupTooltip(); - setupDoubleClick(); - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.part.Page#setActionBars(org.eclipse.ui.IActionBars) - * - * Called automatically after createControl - */ - @Override - public void setActionBars(IActionBars actionBars) { - IToolBarManager toolBarManager = actionBars.getToolBarManager(); - toolBarManager.add(mAddAction); - toolBarManager.add(mDeleteAction); - toolBarManager.add(new Separator()); - toolBarManager.add(mUpAction); - toolBarManager.add(mDownAction); - - IMenuManager menuManager = actionBars.getMenuManager(); - menuManager.add(mAddAction); - menuManager.add(mDeleteAction); - menuManager.add(new Separator()); - menuManager.add(mUpAction); - menuManager.add(mDownAction); - } - - /* (non-Javadoc) - * @see org.eclipse.ui.part.IPage#dispose() - */ - @Override - public void dispose() { - breakConnectionWithEditor(); - - // dispose - super.dispose(); - } - - /* (non-Javadoc) - * @see org.eclipse.ui.part.IPage#getControl() - */ - @Override - public Control getControl() { - return getViewer().getControl(); - } - - void setNewEditor(GraphicalLayoutEditor editor) { - mEditor = editor; - setupOutline(); - } - - void breakConnectionWithEditor() { - // unhook outline viewer - mEditor.getSelectionSynchronizer().removeViewer(getViewer()); - } - - private void setupOutline() { - getViewer().setEditDomain(mEditor.getEditDomain()); - - // hook outline viewer - mEditor.getSelectionSynchronizer().addViewer(getViewer()); - - // initialize outline viewer with model - getViewer().setContents(mEditor.getModel()); - } - - private void setupContextMenu() { - MenuManager menuManager = new MenuManager(); - menuManager.setRemoveAllWhenShown(true); - menuManager.addMenuListener(new IMenuListener() { - /** - * The menu is about to be shown. The menu manager has already been - * requested to remove any existing menu item. This method gets the - * tree selection and if it is of the appropriate type it re-creates - * the necessary actions. - */ - public void menuAboutToShow(IMenuManager manager) { - UiElementNode selected = getModelSelection(); - - if (selected != null) { - doCreateMenuAction(manager, selected); - return; - } - doCreateMenuAction(manager, null /* ui_node */); - } - }); - Control control = getControl(); - Menu contextMenu = menuManager.createContextMenu(control); - control.setMenu(contextMenu); - } - - /** - * Adds the menu actions to the context menu when the given UI node is selected in - * the tree view. - * - * @param manager The context menu manager - * @param ui_node The UI node selected in the tree. Can be null, in which case the root - * is to be modified. - */ - private void doCreateMenuAction(IMenuManager manager, UiElementNode ui_node) { - if (ui_node != null && ui_node.getXmlNode() != null) { - manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(), - null, ui_node, true /* cut */)); - manager.add(new CopyCutAction(mEditor.getLayoutEditor(), mEditor.getClipboard(), - null, ui_node, false /* cut */)); - // Paste is not valid if it would add a second element on a terminal element - // which parent is a document -- an XML document can only have one child. This - // means paste is valid if the current UI node can have children or if the parent - // is not a document. - UiElementNode ui_root = ui_node.getUiRoot(); - if (ui_root.getDescriptor().hasChildren() || - !(ui_root.getUiParent() instanceof UiDocumentNode)) { - manager.add(new PasteAction(mEditor.getLayoutEditor(), mEditor.getClipboard(), - ui_node)); - } - manager.add(new Separator()); - } - - // Append "add" and "remove" actions. They do the same thing as the add/remove - // buttons on the side. - manager.add(mAddAction); - manager.add(mDeleteAction); - - manager.add(new Separator()); - - manager.add(mUpAction); - manager.add(mDownAction); - - manager.add(new Separator()); - - Action propertiesAction = new Action("Properties") { - @Override - public void run() { - EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, - true /* activate */); - } - }; - propertiesAction.setToolTipText("Displays properties of the selected element."); - manager.add(propertiesAction); - } - - /** - * Updates the outline view with the model of the {@link GraphicalLayoutEditor}. - *

              - * This attemps to preserve the selection, if any. - */ - public void reloadModel() { - // Attemps to preserve the UiNode selection, if any - UiElementNode uiNode = null; - try { - // get current selection using the model rather than the edit part as - // reloading the content may change the actual edit part. - uiNode = getModelSelection(); - - // perform the update - getViewer().setContents(mEditor.getModel()); - - } finally { - // restore selection - setModelSelection(uiNode); - } - } - - /** - * Returns the currently selected element, if any, in the viewer. - * This returns the viewer's elements (i.e. an {@link UiElementTreeEditPart}) - * and not the underlying model node. - *

              - * When there is no actual selection, this might still return the root node, - * which is of type {@link UiDocumentTreeEditPart}. - */ - private UiElementTreeEditPart getViewerSelection() { - ISelection selection = getSelection(); - if (selection instanceof StructuredSelection) { - StructuredSelection structuredSelection = (StructuredSelection)selection; - - if (structuredSelection.size() == 1) { - Object selectedObj = structuredSelection.getFirstElement(); - - if (selectedObj instanceof UiElementTreeEditPart) { - return (UiElementTreeEditPart) selectedObj; - } - } - } - - return null; - } - - /** - * Returns the currently selected model element, which is either an - * {@link UiViewTreeEditPart} or an {@link UiLayoutTreeEditPart}. - *

              - * Returns null if there is no selection or if the implicit root is "selected" - * (which actually represents the lack of a real element selection.) - */ - private UiElementNode getModelSelection() { - - UiElementTreeEditPart part = getViewerSelection(); - - if (part instanceof UiViewTreeEditPart || part instanceof UiLayoutTreeEditPart) { - return (UiElementNode) part.getModel(); - } - return null; - } - - /** - * Selects the corresponding edit part in the tree viewer. - */ - private void setViewerSelection(UiElementTreeEditPart selectedPart) { - if (selectedPart != null && !(selectedPart instanceof UiDocumentTreeEditPart)) { - LinkedList segments = new LinkedList(); - for (UiElementTreeEditPart part = selectedPart; - !(part instanceof UiDocumentTreeEditPart); - part = (UiElementTreeEditPart) part.getParent()) { - segments.add(0, part); - } - setSelection(new TreeSelection(new TreePath(segments.toArray()))); - } - } - - /** - * Selects the corresponding model element in the tree viewer. - */ - private void setModelSelection(UiElementNode uiNodeToSelect) { - if (uiNodeToSelect != null) { - - // find an edit part that has the requested model element - UiElementTreeEditPart part = findPartForModel( - (UiElementTreeEditPart) getViewer().getContents(), - uiNodeToSelect); - - // if we found a part, select it and reveal it - if (part != null) { - setViewerSelection(part); - getViewer().reveal(part); - } - } - } - - /** - * Utility method that tries to find an edit part that matches a given model UI node. - * - * @param rootPart The root of the viewer edit parts - * @param uiNode The UI node model to find - * @return The part that matches the model or null if it's not in the sub tree. - */ - private UiElementTreeEditPart findPartForModel(UiElementTreeEditPart rootPart, - UiElementNode uiNode) { - if (rootPart.getModel() == uiNode) { - return rootPart; - } - - for (Object part : rootPart.getChildren()) { - if (part instanceof UiElementTreeEditPart) { - UiElementTreeEditPart found = findPartForModel( - (UiElementTreeEditPart) part, uiNode); - if (found != null) { - return found; - } - } - } - - return null; - } - - /** - * Sets up a custom tooltip when hovering over tree items. - *

              - * The tooltip will display the element's javadoc, if any, or the item's getText otherwise. - */ - private void setupTooltip() { - final Tree tree = (Tree) getControl(); - - /* - * Reference: - * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup - */ - - final Listener listener = new Listener() { - Shell tip = null; - Label label = null; - - public void handleEvent(Event event) { - switch(event.type) { - case SWT.Dispose: - case SWT.KeyDown: - case SWT.MouseExit: - case SWT.MouseDown: - case SWT.MouseMove: - if (tip != null) { - tip.dispose(); - tip = null; - label = null; - } - break; - case SWT.MouseHover: - if (tip != null) { - tip.dispose(); - tip = null; - label = null; - } - - String tooltip = null; - - TreeItem item = tree.getItem(new Point(event.x, event.y)); - if (item != null) { - Object data = item.getData(); - if (data instanceof UiElementTreeEditPart) { - Object model = ((UiElementTreeEditPart) data).getModel(); - if (model instanceof UiElementNode) { - tooltip = ((UiElementNode) model).getDescriptor().getTooltip(); - } - } - - if (tooltip == null) { - tooltip = item.getText(); - } else { - tooltip = item.getText() + ":\r" + tooltip; - } - } - - - if (tooltip != null) { - Shell shell = tree.getShell(); - Display display = tree.getDisplay(); - - tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); - tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - FillLayout layout = new FillLayout(); - layout.marginWidth = 2; - tip.setLayout(layout); - label = new Label(tip, SWT.NONE); - label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); - label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - label.setData("_TABLEITEM", item); - label.setText(tooltip); - label.addListener(SWT.MouseExit, this); - label.addListener(SWT.MouseDown, this); - Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); - Rectangle rect = item.getBounds(0); - Point pt = tree.toDisplay(rect.x, rect.y); - tip.setBounds(pt.x, pt.y, size.x, size.y); - tip.setVisible(true); - } - } - } - }; - - tree.addListener(SWT.Dispose, listener); - tree.addListener(SWT.KeyDown, listener); - tree.addListener(SWT.MouseMove, listener); - tree.addListener(SWT.MouseHover, listener); - } - - /** - * Sets up double-click action on the tree. - *

              - * By default, double-click (a.k.a. "default selection") on a valid list item will - * show the property view. - */ - private void setupDoubleClick() { - final Tree tree = (Tree) getControl(); - - tree.addListener(SWT.DefaultSelection, new Listener() { - public void handleEvent(Event event) { - EclipseUiHelper.showView(EclipseUiHelper.PROPERTY_SHEET_VIEW_ID, - true /* activate */); - } - }); - } - - // --------------- - - private class UiOutlineActions extends UiActions { - - @Override - protected UiDocumentNode getRootNode() { - return mEditor.getModel(); // this is LayoutEditor.getUiRootNode() - } - - // Select the new item - @Override - protected void selectUiNode(UiElementNode uiNodeToSelect) { - setModelSelection(uiNodeToSelect); - } - - @Override - public void commitPendingXmlChanges() { - // Pass. There is nothing to commit before the XML is changed here. - } - - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java deleted file mode 100644 index 41d3747..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiElementPullParser.java +++ /dev/null @@ -1,405 +0,0 @@ -/* - * 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.editors.layout; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.layoutlib.api.IXmlPullParser; - -import org.w3c.dom.Node; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.util.ArrayList; -import java.util.List; - -/** - * XmlPullParser implementation on top of {@link UiElementNode}. - *

              It's designed to work on layout files, and will most likely not work on other resource - * files. - */ -public final class UiElementPullParser implements IXmlPullParser { - - private final ArrayList mNodeStack = new ArrayList(); - private int mParsingState = START_DOCUMENT; - private UiElementNode mRoot; - - public UiElementPullParser(UiElementNode top) { - mRoot = top; - push(mRoot); - } - - private UiElementNode getCurrentNode() { - if (mNodeStack.size() > 0) { - return mNodeStack.get(mNodeStack.size()-1); - } - - return null; - } - - private Node getAttribute(int i) { - if (mParsingState != START_TAG) { - throw new IndexOutOfBoundsException(); - } - - // get the current uiNode - UiElementNode uiNode = getCurrentNode(); - - // get its xml node - Node xmlNode = uiNode.getXmlNode(); - - if (xmlNode != null) { - return xmlNode.getAttributes().item(i); - } - - return null; - } - - private void push(UiElementNode node) { - mNodeStack.add(node); - } - - private UiElementNode pop() { - return mNodeStack.remove(mNodeStack.size()-1); - } - - // ------------- IXmlPullParser -------- - - /** - * {@inheritDoc} - * - * This implementation returns the underlying DOM node. - */ - public Object getViewKey() { - return getCurrentNode(); - } - - // ------------- XmlPullParser -------- - - public void setFeature(String name, boolean state) throws XmlPullParserException { - if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { - return; - } - if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name) && state) { - return; - } - throw new XmlPullParserException("Unsupported feature: " + name); - } - - public boolean getFeature(String name) { - if (FEATURE_PROCESS_NAMESPACES.equals(name)) { - return true; - } - if (FEATURE_REPORT_NAMESPACE_ATTRIBUTES.equals(name)) { - return true; - } - return false; - } - - public void setProperty(String name, Object value) throws XmlPullParserException { - throw new XmlPullParserException("setProperty() not supported"); - } - - public Object getProperty(String name) { - return null; - } - - public void setInput(Reader in) throws XmlPullParserException { - throw new XmlPullParserException("setInput() not supported"); - } - - public void setInput(InputStream inputStream, String inputEncoding) - throws XmlPullParserException { - throw new XmlPullParserException("setInput() not supported"); - } - - public void defineEntityReplacementText(String entityName, String replacementText) - throws XmlPullParserException { - throw new XmlPullParserException("defineEntityReplacementText() not supported"); - } - - public String getNamespacePrefix(int pos) throws XmlPullParserException { - throw new XmlPullParserException("getNamespacePrefix() not supported"); - } - - public String getInputEncoding() { - return null; - } - - public String getNamespace(String prefix) { - throw new RuntimeException("getNamespace() not supported"); - } - - public int getNamespaceCount(int depth) throws XmlPullParserException { - throw new XmlPullParserException("getNamespaceCount() not supported"); - } - - public String getPositionDescription() { - return "XML DOM element depth:" + mNodeStack.size(); - } - - public String getNamespaceUri(int pos) throws XmlPullParserException { - throw new XmlPullParserException("getNamespaceUri() not supported"); - } - - public int getColumnNumber() { - return -1; - } - - public int getLineNumber() { - return -1; - } - - public int getAttributeCount() { - UiElementNode node = getCurrentNode(); - if (node != null) { - return node.getUiAttributes().size(); - } - - return 0; - } - - public String getAttributeName(int i) { - Node attribute = getAttribute(i); - if (attribute != null) { - return attribute.getLocalName(); - } - - return null; - } - - public String getAttributeNamespace(int i) { - Node attribute = getAttribute(i); - if (attribute != null) { - return attribute.getNamespaceURI(); - } - return ""; //$NON-NLS-1$ - } - - public String getAttributePrefix(int i) { - Node attribute = getAttribute(i); - if (attribute != null) { - return attribute.getPrefix(); - } - return null; - } - - public String getAttributeType(int arg0) { - return "CDATA"; - } - - public String getAttributeValue(int i) { - Node attribute = getAttribute(i); - if (attribute != null) { - return attribute.getNodeValue(); - } - - return null; - } - - public String getAttributeValue(String namespace, String localName) { - // get the current uiNode - UiElementNode uiNode = getCurrentNode(); - - // get its xml node - Node xmlNode = uiNode.getXmlNode(); - - if (xmlNode != null) { - Node attribute = xmlNode.getAttributes().getNamedItemNS(namespace, localName); - if (attribute != null) { - return attribute.getNodeValue(); - } - } - - return null; - } - - public int getDepth() { - return mNodeStack.size(); - } - - public int getEventType() throws XmlPullParserException { - return mParsingState; - } - - public String getName() { - if (mParsingState == START_TAG || mParsingState == END_TAG) { - return getCurrentNode().getDescriptor().getXmlLocalName(); - } - - return null; - } - - public String getNamespace() { - if (mParsingState == START_TAG || mParsingState == END_TAG) { - return getCurrentNode().getDescriptor().getNamespace(); - } - - return null; - } - - public String getPrefix() { - if (mParsingState == START_TAG || mParsingState == END_TAG) { - // FIXME will NEVER work - if (getCurrentNode().getDescriptor().getXmlLocalName().startsWith("android:")) { //$NON-NLS-1$ - return "android"; //$NON-NLS-1$ - } - } - - return null; - } - - public String getText() { - return null; - } - - public char[] getTextCharacters(int[] arg0) { - return null; - } - - public boolean isAttributeDefault(int arg0) { - return false; - } - - public boolean isEmptyElementTag() throws XmlPullParserException { - if (mParsingState == START_TAG) { - return getCurrentNode().getUiChildren().size() == 0; - } - - throw new XmlPullParserException("Must be on START_TAG"); - } - - public boolean isWhitespace() throws XmlPullParserException { - return false; - } - - public int next() throws XmlPullParserException, IOException { - UiElementNode node; - switch (mParsingState) { - case END_DOCUMENT: - throw new XmlPullParserException("Nothing after the end"); - case START_DOCUMENT: - /* intended fall-through */ - case START_TAG: - // get the current node, and look for text or children (children first) - node = getCurrentNode(); - List children = node.getUiChildren(); - if (children.size() > 0) { - // move to the new child, and don't change the state. - push(children.get(0)); - - // in case the current state is CURRENT_DOC, we set the proper state. - mParsingState = START_TAG; - } else { - if (mParsingState == START_DOCUMENT) { - // this handles the case where there's no node. - mParsingState = END_DOCUMENT; - } else { - mParsingState = END_TAG; - } - } - break; - case END_TAG: - // look for a sibling. if no sibling, go back to the parent - node = getCurrentNode(); - node = node.getUiNextSibling(); - if (node != null) { - // to go to the sibling, we need to remove the current node, - pop(); - // and add its sibling. - push(node); - mParsingState = START_TAG; - } else { - // move back to the parent - pop(); - - // we have only one element left (mRoot), then we're done with the document. - if (mNodeStack.size() == 1) { - mParsingState = END_DOCUMENT; - } else { - mParsingState = END_TAG; - } - } - break; - case TEXT: - // not used - break; - case CDSECT: - // not used - break; - case ENTITY_REF: - // not used - break; - case IGNORABLE_WHITESPACE: - // not used - break; - case PROCESSING_INSTRUCTION: - // not used - break; - case COMMENT: - // not used - break; - case DOCDECL: - // not used - break; - } - - return mParsingState; - } - - public int nextTag() throws XmlPullParserException, IOException { - int eventType = next(); - if (eventType != START_TAG && eventType != END_TAG) { - throw new XmlPullParserException("expected start or end tag", this, null); - } - return eventType; - } - - public String nextText() throws XmlPullParserException, IOException { - if (getEventType() != START_TAG) { - throw new XmlPullParserException("parser must be on START_TAG to read next text", this, - null); - } - int eventType = next(); - if (eventType == TEXT) { - String result = getText(); - eventType = next(); - if (eventType != END_TAG) { - throw new XmlPullParserException( - "event TEXT it must be immediately followed by END_TAG", this, null); - } - return result; - } else if (eventType == END_TAG) { - return ""; - } else { - throw new XmlPullParserException("parser must be on START_TAG or TEXT to read text", - this, null); - } - } - - public int nextToken() throws XmlPullParserException, IOException { - return next(); - } - - public void require(int type, String namespace, String name) throws XmlPullParserException, - IOException { - if (type != getEventType() || (namespace != null && !namespace.equals(getNamespace())) - || (name != null && !name.equals(getName()))) - throw new XmlPullParserException("expected " + TYPES[type] + getPositionDescription()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java deleted file mode 100644 index 8093c90..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/UiPropertySheetPage.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.editors.layout; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; -import org.eclipse.ui.views.properties.PropertySheetEntry; -import org.eclipse.ui.views.properties.PropertySheetPage; - -/** - * A customized property sheet page for the graphical layout editor. - *

              - * Currently it just provides a custom tooltip to display attributes javadocs. - */ -public class UiPropertySheetPage extends PropertySheetPage { - - - public UiPropertySheetPage() { - super(); - } - - @Override - public void createControl(Composite parent) { - super.createControl(parent); - - setupTooltip(); - } - - /** - * Sets up a custom tooltip when hovering over tree items. - *

              - * The tooltip will display the element's javadoc, if any, or the item's getText otherwise. - */ - private void setupTooltip() { - final Tree tree = (Tree) getControl(); - - /* - * Reference: - * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup - */ - - final Listener listener = new Listener() { - Shell tip = null; - Label label = null; - - public void handleEvent(Event event) { - switch(event.type) { - case SWT.Dispose: - case SWT.KeyDown: - case SWT.MouseExit: - case SWT.MouseDown: - case SWT.MouseMove: - if (tip != null) { - tip.dispose(); - tip = null; - label = null; - } - break; - case SWT.MouseHover: - if (tip != null) { - tip.dispose(); - tip = null; - label = null; - } - - String tooltip = null; - - TreeItem item = tree.getItem(new Point(event.x, event.y)); - if (item != null) { - Object data = item.getData(); - if (data instanceof PropertySheetEntry) { - tooltip = ((PropertySheetEntry) data).getDescription(); - } - - if (tooltip == null) { - tooltip = item.getText(); - } else { - tooltip = item.getText() + ":\r" + tooltip; - } - } - - if (tooltip != null) { - Shell shell = tree.getShell(); - Display display = tree.getDisplay(); - - tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); - tip.setBackground(display .getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - FillLayout layout = new FillLayout(); - layout.marginWidth = 2; - tip.setLayout(layout); - label = new Label(tip, SWT.NONE); - label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); - label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); - label.setData("_TABLEITEM", item); - label.setText(tooltip); - label.addListener(SWT.MouseExit, this); - label.addListener(SWT.MouseDown, this); - Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); - Rectangle rect = item.getBounds(0); - Point pt = tree.toDisplay(rect.x, rect.y); - tip.setBounds(pt.x, pt.y, size.x, size.y); - tip.setVisible(true); - } - } - } - }; - - tree.addListener(SWT.Dispose, listener); - tree.addListener(SWT.KeyDown, listener); - tree.addListener(SWT.MouseMove, listener); - tree.addListener(SWT.MouseHover, listener); - - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java deleted file mode 100644 index c3e9b70..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/CustomViewDescriptorService.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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.editors.layout.descriptors; - -import com.android.ide.eclipse.common.resources.ViewClassInfo; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; - -import org.eclipse.core.resources.IProject; -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.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; - -import java.util.HashMap; -import java.util.List; - -/** - * Service responsible for creating/managing {@link ElementDescriptor} objects for custom - * View classes per project. - *

              - * The service provides an on-demand monitoring of custom classes to check for changes. Monitoring - * starts once a request for an {@link ElementDescriptor} object has been done for a specific - * class.
              - * The monitoring will notify a listen of any changes in the class triggering a change in its - * associated {@link ElementDescriptor} object. - *

              - * If the custom class does not exist, no monitoring is put in place to avoid having to listen - * to all class changes in the projects. - * - */ -public final class CustomViewDescriptorService { - - private static CustomViewDescriptorService sThis = new CustomViewDescriptorService(); - - /** - * Map where keys are the project, and values are another map containing all the known - * custom View class for this project. The custom View class are stored in a map - * where the keys are the fully qualified class name, and the values are their associated - * {@link ElementDescriptor}. - */ - private HashMap> mCustomDescriptorMap = - new HashMap>(); - - /** - * TODO will be used to update the ElementDescriptor of the custom view when it - * is modified (either the class itself or its attributes.xml) - */ - @SuppressWarnings("unused") - private ICustomViewDescriptorListener mListener; - - /** - * Classes which implements this interface provide a method that deal with modifications - * in custom View class triggering a change in its associated {@link ViewClassInfo} object. - */ - public interface ICustomViewDescriptorListener { - /** - * Sent when a custom View class has changed and its {@link ElementDescriptor} was modified. - * @param project the project containing the class. - * @param className the fully qualified class name. - * @param descriptor the updated ElementDescriptor. - */ - public void updatedClassInfo(IProject project, String className, ElementDescriptor descriptor); - } - - /** - * Returns the singleton instance of {@link CustomViewDescriptorService}. - */ - public static CustomViewDescriptorService getInstance() { - return sThis; - } - - /** - * Sets the listener receiving custom View class modification notifications. - * @param listener the listener to receive the notifications. - * - * TODO will be used to update the ElementDescriptor of the custom view when it - * is modified (either the class itself or its attributes.xml) - */ - public void setListener(ICustomViewDescriptorListener listener) { - mListener = listener; - } - - /** - * Returns the {@link ElementDescriptor} for a particular project/class. - *

              - * If it is the first time the ElementDescriptor is requested, the method - * will check that the specified class is in fact a custom View class. Once this is - * established, a monitoring for that particular class is initiated. Any change will - * trigger a notification to the {@link ICustomViewDescriptorListener}. - * @param project the project containing the class. - * @param fqClassName the fully qualified name of the class. - * @return a ElementDescriptor or null if the class was not - * a custom View class. - */ - public ElementDescriptor getDescriptor(IProject project, String fqClassName) { - // look in the map first - synchronized (mCustomDescriptorMap) { - HashMap map = mCustomDescriptorMap.get(project); - - if (map != null) { - ElementDescriptor descriptor = map.get(fqClassName); - if (descriptor != null) { - return descriptor; - } - } - - // if we step here, it looks like we haven't created it yet. - // First lets check this is in fact a valid type in the project - - try { - // We expect the project to be both opened and of java type (since it's an android - // project), so we can create a IJavaProject object from our IProject. - IJavaProject javaProject = JavaCore.create(project); - - // replace $ by . in the class name - String javaClassName = fqClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS-2$ - - // look for the IType object for this class - IType type = javaProject.findType(javaClassName); - if (type != null && type.exists()) { - // the type exists. Let's get the parent class and its ViewClassInfo. - - // get the type hierarchy - ITypeHierarchy hierarchy = type.newSupertypeHierarchy( - new NullProgressMonitor()); - - ElementDescriptor parentDescriptor = getDescriptor( - hierarchy.getSuperclass(type), project, hierarchy); - - if (parentDescriptor != null) { - // we have a valid parent, lets create a new ElementDescriptor. - - ViewElementDescriptor descriptor = new ViewElementDescriptor(fqClassName, - fqClassName, // ui_name - fqClassName, // canonical class name - null, // tooltip - null, // sdk_url - getAttributeDescriptor(type, parentDescriptor), - null, // layout attributes - null, // children - false /* mandatory */); - - synchronized (mCustomDescriptorMap) { - map = mCustomDescriptorMap.get(project); - if (map == null) { - map = new HashMap(); - mCustomDescriptorMap.put(project, map); - } - - map.put(fqClassName, descriptor); - } - - //TODO setup listener on this resource change. - - return descriptor; - } - } - } catch (JavaModelException e) { - // there was an error accessing any of the IType, we'll just return null; - } - } - - - return null; - } - - /** - * Computes (if needed) and returns the {@link ElementDescriptor} for the specified type. - * - * @param type - * @param project - * @param typeHierarchy - * @return A ViewElementDescriptor - */ - private ViewElementDescriptor getDescriptor(IType type, IProject project, - ITypeHierarchy typeHierarchy) { - // check if the type is a built-in View class. - List builtInList = LayoutDescriptors.getInstance().getViewDescriptors(); - - String canonicalName = type.getFullyQualifiedName(); - - for (ElementDescriptor desc : builtInList) { - if (desc instanceof ViewElementDescriptor) { - ViewElementDescriptor viewDescriptor = (ViewElementDescriptor)desc; - if (canonicalName.equals(viewDescriptor.getCanonicalClassName())) { - return viewDescriptor; - } - } - } - - // it's not a built-in class? Lets look if the superclass is built-in - IType parentType = typeHierarchy.getSuperclass(type); - if (parentType != null) { - ViewElementDescriptor parentDescriptor = getDescriptor(parentType, project, - typeHierarchy); - - if (parentDescriptor != null) { - // parent class is a valid View class with a descriptor, so we create one - // for this class. - ViewElementDescriptor descriptor = new ViewElementDescriptor(canonicalName, - canonicalName, // ui_name - canonicalName, // canonical name - null, // tooltip - null, // sdk_url - getAttributeDescriptor(type, parentDescriptor), - null, // layout attributes - null, // children - false /* mandatory */); - - // add it to the map - synchronized (mCustomDescriptorMap) { - HashMap map = mCustomDescriptorMap.get(project); - - if (map == null) { - map = new HashMap(); - mCustomDescriptorMap.put(project, map); - } - - map.put(canonicalName, descriptor); - - } - - //TODO setup listener on this resource change. - - return descriptor; - } - } - - // class is neither a built-in view class, nor extend one. return null. - return null; - } - - /** - * Returns the array of {@link AttributeDescriptor} for the specified {@link IType}. - *

              - * The array should contain the descriptor for this type and all its supertypes. - * @param type the type for which the {@link AttributeDescriptor} are returned. - * @param parentDescriptor the {@link ElementDescriptor} of the direct superclass. - */ - private AttributeDescriptor[] getAttributeDescriptor(IType type, - ElementDescriptor parentDescriptor) { - // TODO add the class attribute descriptors to the parent descriptors. - return parentDescriptor.getAttributes(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java deleted file mode 100644 index 8ad2382..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/LayoutDescriptors.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.editors.layout.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.ViewClassInfo; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; -import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * Complete description of the layout structure. - */ -public class LayoutDescriptors { - - // Public attributes names, attributes descriptors and elements descriptors - public static final String ID_ATTR = "id"; //$NON-NLS-1$ - - /** Singleton instance */ - private static LayoutDescriptors sThis; - - /** The document descriptor. Contains all layouts and views linked together. */ - private DocumentDescriptor mDescriptor = - new DocumentDescriptor("layout_doc", null); //$NON-NLS-1$ - - /** The list of all known ViewLayout descriptors. */ - private ArrayList mLayoutDescriptors = new ArrayList(); - - /** The list of all known View (not ViewLayout) descriptors. */ - private ArrayList mViewDescriptors = new ArrayList(); - - /** Returns a singleton instance of the {@link LayoutDescriptors}. */ - public static synchronized LayoutDescriptors getInstance() { - if (sThis == null) { - sThis = new LayoutDescriptors(); - } - return sThis; - } - - /** @return the document descriptor. Contains all layouts and views linked together. */ - public DocumentDescriptor getDescriptor() { - return mDescriptor; - } - - /** @return The read-only list of all known ViewLayout descriptors. */ - public List getLayoutDescriptors() { - return Collections.unmodifiableList(mLayoutDescriptors); - } - - /** @return The read-only list of all known View (not ViewLayout) descriptors. */ - public List getViewDescriptors() { - return Collections.unmodifiableList(mViewDescriptors); - } - - /** - * Updates the document descriptor. - *

              - * It first computes the new children of the descriptor and then update them - * all at once. - *

              - * TODO: differentiate groups from views in the tree UI? => rely on icons - *

              - * - * @param views The list of views in the framework. - * @param layouts The list of layouts in the framework. - */ - public synchronized void updateDescriptors(ViewClassInfo[] views, ViewClassInfo[] layouts) { - ArrayList newViews = new ArrayList(); - if (views != null) { - for (ViewClassInfo info : views) { - ElementDescriptor desc = convertView(info); - newViews.add(desc); - } - } - - ArrayList newLayouts = new ArrayList(); - if (layouts != null) { - for (ViewClassInfo info : layouts) { - ElementDescriptor desc = convertView(info); - newLayouts.add(desc); - } - } - - ArrayList newDescriptors = new ArrayList(); - newDescriptors.addAll(newLayouts); - newDescriptors.addAll(newViews); - ElementDescriptor[] newArray = newDescriptors.toArray( - new ElementDescriptor[newDescriptors.size()]); - - // Link all layouts to everything else here.. recursively - for (ElementDescriptor layoutDesc : newLayouts) { - layoutDesc.setChildren(newArray); - } - - mViewDescriptors = newViews; - mLayoutDescriptors = newLayouts; - mDescriptor.setChildren(newArray); - } - - /** - * Creates an element descriptor from a given {@link ViewClassInfo}. - */ - private ElementDescriptor convertView(ViewClassInfo info) { - String xml_name = info.getShortClassName(); - String tooltip = info.getJavaDoc(); - - // Process all View attributes - ArrayList attributes = new ArrayList(); - DescriptorsUtils.appendAttributes(attributes, - null, // elementName - AndroidConstants.NS_RESOURCES, - info.getAttributes(), - null, // requiredAttributes - null /* overrides */); - - for (ViewClassInfo link = info.getSuperClass(); - link != null; - link = link.getSuperClass()) { - AttributeInfo[] attrList = link.getAttributes(); - if (attrList.length > 0) { - attributes.add(new SeparatorAttributeDescriptor( - String.format("Attributes from %1$s", link.getShortClassName()))); - DescriptorsUtils.appendAttributes(attributes, - null, // elementName - AndroidConstants.NS_RESOURCES, - attrList, - null, // requiredAttributes - null /* overrides */); - } - } - - // Process all LayoutParams attributes - ArrayList layoutAttributes = new ArrayList(); - LayoutParamsInfo layoutParams = info.getLayoutData(); - - for(; layoutParams != null; layoutParams = layoutParams.getSuperClass()) { - boolean need_separator = true; - for (AttributeInfo attr_info : layoutParams.getAttributes()) { - if (DescriptorsUtils.containsAttribute(layoutAttributes, - AndroidConstants.NS_RESOURCES, attr_info)) { - continue; - } - if (need_separator) { - String title; - if (layoutParams.getShortClassName().equals( - AndroidConstants.CLASS_LAYOUTPARAMS)) { - title = String.format("Layout Attributes from %1$s", - layoutParams.getViewLayoutClass().getShortClassName()); - } else { - title = String.format("Layout Attributes from %1$s (%2$s)", - layoutParams.getViewLayoutClass().getShortClassName(), - layoutParams.getShortClassName()); - } - layoutAttributes.add(new SeparatorAttributeDescriptor(title)); - need_separator = false; - } - DescriptorsUtils.appendAttribute(layoutAttributes, - null, // elementName - AndroidConstants.NS_RESOURCES, - attr_info, - false, // required - null /* overrides */); - } - } - - return new ViewElementDescriptor(xml_name, - xml_name, // ui_name - info.getCanonicalClassName(), - tooltip, - null, // sdk_url - attributes.toArray(new AttributeDescriptor[attributes.size()]), - layoutAttributes.toArray(new AttributeDescriptor[layoutAttributes.size()]), - null, // children - false /* mandatory */); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java deleted file mode 100644 index d718ebd..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/descriptors/ViewElementDescriptor.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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.editors.layout.descriptors; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.layout.uimodel.UiViewElementNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * {@link ViewElementDescriptor} describes the properties expected for a given XML element node - * representing a class in an XML Layout file. - * - * @see ElementDescriptor - */ -public final class ViewElementDescriptor extends ElementDescriptor { - - private String mCanonicalClassName; - - /** The list of layout attributes. Can be empty but not null. */ - private AttributeDescriptor[] mLayoutAttributes; - - - /** - * Constructs a new {@link ViewElementDescriptor} based on its XML name, UI name, - * the canonical name of the class it represents, its tooltip, its SDK url, its attributes list, - * its children list and its mandatory flag. - * - * @param xml_name The XML element node name. Case sensitive. - * @param ui_name The XML element name for the user interface, typically capitalized. - * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is - * representing. - * @param tooltip An optional tooltip. Can be null or empty. - * @param sdk_url An optional SKD URL. Can be null or empty. - * @param attributes The list of allowed attributes. Can be null or empty. - * @param layoutAttributes The list of layout attributes. Can be null or empty. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - */ - public ViewElementDescriptor(String xml_name, String ui_name, - String canonicalClassName, - String tooltip, String sdk_url, - AttributeDescriptor[] attributes, AttributeDescriptor[] layoutAttributes, - ElementDescriptor[] children, boolean mandatory) { - super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); - mCanonicalClassName = canonicalClassName; - mLayoutAttributes = layoutAttributes != null ? layoutAttributes : new AttributeDescriptor[0]; - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name, the canonical - * name of the class it represents, and its children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is - * representing. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - */ - public ViewElementDescriptor(String xml_name, String canonicalClassName, - ElementDescriptor[] children, - boolean mandatory) { - super(xml_name, children, mandatory); - mCanonicalClassName = canonicalClassName; - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name and children list. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is - * representing. - * @param children The list of allowed children. Can be null or empty. - */ - public ViewElementDescriptor(String xml_name, String canonicalClassName, - ElementDescriptor[] children) { - super(xml_name, children); - mCanonicalClassName = canonicalClassName; - } - - /** - * Constructs a new {@link ElementDescriptor} based on its XML name and on the canonical - * name of the class it represents. - * The UI name is build by capitalizing the XML name. - * The UI nodes will be non-mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param canonicalClassName The canonical class name the {@link ViewElementDescriptor} is - * representing. - */ - public ViewElementDescriptor(String xml_name, String canonicalClassName) { - super(xml_name); - mCanonicalClassName = canonicalClassName; - } - - /** - * Returns the canonical name of the class represented by this element descriptor. - */ - public String getCanonicalClassName() { - return mCanonicalClassName; - } - - /** Returns the list of layout attributes. Can be empty but not null. */ - public AttributeDescriptor[] getLayoutAttributes() { - return mLayoutAttributes; - } - - /** - * @return A new {@link UiViewElementNode} linked to this descriptor. - */ - @Override - public UiElementNode createUiNode() { - return new UiViewElementNode(this); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java deleted file mode 100644 index 53a87f5..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentEditPart.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.draw2d.AbstractBackground; -import org.eclipse.draw2d.ColorConstants; -import org.eclipse.draw2d.FreeformLayer; -import org.eclipse.draw2d.FreeformLayout; -import org.eclipse.draw2d.Graphics; -import org.eclipse.draw2d.IFigure; -import org.eclipse.draw2d.Label; -import org.eclipse.draw2d.geometry.Insets; -import org.eclipse.draw2d.geometry.Rectangle; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.PaletteData; -import org.eclipse.swt.widgets.Display; - -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; - -/** - * Graphical edit part for the root document. - *

              - * It acts as a simple container. - */ -public class UiDocumentEditPart extends UiElementEditPart { - - private Display mDisplay; - private FreeformLayer mLayer; - private ImageBackground mImage; - private Label mChild = null; - - final static class ImageBackground extends AbstractBackground { - - private BufferedImage mBufferedImage; - private Image mImage; - - ImageBackground() { - } - - ImageBackground(BufferedImage image, Display display) { - setImage(image, display); - } - - @Override - public void paintBackground(IFigure figure, Graphics graphics, Insets insets) { - if (mImage != null) { - Rectangle rect = getPaintRectangle(figure, insets); - graphics.drawImage(mImage, rect.x, rect.y); - } - } - - void setImage(BufferedImage image, Display display) { - if (image != null) { - int[] data = ((DataBufferInt)image.getData().getDataBuffer()).getData(); - - ImageData imageData = new ImageData(image.getWidth(), image.getHeight(), 32, - new PaletteData(0x00FF0000, 0x0000FF00, 0x000000FF)); - - imageData.setPixels(0, 0, data.length, data, 0); - - mImage = new Image(display, imageData); - } else { - mImage = null; - } - } - - BufferedImage getBufferedImage() { - return mBufferedImage; - } - } - - public UiDocumentEditPart(UiDocumentNode uiDocumentNode, Display display) { - super(uiDocumentNode); - mDisplay = display; - } - - @Override - protected IFigure createFigure() { - mLayer = new FreeformLayer(); - mLayer.setLayoutManager(new FreeformLayout()); - - mLayer.setOpaque(true); - mLayer.setBackgroundColor(ColorConstants.lightGray); - - return mLayer; - } - - @Override - protected void refreshVisuals() { - UiElementNode model = (UiElementNode)getModel(); - - Object editData = model.getEditData(); - if (editData instanceof BufferedImage) { - BufferedImage image = (BufferedImage)editData; - - if (mImage == null || image != mImage.getBufferedImage()) { - mImage = new ImageBackground(image, mDisplay); - } - - mLayer.setBorder(mImage); - - if (mChild != null && mChild.getParent() == mLayer) { - mLayer.remove(mChild); - } - } else if (editData instanceof String) { - mLayer.setBorder(null); - if (mChild == null) { - mChild = new Label(); - } - mChild.setText((String)editData); - - if (mChild != null && mChild.getParent() != mLayer) { - mLayer.add(mChild); - } - Rectangle bounds = mChild.getTextBounds(); - bounds.x = bounds.y = 0; - mLayer.setConstraint(mChild, bounds); - } - - // refresh the children as well - refreshChildrenVisuals(); - } - - @Override - protected void hideSelection() { - // no selection at this level. - } - - @Override - protected void showSelection() { - // no selection at this level. - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java deleted file mode 100644 index af22afb..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiDocumentTreeEditPart.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; - -import java.util.List; - -/** - * Implementation of {@link UiElementTreeEditPart} for the document root. - */ -public class UiDocumentTreeEditPart extends UiElementTreeEditPart { - - public UiDocumentTreeEditPart(UiDocumentNode model) { - super(model); - } - - @SuppressWarnings("unchecked") - @Override - protected List getModelChildren() { - return getUiNode().getUiChildren(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java deleted file mode 100644 index 548a3a2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementEditPart.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.draw2d.geometry.Rectangle; -import org.eclipse.gef.DragTracker; -import org.eclipse.gef.EditPart; -import org.eclipse.gef.EditPolicy; -import org.eclipse.gef.GraphicalEditPart; -import org.eclipse.gef.Request; -import org.eclipse.gef.editparts.AbstractGraphicalEditPart; -import org.eclipse.gef.editpolicies.SelectionEditPolicy; -import org.eclipse.gef.tools.SelectEditPartTracker; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; - -import java.util.List; - -/** - * An {@link EditPart} for a {@link UiElementNode}. - */ -public abstract class UiElementEditPart extends AbstractGraphicalEditPart - implements IUiUpdateListener { - - public UiElementEditPart(UiElementNode uiElementNode) { - setModel(uiElementNode); - } - - //------------------------- - // Derived classes must define these - - abstract protected void hideSelection(); - abstract protected void showSelection(); - - //------------------------- - // Base class overrides - - @Override - public DragTracker getDragTracker(Request request) { - return new SelectEditPartTracker(this); - } - - @Override - protected void createEditPolicies() { - installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, new SelectionEditPolicy() { - @Override - protected void hideSelection() { - UiElementEditPart.this.hideSelection(); - } - - @Override - protected void showSelection() { - UiElementEditPart.this.showSelection(); - } - }); - // TODO add editing policies - } - - /* (non-javadoc) - * Returns a List containing the children model objects. - * Must not return null, instead use the super which returns an empty list. - */ - @SuppressWarnings("unchecked") - @Override - protected List getModelChildren() { - return getUiNode().getUiChildren(); - } - - @Override - public void activate() { - super.activate(); - getUiNode().addUpdateListener(this); - } - - @Override - public void deactivate() { - super.deactivate(); - getUiNode().removeUpdateListener(this); - } - - @Override - protected void refreshVisuals() { - if (getFigure().getParent() != null) { - ((GraphicalEditPart) getParent()).setLayoutConstraint(this, getFigure(), getBounds()); - } - - // update the visuals of the children as well - refreshChildrenVisuals(); - } - - protected void refreshChildrenVisuals() { - if (children != null) { - for (Object child : children) { - if (child instanceof UiElementEditPart) { - UiElementEditPart childPart = (UiElementEditPart)child; - childPart.refreshVisuals(); - } - } - } - } - - //------------------------- - // IUiUpdateListener implementation - - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - // TODO: optimize by refreshing only when needed - switch(state) { - case ATTR_UPDATED: - refreshVisuals(); - break; - case CHILDREN_CHANGED: - refreshChildren(); - - // new children list, need to update the layout - refreshVisuals(); - break; - case CREATED: - refreshVisuals(); - break; - case DELETED: - // pass - break; - } - } - - //------------------------- - // Local methods - - /** @return The object model casted to an {@link UiElementNode} */ - protected final UiElementNode getUiNode() { - return (UiElementNode) getModel(); - } - - protected final ElementDescriptor getDescriptor() { - return getUiNode().getDescriptor(); - } - - protected final UiElementEditPart getEditPartParent() { - EditPart parent = getParent(); - if (parent instanceof UiElementEditPart) { - return (UiElementEditPart)parent; - } - return null; - } - - /** - * Returns a given XML attribute. - * @param attrbName The local name of the attribute. - * @return the attribute as a {@link String}, if it exists, or null - */ - protected final String getStringAttr(String attrName) { - UiElementNode uiNode = getUiNode(); - if (uiNode.getXmlNode() != null) { - Node xmlNode = uiNode.getXmlNode(); - if (xmlNode != null) { - NamedNodeMap nodeAttributes = xmlNode.getAttributes(); - if (nodeAttributes != null) { - Node attr = nodeAttributes.getNamedItemNS( - AndroidConstants.NS_RESOURCES, attrName); - if (attr != null) { - return attr.getNodeValue(); - } - } - } - } - return null; - } - - protected final Rectangle getBounds() { - UiElementNode model = (UiElementNode)getModel(); - - Object editData = model.getEditData(); - if (editData instanceof Rectangle) { - return (Rectangle)editData; // return a copy? - } - - // return a dummy rect - return new Rectangle(0, 0, 0, 0); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java deleted file mode 100644 index fd788dd..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPart.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.gef.editparts.AbstractTreeEditPart; -import org.eclipse.swt.graphics.Image; -import org.eclipse.ui.views.contentoutline.IContentOutlinePage; - -/** - * Base {@link AbstractTreeEditPart} to represent {@link UiElementNode} objects in the - * {@link IContentOutlinePage} linked to the layout editor. - */ -public class UiElementTreeEditPart extends AbstractTreeEditPart { - - public UiElementTreeEditPart(UiElementNode uiElementNode) { - setModel(uiElementNode); - } - - @Override - protected void createEditPolicies() { - // TODO Auto-generated method stub - super.createEditPolicies(); - } - - @Override - protected Image getImage() { - return getUiNode().getDescriptor().getIcon(); - } - - @Override - protected String getText() { - return getUiNode().getShortDescription(); - } - - @Override - public void activate() { - if (!isActive()) { - super.activate(); - // TODO - } - } - - @Override - public void deactivate() { - if (isActive()) { - super.deactivate(); - // TODO - } - } - - /** - * Returns the casted model object represented by this {@link AbstractTreeEditPart}. - */ - protected UiElementNode getUiNode() { - return (UiElementNode)getModel(); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java deleted file mode 100644 index de6c404..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementTreeEditPartFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.gef.EditPart; -import org.eclipse.gef.EditPartFactory; -import org.eclipse.gef.editparts.AbstractTreeEditPart; -import org.eclipse.ui.views.contentoutline.IContentOutlinePage; - -/** - * {@link EditPartFactory} to create {@link AbstractTreeEditPart} for {@link UiElementNode} objects. - * These objects are used in the {@link IContentOutlinePage} linked to the layout editor. - */ -public class UiElementTreeEditPartFactory implements EditPartFactory { - - public EditPart createEditPart(EditPart context, Object model) { - if (model instanceof UiDocumentNode) { - return new UiDocumentTreeEditPart((UiDocumentNode) model); - } else if (model instanceof UiElementNode) { - UiElementNode node = (UiElementNode) model; - if (node.getDescriptor().hasChildren()) { - return new UiLayoutTreeEditPart(node); - } else { - return new UiViewTreeEditPart(node); - } - } - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java deleted file mode 100644 index 18dcd9c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiElementsEditPartFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.gef.EditPart; -import org.eclipse.gef.EditPartFactory; -import org.eclipse.swt.widgets.Display; - -/** - * A factory that returns the appropriate {@link EditPart} for a given model object. - *

              - * The only model objects we use are {@link UiElementNode} objects and they are - * edited using {@link UiElementEditPart}. - */ -public class UiElementsEditPartFactory implements EditPartFactory { - - private Display mDisplay; - - public UiElementsEditPartFactory(Display display) { - mDisplay = display; - } - - public EditPart createEditPart(EditPart context, Object model) { - if (model instanceof UiDocumentNode) { - return new UiDocumentEditPart((UiDocumentNode) model, mDisplay); - } else if (model instanceof UiElementNode) { - UiElementNode node = (UiElementNode) model; - - if (node.getDescriptor().hasChildren()) { - return new UiLayoutEditPart(node); - } - - return new UiViewEditPart(node); - } - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java deleted file mode 100644 index d9433ca..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutEditPart.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.draw2d.ColorConstants; -import org.eclipse.draw2d.IFigure; -import org.eclipse.draw2d.Label; -import org.eclipse.draw2d.LineBorder; -import org.eclipse.draw2d.XYLayout; -import org.eclipse.gef.EditPolicy; -import org.eclipse.gef.commands.Command; -import org.eclipse.gef.editpolicies.ContainerEditPolicy; -import org.eclipse.gef.requests.CreateRequest; - -/** - * Graphical edit part for an {@link UiElementNode} that represents a ViewLayout. - *

              - * It acts as a simple container. - */ -public final class UiLayoutEditPart extends UiElementEditPart { - - public UiLayoutEditPart(UiElementNode uiElementNode) { - super(uiElementNode); - } - - @Override - protected void createEditPolicies() { - super.createEditPolicies(); - - installEditPolicy(EditPolicy.CONTAINER_ROLE, new ContainerEditPolicy() { - @Override - protected Command getCreateCommand(CreateRequest request) { - return null; - } - }); - } - - @Override - protected IFigure createFigure() { - Label f = new Label(); - f.setLayoutManager(new XYLayout()); - return f; - } - - @Override - protected void hideSelection() { - IFigure f = getFigure(); - if (f instanceof Label) { - f.setBorder(null); - } - } - - @Override - protected void showSelection() { - IFigure f = getFigure(); - if (f instanceof Label) { - f.setBorder(new LineBorder(ColorConstants.red, 1)); - } - } - - public void showDropTarget() { - IFigure f = getFigure(); - if (f instanceof Label) { - f.setBorder(new LineBorder(ColorConstants.blue, 1)); - } - } - - public void hideDropTarget() { - hideSelection(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java deleted file mode 100644 index 4359e23..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiLayoutTreeEditPart.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import java.util.List; - -/** - * Implementation of {@link UiElementTreeEditPart} for layout objects. - */ -public class UiLayoutTreeEditPart extends UiElementTreeEditPart { - - public UiLayoutTreeEditPart(UiElementNode node) { - super(node); - } - - @SuppressWarnings("unchecked") - @Override - protected List getModelChildren() { - return getUiNode().getUiChildren(); - } - - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java deleted file mode 100644 index b427ead..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewEditPart.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.draw2d.ColorConstants; -import org.eclipse.draw2d.IFigure; -import org.eclipse.draw2d.Label; -import org.eclipse.draw2d.LineBorder; - -/** - * Graphical edit part for an {@link UiElementNode} that represents a View. - */ -public class UiViewEditPart extends UiElementEditPart { - - public UiViewEditPart(UiElementNode uiElementNode) { - super(uiElementNode); - } - - @Override - protected IFigure createFigure() { - Label f = new Label(); - return f; - } - - @Override - protected void hideSelection() { - IFigure f = getFigure(); - if (f instanceof Label) { - f.setBorder(null); - } - } - - @Override - protected void showSelection() { - IFigure f = getFigure(); - if (f instanceof Label) { - f.setBorder(new LineBorder(ColorConstants.red, 1)); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java deleted file mode 100644 index 62b5e8a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/parts/UiViewTreeEditPart.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.editors.layout.parts; - -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Implementation of {@link UiElementTreeEditPart} for view objects. - */ -public class UiViewTreeEditPart extends UiElementTreeEditPart { - - public UiViewTreeEditPart(UiElementNode node) { - super(node); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java deleted file mode 100644 index 75cf4b6..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/layout/uimodel/UiViewElementNode.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.editors.layout.uimodel; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Specialized version of {@link UiElementNode} for the {@link ViewElementDescriptor}s. - */ -public class UiViewElementNode extends UiElementNode { - - private AttributeDescriptor[] mCachedAttributeDescriptors; - - public UiViewElementNode(ViewElementDescriptor elementDescriptor) { - super(elementDescriptor); - } - - /** - * Returns an AttributeDescriptor array that depends on the current UiParent. - *

              - * The array merges both "direct" attributes with the descriptor layout attributes. - * The array instance is cached and cleared if the UiParent is changed. - */ - @Override - public AttributeDescriptor[] getAttributeDescriptors() { - if (mCachedAttributeDescriptors != null) { - return mCachedAttributeDescriptors; - } - - UiElementNode ui_parent = getUiParent(); - AttributeDescriptor[] direct_attrs = super.getAttributeDescriptors(); - mCachedAttributeDescriptors = direct_attrs; - - AttributeDescriptor[] layout_attrs = null; - boolean need_xmlns = false; - - if (ui_parent instanceof UiDocumentNode) { - // Limitation: right now the layout behaves as if everything was - // owned by a FrameLayout. - // TODO replace by something user-configurable. - for (ElementDescriptor desc : LayoutDescriptors.getInstance().getLayoutDescriptors()) { - if (desc instanceof ViewElementDescriptor && - desc.getXmlName().equals(AndroidConstants.CLASS_FRAMELAYOUT)) { - layout_attrs = ((ViewElementDescriptor) desc).getLayoutAttributes(); - need_xmlns = true; - break; - } - } - } else if (ui_parent instanceof UiViewElementNode){ - layout_attrs = - ((ViewElementDescriptor) ui_parent.getDescriptor()).getLayoutAttributes(); - } - - if (layout_attrs == null || layout_attrs.length == 0) { - return mCachedAttributeDescriptors; - } - - mCachedAttributeDescriptors = - new AttributeDescriptor[direct_attrs.length + - layout_attrs.length + - (need_xmlns ? 1 : 0)]; - System.arraycopy(direct_attrs, 0, - mCachedAttributeDescriptors, 0, - direct_attrs.length); - System.arraycopy(layout_attrs, 0, - mCachedAttributeDescriptors, direct_attrs.length, - layout_attrs.length); - if (need_xmlns) { - AttributeDescriptor desc = new XmlnsAttributeDescriptor( - "android", //$NON-NLS-1$ - AndroidConstants.NS_RESOURCES); - mCachedAttributeDescriptors[direct_attrs.length + layout_attrs.length] = desc; - } - - return mCachedAttributeDescriptors; - } - - /** - * Sets the parent of this UI node. - *

              - * Also removes the cached AttributeDescriptor array that depends on the current UiParent. - */ - @Override - protected void setUiParent(UiElementNode parent) { - super.setUiParent(parent); - mCachedAttributeDescriptors = null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java deleted file mode 100644 index e43c984..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestContentAssist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest; - -import com.android.ide.eclipse.editors.AndroidContentAssist; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; - -/** - * Content Assist Processor for AndroidManifest.xml - */ -final class ManifestContentAssist extends AndroidContentAssist { - - /** - * Constructor for ManifestContentAssist - */ - public ManifestContentAssist() { - super(new ElementDescriptor[] { AndroidManifestDescriptors.MANIFEST_ELEMENT }); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java deleted file mode 100644 index 666a066..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditor.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidXPathFactory; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.manifest.pages.ApplicationPage; -import com.android.ide.eclipse.editors.manifest.pages.InstrumentationPage; -import com.android.ide.eclipse.editors.manifest.pages.OverviewPage; -import com.android.ide.eclipse.editors.manifest.pages.PermissionPage; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarker; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import java.util.List; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; - -/** - * Multi-page form editor for AndroidManifest.xml. - */ -public final class ManifestEditor extends AndroidEditor { - private final static String EMPTY = ""; //$NON-NLS-1$ - - - /** Root node of the UI element hierarchy */ - private UiElementNode mUiManifestNode; - /** Listener to update the root node if the resource framework changes */ - private Runnable mResourceRefreshListener; - /** The Application Page tab */ - private ApplicationPage mAppPage; - /** The Overview Manifest Page tab */ - private OverviewPage mOverviewPage; - - /** - * Creates the form editor for AndroidManifest.xml. - */ - public ManifestEditor() { - super(); - initUiManifestNode(); - } - - /** - * Return the root node of the UI element hierarchy, which here - * is the "manifest" node. - */ - @Override - public UiElementNode getUiRootNode() { - return mUiManifestNode; - } - - // ---- Base Class Overrides ---- - - @Override - public void dispose() { - if (mResourceRefreshListener != null) { - EditorsPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener = null; - } - super.dispose(); - } - - /** - * Returns whether the "save as" operation is supported by this editor. - *

              - * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return true; - } - - /** - * Creates the various form pages. - */ - @Override - protected void createFormPages() { - try { - addPage(mOverviewPage = new OverviewPage(this)); - addPage(mAppPage = new ApplicationPage(this)); - addPage(new PermissionPage(this)); - addPage(new InstrumentationPage(this)); - } catch (PartInitException e) { - EditorsPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ - } - } - - /* (non-java doc) - * Change the tab/title name to include the project name. - */ - @Override - protected void setInput(IEditorInput input) { - super.setInput(input); - IFile inputFile = getInputFile(); - if (inputFile != null) { - setPartName(String.format("%1$s Manifest", inputFile.getProject().getName())); - } - } - - /** - * Processes the new XML Model, which XML root node is given. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - @Override - protected void xmlModelChanged(Document xml_doc) { - mUiManifestNode.setXmlDocument(xml_doc); - if (xml_doc != null) { - ElementDescriptor manifest_desc = mUiManifestNode.getDescriptor(); - try { - XPath xpath = AndroidXPathFactory.newXPath(); - Node node = (Node) xpath.evaluate("/" + manifest_desc.getXmlName(), //$NON-NLS-1$ - xml_doc, - XPathConstants.NODE); - assert node != null && node.getNodeName().equals(manifest_desc.getXmlName()); - - // Refresh the manifest UI node and all its descendants - mUiManifestNode.loadFromXmlNode(node); - - startMonitoringMarkers(); - } catch (XPathExpressionException e) { - EditorsPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ - manifest_desc.getXmlName()); - } - } - - super.xmlModelChanged(xml_doc); - } - - /** - * Reads and processes the current markers and adds a listener for marker changes. - */ - private void startMonitoringMarkers() { - final IFile inputFile = getInputFile(); - if (inputFile != null) { - updateFromExistingMarkers(inputFile); - - ResourceMonitor.getMonitor().addFileListener(new IFileListener() { - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - if (file.equals(inputFile)) { - processMarkerChanges(markerDeltas); - } - } - }, IResourceDelta.CHANGED); - } - } - - /** - * Processes the markers of the specified {@link IFile} and updates the error status of - * {@link UiElementNode}s and {@link UiAttributeNode}s. - * @param inputFile the file being edited. - */ - private void updateFromExistingMarkers(IFile inputFile) { - try { - // get the markers for the file - IMarker[] markers = inputFile.findMarkers(AndroidConstants.MARKER_ANDROID, true, - IResource.DEPTH_ZERO); - - UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( - AndroidManifestDescriptors.APPLICATION_ELEMENT.getXmlName()); - List children = app_ui_node.getUiChildren(); - - for (IMarker marker : markers) { - processMarker(marker, children, IResourceDelta.ADDED); - } - } catch (CoreException e) { - // findMarkers can throw an exception, in which case, we'll do nothing. - } - } - - /** - * Processes a {@link IMarker} change. - * @param markerDeltas the list of {@link IMarkerDelta} - */ - private void processMarkerChanges(IMarkerDelta[] markerDeltas) { - UiElementNode app_ui_node = mUiManifestNode.findUiChildNode( - AndroidManifestDescriptors.APPLICATION_ELEMENT.getXmlName()); - List children = app_ui_node.getUiChildren(); - - for (IMarkerDelta markerDelta : markerDeltas) { - processMarker(markerDelta.getMarker(), children, markerDelta.getKind()); - } - } - - /** - * Processes a new/old/updated marker. - * @param marker The marker being added/removed/changed - * @param nodeList the list of activity/service/provider/receiver nodes. - * @param kind the change kind. Can be {@link IResourceDelta#ADDED}, - * {@link IResourceDelta#REMOVED}, or {@link IResourceDelta#CHANGED} - */ - private void processMarker(IMarker marker, List nodeList, int kind) { - // get the data from the marker - String nodeType = marker.getAttribute(AndroidConstants.MARKER_ATTR_TYPE, EMPTY); - if (nodeType == EMPTY) { - return; - } - - String className = marker.getAttribute(AndroidConstants.MARKER_ATTR_CLASS, EMPTY); - if (className == EMPTY) { - return; - } - - for (UiElementNode ui_node : nodeList) { - if (ui_node.getDescriptor().getXmlName().equals(nodeType)) { - for (UiAttributeNode attr : ui_node.getUiAttributes()) { - if (attr.getDescriptor().getXmlLocalName().equals( - AndroidManifestDescriptors.ANDROID_NAME_ATTR)) { - if (attr.getCurrentValue().equals(className)) { - if (kind == IResourceDelta.REMOVED) { - attr.setHasError(false); - } else { - attr.setHasError(true); - } - return; - } - } - } - } - } - } - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - */ - private void initUiManifestNode() { - // The manifest UI node is always created, even if there's no corresponding XML node. - if (mUiManifestNode == null) { - ElementDescriptor manifest_desc = AndroidManifestDescriptors.MANIFEST_ELEMENT; - mUiManifestNode = manifest_desc.createUiNode(); - mUiManifestNode.setEditor(this); - - // Similarly, always create the /manifest/application and /manifest/uses-sdk nodes - ElementDescriptor app_desc = AndroidManifestDescriptors.APPLICATION_ELEMENT; - boolean present = false; - for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { - if (ui_node.getDescriptor() == app_desc) { - present = true; - break; - } - } - if (!present) { - mUiManifestNode.appendNewUiChild(app_desc); - } - - app_desc = AndroidManifestDescriptors.USES_SDK_ELEMENT; - present = false; - for (UiElementNode ui_node : mUiManifestNode.getUiChildren()) { - if (ui_node.getDescriptor() == app_desc) { - present = true; - break; - } - } - if (!present) { - mUiManifestNode.appendNewUiChild(app_desc); - } - - // Add a listener to refresh the root node if the resource framework changes - // by forcing it to parse its own XML - mResourceRefreshListener = new Runnable() { - public void run() { - commitPages(false /* onSave */); - - mUiManifestNode.reloadFromXmlNode(mUiManifestNode.getXmlNode()); - if (mOverviewPage != null) { - mOverviewPage.refreshUiApplicationNode(); - } - if (mAppPage != null) { - mAppPage.refreshUiApplicationNode(); - } - } - }; - EditorsPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener.run(); - } - } - - /** - * Returns the {@link IFile} being edited, or null if it couldn't be computed. - */ - private IFile getInputFile() { - IEditorInput input = getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput) input; - return fileInput.getFile(); - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java deleted file mode 100644 index 911faa1..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestEditorContributor.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest; - -import org.eclipse.jface.action.IAction; -import org.eclipse.ui.IActionBars; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.actions.ActionFactory; -import org.eclipse.ui.ide.IDEActionFactory; -import org.eclipse.ui.part.MultiPageEditorActionBarContributor; -import org.eclipse.ui.texteditor.ITextEditor; -import org.eclipse.ui.texteditor.ITextEditorActionConstants; - -/** - * Manages the installation/deinstallation of global actions for multi-page - * editors. Responsible for the redirection of global actions to the active - * editor. Multi-page contributor replaces the contributors for the individual - * editors in the multi-page editor. - * - * TODO: Doesn't look like we need this. Remove it if not needed. - * @deprecated - */ -final class ManifestEditorContributor extends MultiPageEditorActionBarContributor { - private IEditorPart mActiveEditorPart; - - /** - * Creates a multi-page contributor. - * - * Marked as Private so it can't be instanciated. This is a cheap way to make sure - * it's not being used. As noted in constructor, should be removed if not used. - * @deprecated - */ - private ManifestEditorContributor() { - super(); - } - - /** - * Returns the action registed with the given text editor. - * - * @return IAction or null if editor is null. - */ - protected IAction getAction(ITextEditor editor, String actionID) { - return (editor == null ? null : editor.getAction(actionID)); - } - - /* - * (non-JavaDoc) Method declared in - * AbstractMultiPageEditorActionBarContributor. - */ - - @Override - public void setActivePage(IEditorPart part) { - if (mActiveEditorPart == part) - return; - - mActiveEditorPart = part; - - IActionBars actionBars = getActionBars(); - if (actionBars != null) { - - ITextEditor editor = - (part instanceof ITextEditor) ? (ITextEditor)part : null; - - actionBars.setGlobalActionHandler(ActionFactory.DELETE.getId(), - getAction(editor, ITextEditorActionConstants.DELETE)); - actionBars.setGlobalActionHandler(ActionFactory.UNDO.getId(), - getAction(editor, ITextEditorActionConstants.UNDO)); - actionBars.setGlobalActionHandler(ActionFactory.REDO.getId(), - getAction(editor, ITextEditorActionConstants.REDO)); - actionBars.setGlobalActionHandler(ActionFactory.CUT.getId(), - getAction(editor, ITextEditorActionConstants.CUT)); - actionBars.setGlobalActionHandler(ActionFactory.COPY.getId(), - getAction(editor, ITextEditorActionConstants.COPY)); - actionBars.setGlobalActionHandler(ActionFactory.PASTE.getId(), - getAction(editor, ITextEditorActionConstants.PASTE)); - actionBars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), - getAction(editor, ITextEditorActionConstants.SELECT_ALL)); - actionBars.setGlobalActionHandler(ActionFactory.FIND.getId(), - getAction(editor, ITextEditorActionConstants.FIND)); - actionBars.setGlobalActionHandler( - IDEActionFactory.BOOKMARK.getId(), getAction(editor, - IDEActionFactory.BOOKMARK.getId())); - actionBars.updateActionBars(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java deleted file mode 100644 index e33e1ef..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/ManifestSourceViewerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest; - - -import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; - -/** - * Source Viewer Configuration that calls in ManifestContentAssist. - */ -public final class ManifestSourceViewerConfig extends AndroidSourceViewerConfig { - - public ManifestSourceViewerConfig() { - super(new ManifestContentAssist()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java deleted file mode 100644 index 171eaee..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/AndroidManifestDescriptors.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ReferenceAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; - -import org.eclipse.core.runtime.IStatus; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeSet; -import java.util.Map.Entry; - - -/** - * Complete description of the AndroidManifest.xml structure. - *

              - * The root element are static instances which always exists. - * However their sub-elements and attributes are created only when the SDK changes or is - * loaded the first time. - */ -public class AndroidManifestDescriptors { - - private static final String MANIFEST_NODE_NAME = "manifest"; //$NON-NLS-1$ - private static final String ANDROID_MANIFEST_STYLEABLE = "AndroidManifest"; //$NON-NLS-1$ - - // Public attributes names, attributes descriptors and elements descriptors - - public static final String ANDROID_LABEL_ATTR = "label"; //$NON-NLS-1$ - public static final String ANDROID_NAME_ATTR = "name"; //$NON-NLS-1$ - public static final String PACKAGE_ATTR = "package"; //$NON-NLS-1$ - - /** The {@link ElementDescriptor} for the root Manifest element. */ - public static final ElementDescriptor MANIFEST_ELEMENT; - /** The {@link ElementDescriptor} for the root Application element. */ - public static final ElementDescriptor APPLICATION_ELEMENT; - - /** The {@link ElementDescriptor} for the root Instrumentation element. */ - public static final ElementDescriptor INTRUMENTATION_ELEMENT; - /** The {@link ElementDescriptor} for the root Permission element. */ - public static final ElementDescriptor PERMISSION_ELEMENT; - /** The {@link ElementDescriptor} for the root UsesPermission element. */ - public static final ElementDescriptor USES_PERMISSION_ELEMENT; - /** The {@link ElementDescriptor} for the root UsesSdk element. */ - public static final ElementDescriptor USES_SDK_ELEMENT; - - /** The {@link ElementDescriptor} for the root PermissionGroup element. */ - public static final ElementDescriptor PERMISSION_GROUP_ELEMENT; - /** The {@link ElementDescriptor} for the root PermissionTree element. */ - public static final ElementDescriptor PERMISSION_TREE_ELEMENT; - - /** Private package attribute for the manifest element. Needs to be handled manually. */ - private static final TextAttributeDescriptor PACKAGE_ATTR_DESC; - - static { - APPLICATION_ELEMENT = createElement("application", null, true); //$NON-NLS-1$ + no child & mandatory - INTRUMENTATION_ELEMENT = createElement("instrumentation"); //$NON-NLS-1$ - - PERMISSION_ELEMENT = createElement("permission"); //$NON-NLS-1$ - USES_PERMISSION_ELEMENT = createElement("uses-permission"); //$NON-NLS-1$ - USES_SDK_ELEMENT = createElement("uses-sdk", null, true); //$NON-NLS-1$ + no child & mandatory - - PERMISSION_GROUP_ELEMENT = createElement("permission-group"); //$NON-NLS-1$ - PERMISSION_TREE_ELEMENT = createElement("permission-tree"); //$NON-NLS-1$ - - MANIFEST_ELEMENT = createElement( - MANIFEST_NODE_NAME, // xml name - new ElementDescriptor[] { - APPLICATION_ELEMENT, - INTRUMENTATION_ELEMENT, - PERMISSION_ELEMENT, - USES_PERMISSION_ELEMENT, - PERMISSION_GROUP_ELEMENT, - PERMISSION_TREE_ELEMENT, - USES_SDK_ELEMENT, - }, - true /* mandatory */); - - // The "package" attribute is treated differently as it doesn't have the standard - // Android XML namespace. - PACKAGE_ATTR_DESC = new PackageAttributeDescriptor(PACKAGE_ATTR, - "Package", - null /* nsUri */, - "This attribute gives a unique name for the package, using a Java-style naming convention to avoid name collisions.\nFor example, applications published by Google could have names of the form com.google.app.appname"); - } - - /** - * Updates the document descriptor. - *

              - * It first computes the new children of the descriptor and then updates them - * all at once. - * - * @param manifestMap The map style => attributes from the attrs_manifest.xml file - */ - public static synchronized void updateDescriptors( - Map manifestMap) { - - XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( - "android", //$NON-NLS-1$ - AndroidConstants.NS_RESOURCES); - - // -- setup the various attribute format overrides -- - // The key for each override is "element1,element2,.../attr-xml-local-name" or - // "*/attr-xml-local-name" to match the attribute in any element. - - Map overrides = new HashMap(); - - overrides.put("*/icon", new DescriptorsUtils.ITextAttributeCreator() { //$NON-NLS-1$ - public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, - String tooltip) { - return new ReferenceAttributeDescriptor( - ResourceType.DRAWABLE, - xmlName, uiName, nsUri, - tooltip); - } - }); - - overrides.put("*/theme", ThemeAttributeDescriptor.class); //$NON-NLS-1$ - overrides.put("*/permission", ListAttributeDescriptor.class); //$NON-NLS-1$ - overrides.put("*/targetPackage", PackageAttributeDescriptor.class); //$NON-NLS-1$ - - overrides.put("action,category,uses-permission/" + ANDROID_NAME_ATTR, //$NON-NLS-1$ - ListAttributeDescriptor.class); - overrides.put("application/" + ANDROID_NAME_ATTR, ApplicationAttributeDescriptor.class); //$NON-NLS-1$ - - overrideClassName(overrides, "activity", AndroidConstants.CLASS_ACTIVITY); //$NON-NLS-1$ - overrideClassName(overrides, "receiver", AndroidConstants.CLASS_BROADCASTRECEIVER); //$NON-NLS-1$ - overrideClassName(overrides, "service", AndroidConstants.CLASS_SERVICE); //$NON-NLS-1$ - overrideClassName(overrides, "provider", AndroidConstants.CLASS_CONTENTPROVIDER); //$NON-NLS-1$ - - // -- list element nodes already created -- - // These elements are referenced by already opened editors, so we want to update them - // but not re-create them when reloading an SDK on the fly. - - HashMap elementDescs = - new HashMap(); - elementDescs.put(MANIFEST_ELEMENT.getXmlLocalName(), MANIFEST_ELEMENT); - elementDescs.put(APPLICATION_ELEMENT.getXmlLocalName(), APPLICATION_ELEMENT); - elementDescs.put(INTRUMENTATION_ELEMENT.getXmlLocalName(), INTRUMENTATION_ELEMENT); - elementDescs.put(PERMISSION_ELEMENT.getXmlLocalName(), PERMISSION_ELEMENT); - elementDescs.put(USES_PERMISSION_ELEMENT.getXmlLocalName(), USES_PERMISSION_ELEMENT); - elementDescs.put(USES_SDK_ELEMENT.getXmlLocalName(), USES_SDK_ELEMENT); - elementDescs.put(PERMISSION_GROUP_ELEMENT.getXmlLocalName(), PERMISSION_GROUP_ELEMENT); - elementDescs.put(PERMISSION_TREE_ELEMENT.getXmlLocalName(), PERMISSION_TREE_ELEMENT); - - // -- - - inflateElement(manifestMap, overrides, elementDescs, - MANIFEST_ELEMENT, "AndroidManifest"); //$NON-NLS-1$ - insertAttribute(MANIFEST_ELEMENT, PACKAGE_ATTR_DESC); - - sanityCheck(manifestMap, MANIFEST_ELEMENT); - } - - /** - * Sets up an attribute override for ANDROID_NAME_ATTR using a ClassAttributeDescriptor - * with the specified class name. - */ - private static void overrideClassName(Map overrides, - String elementName, final String className) { - overrides.put(elementName + "/" + ANDROID_NAME_ATTR, - new DescriptorsUtils.ITextAttributeCreator() { - public TextAttributeDescriptor create(String xmlName, String uiName, String nsUri, - String tooltip) { - if (AndroidConstants.CLASS_ACTIVITY.equals(className)) { - return new ClassAttributeDescriptor( - className, - PostActivityCreationAction.getAction(), - xmlName, uiName + "*", //$NON-NLS-1$ - nsUri, - tooltip, - true /*mandatory */); - } else if (AndroidConstants.CLASS_BROADCASTRECEIVER.equals(className)) { - return new ClassAttributeDescriptor( - className, - PostReceiverCreationAction.getAction(), - xmlName, uiName + "*", //$NON-NLS-1$ - nsUri, - tooltip, - true /*mandatory */); - - } else { - return new ClassAttributeDescriptor( - className, - xmlName, uiName + "*", //$NON-NLS-1$ - nsUri, - tooltip, - true /*mandatory */); - } - } - }); - } - - /** - * Returns a new ElementDescriptor constructed from the information given here - * and the javadoc & attributes extracted from the style map if any. - *

              - * Creates an element with no attribute overrides. - */ - private static ElementDescriptor createElement( - String xmlName, - ElementDescriptor[] childrenElements, - boolean mandatory) { - // Creates an element with no attribute overrides. - String styleName = guessStyleName(xmlName); - String sdkUrl = DescriptorsUtils.MANIFEST_SDK_URL + styleName; - String uiName = getUiName(xmlName); - - ElementDescriptor element = new ManifestElementDescriptor(xmlName, uiName, null, sdkUrl, - null, childrenElements, mandatory); - - return element; - } - - /** - * Returns a new ElementDescriptor constructed from its XML local name. - *

              - * This version creates an element not mandatory. - */ - private static ElementDescriptor createElement(String xmlName) { - // Creates an element with no child and not mandatory - return createElement(xmlName, null, false); - } - - /** - * Inserts an attribute in this element attribute list if it is not present there yet - * (based on the attribute XML name.) - * The attribute is inserted at the beginning of the attribute list. - */ - private static void insertAttribute(ElementDescriptor element, AttributeDescriptor newAttr) { - AttributeDescriptor[] attributes = element.getAttributes(); - for (AttributeDescriptor attr : attributes) { - if (attr.getXmlLocalName().equals(newAttr.getXmlLocalName())) { - return; - } - } - - AttributeDescriptor[] newArray = new AttributeDescriptor[attributes.length + 1]; - newArray[0] = newAttr; - System.arraycopy(attributes, 0, newArray, 1, attributes.length); - element.setAttributes(newArray); - } - - /** - * "Inflates" the properties of an {@link ElementDescriptor} from the styleable declaration. - *

              - * This first creates all the attributes for the given ElementDescriptor. - * It then find all children of the descriptor, inflate them recursively and set them - * as child to this ElementDescriptor. - * - * @param styleMap The input styleable map for manifest elements & attributes - * @param overrides A list of attribute overrides (to customize the type of the attribute - * descriptors) - * @param existingElementDescs A map of already created element descriptors, keyed by - * XML local name. This is used to use the static elements created initially by this - * class, which are referenced directly by editors (so that reloading an SDK won't - * break these references) - * @param elemDesc The current {@link ElementDescriptor} to inflate. - * @param styleName The name of the {@link ElementDescriptor} to inflate. Its XML local name - * will be guessed automatically from the style name. - */ - private static void inflateElement( - Map styleMap, - Map overrides, - HashMap existingElementDescs, - ElementDescriptor elemDesc, - String styleName) { - assert elemDesc != null; - assert styleName != null; - - // define attributes - DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; - if (style != null) { - ArrayList attrDescs = new ArrayList(); - DescriptorsUtils.appendAttributes(attrDescs, - elemDesc.getXmlLocalName(), - AndroidConstants.NS_RESOURCES, - style.getAttributes(), null, overrides); - elemDesc.setTooltip(style.getJavaDoc()); - elemDesc.setAttributes(attrDescs.toArray(new AttributeDescriptor[attrDescs.size()])); - } - - // find all elements that have this one as parent - ArrayList children = new ArrayList(); - for (Entry entry : styleMap.entrySet()) { - DeclareStyleableInfo childStyle = entry.getValue(); - boolean isParent = false; - String[] parents = childStyle.getParents(); - if (parents != null) { - for (String parent: parents) { - if (styleName.equals(parent)) { - isParent = true; - break; - } - } - } - if (isParent) { - String childStyleName = entry.getKey(); - String childXmlName = guessXmlName(childStyleName); - - // create or re-use element - ElementDescriptor child = existingElementDescs.get(childXmlName); - if (child == null) { - child = createElement(childXmlName); - existingElementDescs.put(childXmlName, child); - } - children.add(child); - - inflateElement(styleMap, overrides, existingElementDescs, child, childStyleName); - } - } - elemDesc.setChildren(children.toArray(new ElementDescriptor[children.size()])); - } - - /** - * Get an UI name from the element XML name. - *

              - * Capitalizes the first letter and replace non-alphabet by a space followed by a capital. - */ - private static String getUiName(String xmlName) { - StringBuilder sb = new StringBuilder(); - - boolean capitalize = true; - for (char c : xmlName.toCharArray()) { - if (capitalize && c >= 'a' && c <= 'z') { - sb.append((char)(c + 'A' - 'a')); - capitalize = false; - } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { - sb.append(' '); - capitalize = true; - } else { - sb.append(c); - } - } - - return sb.toString(); - } - - /** - * Guesses the style name for a given XML element name. - *

              - * The rules are: - * - capitalize the first letter: - * - if there's a dash, skip it and capitalize the next one - * - prefix AndroidManifest - * The exception is "manifest" which just becomes AndroidManifest. - *

              - * Examples: - * - manifest => AndroidManifest - * - application => AndroidManifestApplication - * - uses-permission => AndroidManifestUsesPermission - */ - private static String guessStyleName(String xmlName) { - StringBuilder sb = new StringBuilder(); - - if (!xmlName.equals(MANIFEST_NODE_NAME)) { - boolean capitalize = true; - for (char c : xmlName.toCharArray()) { - if (capitalize && c >= 'a' && c <= 'z') { - sb.append((char)(c + 'A' - 'a')); - capitalize = false; - } else if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { - // not a letter -- skip the character and capitalize the next one - capitalize = true; - } else { - sb.append(c); - } - } - } - - sb.insert(0, ANDROID_MANIFEST_STYLEABLE); - return sb.toString(); - } - - /** - * This method performs a sanity check to make sure all the styles declared in the - * manifestMap are actually defined in the actual element descriptors and reachable from - * the manifestElement root node. - */ - private static void sanityCheck(Map manifestMap, - ElementDescriptor manifestElement) { - TreeSet elementsDeclared = new TreeSet(); - findAllElementNames(manifestElement, elementsDeclared); - - TreeSet stylesDeclared = new TreeSet(); - for (String styleName : manifestMap.keySet()) { - if (styleName.startsWith(ANDROID_MANIFEST_STYLEABLE)) { - stylesDeclared.add(styleName); - } - } - - for (Iterator it = elementsDeclared.iterator(); it.hasNext();) { - String xmlName = it.next(); - String styleName = guessStyleName(xmlName); - if (stylesDeclared.remove(styleName)) { - it.remove(); - } - } - - StringBuilder sb = new StringBuilder(); - if (!stylesDeclared.isEmpty()) { - sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by the SDK but unknown to ADT: "); - for (String name : stylesDeclared) { - name = guessXmlName(name); - - if (name != stylesDeclared.last()) { - sb.append(", "); //$NON-NLS-1$ - } - } - - EditorsPlugin.log(IStatus.WARNING, "%s", sb.toString()); - EditorsPlugin.printToConsole(null, sb); - sb.setLength(0); - } - - if (!elementsDeclared.isEmpty()) { - sb.append("Warning, ADT/SDK Mismatch! The following elements are declared by ADT but not by the SDK: "); - for (String name : elementsDeclared) { - sb.append(name); - if (name != elementsDeclared.last()) { - sb.append(", "); //$NON-NLS-1$ - } - } - - EditorsPlugin.log(IStatus.WARNING, "%s", sb.toString()); - EditorsPlugin.printToConsole(null, sb); - } - } - - /** - * Performs an approximate translation of the style name into a potential - * xml name. This is more or less the reverse from guessStyleName(). - * - * @return The XML local name for a given style name. - */ - private static String guessXmlName(String name) { - StringBuilder sb = new StringBuilder(); - if (ANDROID_MANIFEST_STYLEABLE.equals(name)) { - sb.append(MANIFEST_NODE_NAME); - } else { - name = name.replace(ANDROID_MANIFEST_STYLEABLE, ""); //$NON-NLS-1$ - boolean first_char = true; - for (char c : name.toCharArray()) { - if (c >= 'A' && c <= 'Z') { - if (!first_char) { - sb.append('-'); - } - c = (char) (c - 'A' + 'a'); - } - sb.append(c); - first_char = false; - } - } - return sb.toString(); - } - - /** - * Helper method used by {@link #sanityCheck(Map, ElementDescriptor)} to find all the - * {@link ElementDescriptor} names defined by the tree of descriptors. - *

              - * Note: this assumes no circular reference in the tree of {@link ElementDescriptor}s. - */ - private static void findAllElementNames(ElementDescriptor element, TreeSet declared) { - declared.add(element.getXmlName()); - for (ElementDescriptor desc : element.getChildren()) { - findAllElementNames(desc, declared); - } - } - - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java deleted file mode 100644 index eab7f09..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ApplicationAttributeDescriptor.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Describes an 'Application' class XML attribute. It is displayed by a - * {@link UiClassAttributeNode}, that restricts creation and selection to classes - * inheriting from android.app.Application. - */ -public class ApplicationAttributeDescriptor extends TextAttributeDescriptor { - - public ApplicationAttributeDescriptor(String xmlLocalName, String uiName, - String nsUri, String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * @return A new {@link UiClassAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiClassAttributeNode("android.app.Application", //$NON-NLS-1$ - null /* postCreationAction */, false /* mandatory */, this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java deleted file mode 100644 index 1144006..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ClassAttributeDescriptor.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Describes an XML attribute representing a class name. - * It is displayed by a {@link UiClassAttributeNode}. - */ -public class ClassAttributeDescriptor extends TextAttributeDescriptor { - - /** Superclass of the class value. */ - private String mSuperClassName; - - private IPostTypeCreationAction mPostCreationAction; - - /** indicates if the class parameter is mandatory */ - boolean mMandatory; - - /** - * Creates a new {@link ClassAttributeDescriptor} - * @param superClassName the fully qualified name of the superclass of the class represented - * by the attribute. - * @param xmlLocalName The XML name of the attribute (case sensitive, with android: prefix). - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip A non-empty tooltip string or null. - * @param mandatory indicates if the class attribute is mandatory. - */ - public ClassAttributeDescriptor(String superClassName, - String xmlLocalName, String uiName, String nsUri, - String tooltip, boolean mandatory) { - super(xmlLocalName, uiName, nsUri, tooltip); - mSuperClassName = superClassName; - } - - /** - * Creates a new {@link ClassAttributeDescriptor} - * @param superClassName the fully qualified name of the superclass of the class represented - * by the attribute. - * @param postCreationAction the {@link IPostTypeCreationAction} to be executed on the - * newly created class. - * @param xmlLocalName The XML local name of the attribute (case sensitive). - * @param uiName The UI name of the attribute. Cannot be an empty string and cannot be null. - * @param nsUri The URI of the attribute. Can be null if attribute has no namespace. - * See {@link AndroidConstants#NS_RESOURCES} for a common value. - * @param tooltip A non-empty tooltip string or null. - * @param mandatory indicates if the class attribute is mandatory. - */ - public ClassAttributeDescriptor(String superClassName, - IPostTypeCreationAction postCreationAction, - String xmlLocalName, String uiName, String nsUri, - String tooltip, boolean mandatory) { - super(xmlLocalName, uiName, nsUri, tooltip); - mSuperClassName = superClassName; - mPostCreationAction = postCreationAction; - } - - /** - * @return A new {@link UiClassAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiClassAttributeNode(mSuperClassName, mPostCreationAction, - mMandatory, this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java deleted file mode 100644 index 6e589f7..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/InstrumentationAttributeDescriptor.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Describes a 'Instrumentation' class XML attribute. It is displayed by a - * {@link UiClassAttributeNode}, that restricts creation and selection to classes inheriting from - * android.app.Instrumentation. - */ -public class InstrumentationAttributeDescriptor extends TextAttributeDescriptor { - - public InstrumentationAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * @return A new {@link UiClassAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiClassAttributeNode(AndroidConstants.CLASS_INSTRUMENTATION, - null /* postCreationAction */, true /* mandatory */, this, uiParent); - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java deleted file mode 100644 index d89292b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ManifestElementDescriptor.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.model.UiManifestElementNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * {@link ManifestElementDescriptor} describes an XML element node, with its - * element name, its possible attributes, its possible child elements but also - * its display name and tooltip. - * - * This {@link ElementDescriptor} is specialized to create {@link UiManifestElementNode} UI nodes. - */ -public class ManifestElementDescriptor extends ElementDescriptor { - - /** - * Constructs a new {@link ManifestElementDescriptor}. - * - * @param xml_name The XML element node name. Case sensitive. - * @param ui_name The XML element name for the user interface, typically capitalized. - * @param tooltip An optional tooltip. Can be null or empty. - * @param sdk_url An optional SKD URL. Can be null or empty. - * @param attributes The list of allowed attributes. Can be null or empty. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). - */ - public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, - AttributeDescriptor[] attributes, - ElementDescriptor[] children, - boolean mandatory) { - super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); - } - - /** - * Constructs a new {@link ManifestElementDescriptor}. - * - * @param xml_name The XML element node name. Case sensitive. - * @param ui_name The XML element name for the user interface, typically capitalized. - * @param tooltip An optional tooltip. Can be null or empty. - * @param sdk_url An optional SKD URL. Can be null or empty. - * @param attributes The list of allowed attributes. Can be null or empty. - * @param children The list of allowed children. Can be null or empty. - */ - public ManifestElementDescriptor(String xml_name, String ui_name, String tooltip, String sdk_url, - AttributeDescriptor[] attributes, - ElementDescriptor[] children) { - super(xml_name, ui_name, tooltip, sdk_url, attributes, children, false); - } - - /** - * This is a shortcut for - * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, children). - * This constructor is mostly used for unit tests. - * - * @param xml_name The XML element node name. Case sensitive. - */ - public ManifestElementDescriptor(String xml_name, ElementDescriptor[] children) { - super(xml_name, children); - } - - /** - * This is a shortcut for - * ManifestElementDescriptor(xml_name, xml_name.capitalize(), null, null, null, null). - * This constructor is mostly used for unit tests. - * - * @param xml_name The XML element node name. Case sensitive. - */ - public ManifestElementDescriptor(String xml_name) { - super(xml_name, null); - } - - /** - * @return A new {@link UiElementNode} linked to this descriptor. - */ - @Override - public UiElementNode createUiNode() { - return new UiManifestElementNode(this); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java deleted file mode 100644 index 34c5d0d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PackageAttributeDescriptor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.model.UiPackageAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * Describes a package XML attribute. It is displayed by a {@link UiPackageAttributeNode}. - */ -public class PackageAttributeDescriptor extends TextAttributeDescriptor { - - public PackageAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * @return A new {@link UiPackageAttributeNode} linked to this descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiPackageAttributeNode(this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java deleted file mode 100644 index 3442c24..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostActivityCreationAction.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; - -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; - -/** - * Action to be executed after an Activity class is created. - */ -class PostActivityCreationAction implements IPostTypeCreationAction { - - private final static PostActivityCreationAction sAction = new PostActivityCreationAction(); - - private PostActivityCreationAction() { - // private constructor to enforce singleton. - } - - - /** - * Returns the action. - */ - public static IPostTypeCreationAction getAction() { - return sAction; - } - - /** - * Processes a newly created Activity. - * - */ - public void processNewType(IType newType) { - try { - String methodContent = - " /** Called when the activity is first created. */\n" + - " @Override\n" + - " public void onCreate(Bundle savedInstanceState) {\n" + - " super.onCreate(savedInstanceState);\n" + - "\n" + - " // TODO Auto-generated method stub\n" + - " }"; - newType.createMethod(methodContent, null /* sibling*/, false /* force */, - new NullProgressMonitor()); - - // we need to add the import for Bundle, so we need the compilation unit. - // Since the type could be enclosed in other types, we loop till we find it. - ICompilationUnit compilationUnit = null; - IJavaElement element = newType; - do { - IJavaElement parentElement = element.getParent(); - if (parentElement != null) { - if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) { - compilationUnit = (ICompilationUnit)parentElement; - } - - element = parentElement; - } else { - break; - } - } while (compilationUnit == null); - - if (compilationUnit != null) { - compilationUnit.createImport(AndroidConstants.CLASS_BUNDLE, - null /* sibling */, new NullProgressMonitor()); - } - } catch (JavaModelException e) { - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java deleted file mode 100644 index 5a8137d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/PostReceiverCreationAction.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.manifest.model.UiClassAttributeNode.IPostTypeCreationAction; - -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jdt.core.ICompilationUnit; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IType; -import org.eclipse.jdt.core.JavaModelException; - -/** - * Action to be executed after an BroadcastReceiver class is created. - */ -class PostReceiverCreationAction implements IPostTypeCreationAction { - - private final static PostReceiverCreationAction sAction = new PostReceiverCreationAction(); - - private PostReceiverCreationAction() { - // private constructor to enforce singleton. - } - - /** - * Returns the action. - */ - public static IPostTypeCreationAction getAction() { - return sAction; - } - - /** - * Processes a newly created Activity. - * - */ - public void processNewType(IType newType) { - try { - String methodContent = - " @Override\n" + - " public void onReceive(Context context, Intent intent) {\n" + - " // TODO Auto-generated method stub\n" + - " }"; - newType.createMethod(methodContent, null /* sibling*/, false /* force */, - new NullProgressMonitor()); - - // we need to add the import for Bundle, so we need the compilation unit. - // Since the type could be enclosed in other types, we loop till we find it. - ICompilationUnit compilationUnit = null; - IJavaElement element = newType; - do { - IJavaElement parentElement = element.getParent(); - if (parentElement != null) { - if (parentElement.getElementType() == IJavaElement.COMPILATION_UNIT) { - compilationUnit = (ICompilationUnit)parentElement; - } - - element = parentElement; - } else { - break; - } - } while (compilationUnit == null); - - if (compilationUnit != null) { - compilationUnit.createImport(AndroidConstants.CLASS_CONTEXT, - null /* sibling */, new NullProgressMonitor()); - compilationUnit.createImport(AndroidConstants.CLASS_INTENT, - null /* sibling */, new NullProgressMonitor()); - } - } catch (JavaModelException e) { - // looks like the class already existed (this happens when the user check to create - // inherited abstract methods). - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java deleted file mode 100644 index 4219007..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/descriptors/ThemeAttributeDescriptor.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.descriptors; - -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; - -/** - * Describes a Theme/Style XML attribute displayed by a {@link UiResourceAttributeNode} - */ -public final class ThemeAttributeDescriptor extends TextAttributeDescriptor { - - public ThemeAttributeDescriptor(String xmlLocalName, String uiName, String nsUri, - String tooltip) { - super(xmlLocalName, uiName, nsUri, tooltip); - } - - /** - * @return A new {@link UiResourceAttributeNode} linked to this theme descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiResourceAttributeNode(ResourceType.STYLE, this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java deleted file mode 100644 index f8aac1d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java +++ /dev/null @@ -1,624 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.model; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; -import com.android.ide.eclipse.common.project.BaseProjectHelper; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.SectionHelper; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; -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.core.search.IJavaSearchScope; -import org.eclipse.jdt.core.search.SearchEngine; -import org.eclipse.jdt.ui.IJavaElementSearchConstants; -import org.eclipse.jdt.ui.JavaUI; -import org.eclipse.jdt.ui.actions.OpenNewClassWizardAction; -import org.eclipse.jdt.ui.dialogs.ITypeInfoFilterExtension; -import org.eclipse.jdt.ui.dialogs.ITypeInfoRequestor; -import org.eclipse.jdt.ui.dialogs.ITypeSelectionComponent; -import org.eclipse.jdt.ui.dialogs.TypeSelectionExtension; -import org.eclipse.jdt.ui.wizards.NewClassWizardPage; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.dialogs.SelectionDialog; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.events.HyperlinkAdapter; -import org.eclipse.ui.forms.events.HyperlinkEvent; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; -import org.w3c.dom.Element; - -import java.util.ArrayList; - -/** - * Represents an XML attribute for a class, that can be modified using a simple text field or - * a dialog to choose an existing class. Also, there's a link to create a new class. - *

              - * See {@link UiTextAttributeNode} for more information. - */ -public class UiClassAttributeNode extends UiTextAttributeNode { - - private String mReferenceClass; - private IPostTypeCreationAction mPostCreationAction; - private boolean mMandatory; - - private class HierarchyTypeSelection extends TypeSelectionExtension { - - private IJavaProject mJavaProject; - - public HierarchyTypeSelection(IProject project, String referenceClass) { - mJavaProject = JavaCore.create(project); - mReferenceClass = referenceClass; - } - - @Override - public ITypeInfoFilterExtension getFilterExtension() { - return new ITypeInfoFilterExtension() { - public boolean select(ITypeInfoRequestor typeInfoRequestor) { - String packageName = typeInfoRequestor.getPackageName(); - String typeName = typeInfoRequestor.getTypeName(); - String enclosingType = typeInfoRequestor.getEnclosingName(); - - // build the full class name. - StringBuilder sb = new StringBuilder(packageName); - sb.append('.'); - if (enclosingType.length() > 0) { - sb.append(enclosingType); - sb.append('.'); - } - sb.append(typeName); - - String className = sb.toString(); - - try { - IType type = mJavaProject.findType(className); - if (type != null) { - // get the type hierarchy - ITypeHierarchy hierarchy = type.newSupertypeHierarchy( - new NullProgressMonitor()); - - // if the super class is not the reference class, it may inherit from - // it so we get its supertype. At some point it will be null and we - // will return false; - IType superType = type; - while ((superType = hierarchy.getSuperclass(superType)) != null) { - if (mReferenceClass.equals(superType.getFullyQualifiedName())) { - return true; - } - } - } - } catch (JavaModelException e) { - } - - return false; - } - }; - } - } - - /** - * Classes which implement this interface provide a method processing newly created classes. - */ - public static interface IPostTypeCreationAction { - /** - * Sent to process a newly created class. - * @param newType the IType representing the newly created class. - */ - public void processNewType(IType newType); - } - - /** - * Creates a {@link UiClassAttributeNode} object that will display ui to select or create - * classes. - * @param referenceClass The allowed supertype of the classes that are to be selected - * or created. Can be null. - * @param postCreationAction a {@link IPostTypeCreationAction} object handling post creation - * modification of the class. - * @param mandatory indicates if the class value is mandatory - * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. - */ - public UiClassAttributeNode(String referenceClass, IPostTypeCreationAction postCreationAction, - boolean mandatory, AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - - mReferenceClass = referenceClass; - mPostCreationAction = postCreationAction; - mMandatory = mandatory; - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public void createUiControl(final Composite parent, IManagedForm managedForm) { - setManagedForm(managedForm); - FormToolkit toolkit = managedForm.getToolkit(); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - - StringBuilder label = new StringBuilder(); - label.append("

              "); - label.append(desc.getUiName()); - label.append("

              "); - FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */, - label.toString(), true /* setupLayoutData */); - formText.addHyperlinkListener(new HyperlinkAdapter() { - @Override - public void linkActivated(HyperlinkEvent e) { - super.linkActivated(e); - handleLabelClick(); - } - }); - formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - SectionHelper.addControlTooltip(formText, desc.getTooltip()); - - Composite composite = toolkit.createComposite(parent); - composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); - GridLayout gl = new GridLayout(2, false); - gl.marginHeight = gl.marginWidth = 0; - composite.setLayout(gl); - // Fixes missing text borders under GTK... also requires adding a 1-pixel margin - // for the text field below - toolkit.paintBordersFor(composite); - - final Text text = toolkit.createText(composite, getCurrentValue()); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK - text.setLayoutData(gd); - Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); - - setTextWidget(text); - - browseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - handleBrowseClick(); - } - }); - } - - /* (non-java doc) - * - * Add a modify listener that will check the validity of the class - */ - @Override - protected void onAddValidators(final Text text) { - ModifyListener listener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - try { - String textValue = text.getText().trim(); - if (textValue.length() == 0) { - if (mMandatory) { - setErrorMessage("Value is mandatory", text); - } else { - setErrorMessage(null, text); - } - return; - } - // first we need the current java package. - String javaPackage = getManifestPackage(); - - // build the fully qualified name of the class - String className = AndroidManifestHelper.combinePackageAndClassName(javaPackage, - textValue); - - // only test the vilibility for activities. - boolean testVisibility = AndroidConstants.CLASS_ACTIVITY.equals( - mReferenceClass); - - // test the class - setErrorMessage(BaseProjectHelper.testClassForManifest( - BaseProjectHelper.getJavaProject(getProject()), className, - mReferenceClass, testVisibility), text); - } catch (CoreException ce) { - setErrorMessage(ce.getMessage(), text); - } - } - }; - - text.addModifyListener(listener); - - // Make sure the validator removes its message(s) when the widget is disposed - text.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - // we don't want to use setErrorMessage, because we don't want to reset - // the error flag in the UiAttributeNode - getManagedForm().getMessageManager().removeMessage(text, text); - } - }); - - // Finally call the validator once to make sure the initial value is processed - listener.modifyText(null); - } - - private void handleBrowseClick() { - Text text = getTextWidget(); - - // we need to get the project of the manifest. - IProject project = getProject(); - if (project != null) { - - // Create a search scope including only the source folder of the current - // project. - IJavaSearchScope scope = SearchEngine.createJavaSearchScope( - getPackageFragmentRoots(project), false); - - try { - SelectionDialog dlg = JavaUI.createTypeDialog(text.getShell(), - PlatformUI.getWorkbench().getProgressService(), - scope, - IJavaElementSearchConstants.CONSIDER_CLASSES, // style - false, // no multiple selection - "**", //$NON-NLS-1$ //filter - new HierarchyTypeSelection(project, mReferenceClass)); - dlg.setMessage(String.format("Select class name for element %1$s:", - getUiParent().getBreadcrumbTrailDescription(false /* include_root */))); - if (dlg instanceof ITypeSelectionComponent) { - ((ITypeSelectionComponent)dlg).triggerSearch(); - } - - if (dlg.open() == Window.OK) { - Object[] results = dlg.getResult(); - if (results.length == 1) { - handleNewType((IType)results[0]); - } - } - } catch (JavaModelException e1) { - } - } - } - - private void handleLabelClick() { - // get the current value - String className = getTextWidget().getText().trim(); - - // get the package name from the manifest. - String packageName = getManifestPackage(); - - if (className.length() == 0) { - createNewClass(packageName, null /* className */); - } else { - // build back the fully qualified class name. - String fullClassName = className; - if (className.startsWith(".")) { //$NON-NLS-1$ - fullClassName = packageName + className; - } else { - String[] segments = className.split(AndroidConstants.RE_DOT); - if (segments.length == 1) { - fullClassName = packageName + "." + className; //$NON-NLS-1$ - } - } - - // in case the type is enclosed, we need to replace the $ with . - fullClassName = fullClassName.replaceAll("\\$", "\\."); //$NON-NLS-1$ //$NON-NLS2$ - - // now we try to find the file that contains this class and we open it in the editor. - IProject project = getProject(); - IJavaProject javaProject = JavaCore.create(project); - - try { - IType result = javaProject.findType(fullClassName); - if (result != null) { - JavaUI.openInEditor(result); - } else { - // split the last segment from the fullClassname - int index = fullClassName.lastIndexOf('.'); - if (index != -1) { - createNewClass(fullClassName.substring(0, index), - fullClassName.substring(index+1)); - } else { - createNewClass(packageName, className); - } - } - } catch (JavaModelException e) { - } catch (PartInitException e) { - } - } - } - - private IProject getProject() { - UiElementNode uiNode = getUiParent(); - AndroidEditor editor = uiNode.getEditor(); - IEditorInput input = editor.getEditorInput(); - if (input instanceof IFileEditorInput) { - // from the file editor we can get the IFile object, and from it, the IProject. - IFile file = ((IFileEditorInput)input).getFile(); - return file.getProject(); - } - - return null; - } - - - /** - * Returns the current value of the /manifest/package attribute. - * @return the package or an empty string if not found - */ - private String getManifestPackage() { - // get the root uiNode to get the 'package' attribute value. - UiElementNode rootNode = getUiParent().getUiRoot(); - - Element xmlElement = (Element) rootNode.getXmlNode(); - - if (xmlElement != null) { - return xmlElement.getAttribute(AndroidManifestDescriptors.PACKAGE_ATTR); - } - return ""; //$NON-NLS-1$ - } - - - /** - * Computes and return the {@link IPackageFragmentRoot}s corresponding to the source folders of - * the specified project. - * @param project the project - * @return an array of IPackageFragmentRoot. - */ - private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) { - ArrayList result = new ArrayList(); - try { - IJavaProject javaProject = JavaCore.create(project); - IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); - for (int i = 0; i < roots.length; i++) { - IClasspathEntry entry = roots[i].getRawClasspathEntry(); - if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - result.add(roots[i]); - } - } - } catch (JavaModelException e) { - } - - return result.toArray(new IPackageFragmentRoot[result.size()]); - } - - private void handleNewType(IType type) { - Text text = getTextWidget(); - - // get the fully qualified name with $ to properly detect the enclosing types. - String name = type.getFullyQualifiedName('$'); - - String packageValue = getManifestPackage(); - - // check if the class doesn't start with the package. - if (packageValue.length() > 0 && name.startsWith(packageValue)) { - // if it does, we remove the package and the first dot. - name = name.substring(packageValue.length() + 1); - - // look for how many segments we have left. - // if one, just write it that way. - // if more than one, write it with a leading dot. - String[] packages = name.split(AndroidConstants.RE_DOT); - if (packages.length == 1) { - text.setText(name); - } else { - text.setText("." + name); //$NON-NLS-1$ - } - } else { - text.setText(name); - } - } - - private void createNewClass(String packageName, String className) { - // create the wizard page for the class creation, and configure it - NewClassWizardPage page = new NewClassWizardPage(); - - // set the parent class - page.setSuperClass(mReferenceClass, true /* canBeModified */); - - // get the source folders as java elements. - IPackageFragmentRoot[] roots = getPackageFragmentRoots(getProject()); - - IPackageFragmentRoot currentRoot = null; - IPackageFragment currentFragment = null; - int packageMatchCount = -1; - - for (IPackageFragmentRoot root : roots) { - // Get the java element for the package. - // This method is said to always return a IPackageFragment even if the - // underlying folder doesn't exist... - IPackageFragment fragment = root.getPackageFragment(packageName); - if (fragment != null && fragment.exists()) { - // we have a perfect match! we use it. - currentRoot = root; - currentFragment = fragment; - packageMatchCount = -1; - break; - } else { - // we don't have a match. we look for the fragment with the best match - // (ie the closest parent package we can find) - try { - IJavaElement[] children; - children = root.getChildren(); - for (IJavaElement child : children) { - if (child instanceof IPackageFragment) { - fragment = (IPackageFragment)child; - if (packageName.startsWith(fragment.getElementName())) { - // its a match. get the number of segments - String[] segments = fragment.getElementName().split("\\."); //$NON-NLS-1$ - if (segments.length > packageMatchCount) { - packageMatchCount = segments.length; - currentFragment = fragment; - currentRoot = root; - } - } - } - } - } catch (JavaModelException e) { - // Couldn't get the children: we just ignore this package root. - } - } - } - - ArrayList createdFragments = null; - - if (currentRoot != null) { - // if we have a perfect match, we set it and we're done. - if (packageMatchCount == -1) { - page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); - page.setPackageFragment(currentFragment, true /* canBeModified */); - } else { - // we have a partial match. - // create the package. We have to start with the first segment so that we - // know what to delete in case of a cancel. - try { - createdFragments = new ArrayList(); - - int totalCount = packageName.split("\\.").length; //$NON-NLS-1$ - int count = 0; - int index = -1; - // skip the matching packages - while (count < packageMatchCount) { - index = packageName.indexOf('.', index+1); - count++; - } - - // create the rest of the segments, except for the last one as indexOf will - // return -1; - while (count < totalCount - 1) { - index = packageName.indexOf('.', index+1); - count++; - createdFragments.add(currentRoot.createPackageFragment( - packageName.substring(0, index), - true /* force*/, new NullProgressMonitor())); - } - - // create the last package - createdFragments.add(currentRoot.createPackageFragment( - packageName, true /* force*/, new NullProgressMonitor())); - - // set the root and fragment in the Wizard page - page.setPackageFragmentRoot(currentRoot, true /* canBeModified*/); - page.setPackageFragment(createdFragments.get(createdFragments.size()-1), - true /* canBeModified */); - } catch (JavaModelException e) { - // if we can't create the packages, there's a problem. we revert to the default - // package - for (IPackageFragmentRoot root : roots) { - // Get the java element for the package. - // This method is said to always return a IPackageFragment even if the - // underlying folder doesn't exist... - IPackageFragment fragment = root.getPackageFragment(packageName); - if (fragment != null && fragment.exists()) { - page.setPackageFragmentRoot(root, true /* canBeModified*/); - page.setPackageFragment(fragment, true /* canBeModified */); - break; - } - } - } - } - } else if (roots.length > 0) { - // if we haven't found a valid fragment, we set the root to the first source folder. - page.setPackageFragmentRoot(roots[0], true /* canBeModified*/); - } - - // if we have a starting class name we use it - if (className != null) { - page.setTypeName(className, true /* canBeModified*/); - } - - // create the action that will open it the wizard. - OpenNewClassWizardAction action = new OpenNewClassWizardAction(); - action.setConfiguredWizardPage(page); - action.run(); - IJavaElement element = action.getCreatedElement(); - - if (element != null) { - if (element.getElementType() == IJavaElement.TYPE) { - - IType type = (IType)element; - - if (mPostCreationAction != null) { - mPostCreationAction.processNewType(type); - } - - handleNewType(type); - } - } else { - // lets delete the packages we created just for this. - // we need to start with the leaf and go up - if (createdFragments != null) { - try { - for (int i = createdFragments.size() - 1 ; i >= 0 ; i--) { - createdFragments.get(i).delete(true /* force*/, new NullProgressMonitor()); - } - } catch (JavaModelException e) { - e.printStackTrace(); - } - } - } - } - - /** - * Sets the error messages. If message is null, the message is removed. - * @param message the message to set, or null to remove the current message - * @param textWidget the {@link Text} widget associated to the message. - */ - private final void setErrorMessage(String message, Text textWidget) { - if (message != null) { - setHasError(true); - getManagedForm().getMessageManager().addMessage(textWidget, message, null /* data */, - IMessageProvider.ERROR, textWidget); - } else { - setHasError(false); - getManagedForm().getMessageManager().removeMessage(textWidget, textWidget); - } - } - - @Override - public String[] getPossibleValues() { - // TODO: compute a list of existing classes for content assist completion - return null; - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java deleted file mode 100644 index 10ce50d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiManifestElementNode.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.model; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.manifest.descriptors.ManifestElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.w3c.dom.Element; - -/** - * Represents an XML node that can be modified by the user interface in the XML editor. - *

              - * Each tree viewer used in the application page's parts needs to keep a model representing - * each underlying node in the tree. This interface represents the base type for such a node. - *

              - * Each node acts as an intermediary model between the actual XML model (the real data support) - * and the tree viewers or the corresponding page parts. - *

              - * Element nodes don't contain data per se. Their data is contained in their attributes - * as well as their children's attributes, see {@link UiAttributeNode}. - *

              - * The structure of a given {@link UiElementNode} is declared by a corresponding - * {@link ElementDescriptor}. - */ -public final class UiManifestElementNode extends UiElementNode { - - /** - * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}. - * - * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null. - */ - public UiManifestElementNode(ManifestElementDescriptor elementDescriptor) { - super(elementDescriptor); - } - - /** - * Computes a short string describing the UI node suitable for tree views. - * Uses the element's attribute "android:name" if present, or the "android:label" one - * followed by the element's name. - * - * @return A short string describing the UI node suitable for tree views. - */ - @Override - public String getShortDescription() { - if (getXmlNode() != null && - getXmlNode() instanceof Element && - getXmlNode().hasAttributes()) { - - // Application and Manifest nodes have a special treatment: they are unique nodes - // so we don't bother trying to differentiate their strings and we fall back to - // just using the UI name below. - ElementDescriptor desc = getDescriptor(); - if (desc != AndroidManifestDescriptors.MANIFEST_ELEMENT && - desc != AndroidManifestDescriptors.APPLICATION_ELEMENT) { - Element elem = (Element) getXmlNode(); - String attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, - AndroidManifestDescriptors.ANDROID_NAME_ATTR); - if (attr == null || attr.length() == 0) { - attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, - AndroidManifestDescriptors.ANDROID_LABEL_ATTR); - } - if (attr != null && attr.length() > 0) { - return String.format("%1$s (%2$s)", attr, getDescriptor().getUiName()); - } - } - } - - return String.format("%1$s", getDescriptor().getUiName()); - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java deleted file mode 100644 index 5e02273..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.model; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiTextAttributeNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.IPackageFragment; -import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jdt.ui.JavaUI; -import org.eclipse.jdt.ui.actions.OpenNewPackageWizardAction; -import org.eclipse.jdt.ui.actions.ShowInPackageViewAction; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.IWorkbenchPartSite; -import org.eclipse.ui.dialogs.SelectionDialog; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.events.HyperlinkAdapter; -import org.eclipse.ui.forms.events.HyperlinkEvent; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; - -import java.util.ArrayList; - -/** - * Represents an XML attribute for a package, that can be modified using a simple text field or - * a dialog to choose an existing package. Also, there's a link to create a new package. - *

              - * See {@link UiTextAttributeNode} for more information. - */ -public class UiPackageAttributeNode extends UiTextAttributeNode { - - /** - * Creates a {@link UiPackageAttributeNode} object that will display ui to select or create - * a package. - * @param attributeDescriptor the {@link AttributeDescriptor} object linked to the Ui Node. - */ - public UiPackageAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public void createUiControl(final Composite parent, final IManagedForm managedForm) { - setManagedForm(managedForm); - FormToolkit toolkit = managedForm.getToolkit(); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - - StringBuilder label = new StringBuilder(); - label.append("

              "); //$NON-NLS-1$ - label.append(desc.getUiName()); - label.append("

              "); //$NON-NLS-1$ - FormText formText = SectionHelper.createFormText(parent, toolkit, true /* isHtml */, - label.toString(), true /* setupLayoutData */); - formText.addHyperlinkListener(new HyperlinkAdapter() { - @Override - public void linkActivated(HyperlinkEvent e) { - super.linkActivated(e); - doLabelClick(); - } - }); - formText.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - SectionHelper.addControlTooltip(formText, desc.getTooltip()); - - Composite composite = toolkit.createComposite(parent); - composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); - GridLayout gl = new GridLayout(2, false); - gl.marginHeight = gl.marginWidth = 0; - composite.setLayout(gl); - // Fixes missing text borders under GTK... also requires adding a 1-pixel margin - // for the text field below - toolkit.paintBordersFor(composite); - - final Text text = toolkit.createText(composite, getCurrentValue()); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK - text.setLayoutData(gd); - - setTextWidget(text); - - Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); - - browseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - doBrowseClick(); - } - }); - - } - - /* (non-java doc) - * Adds a validator to the text field that calls managedForm.getMessageManager(). - */ - @Override - protected void onAddValidators(final Text text) { - ModifyListener listener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - String package_name = text.getText(); - if (package_name.indexOf('.') < 1) { - getManagedForm().getMessageManager().addMessage(text, - "Package name should contain at least two identifiers.", - null /* data */, IMessageProvider.ERROR, text); - } else { - getManagedForm().getMessageManager().removeMessage(text, text); - } - } - }; - - text.addModifyListener(listener); - - // Make sure the validator removes its message(s) when the widget is disposed - text.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - getManagedForm().getMessageManager().removeMessage(text, text); - } - }); - - // Finally call the validator once to make sure the initial value is processed - listener.modifyText(null); - } - - /** - * Handles response to the Browse button by creating a Package dialog. - * */ - private void doBrowseClick() { - Text text = getTextWidget(); - - // we need to get the project of the manifest. - IProject project = getProject(); - if (project != null) { - - try { - SelectionDialog dlg = JavaUI.createPackageDialog(text.getShell(), - JavaCore.create(project), 0); - dlg.setTitle("Select Android Package"); - dlg.setMessage("Select the package for the Android project."); - dlg.setDefaultImage(EditorsPlugin.getAndroidLogo()); - - if (dlg.open() == Window.OK) { - Object[] results = dlg.getResult(); - if (results.length == 1) { - setPackageTextField((IPackageFragment)results[0]); - } - } - } catch (JavaModelException e1) { - } - } - } - - /** - * Handles response to the Label hyper link being activated. - */ - private void doLabelClick() { - // get the current package name - String package_name = getTextWidget().getText().trim(); - - if (package_name.length() == 0) { - createNewPackage(); - } else { - // Try to select the package in the Package Explorer for the current - // project and the current editor's site. - - IProject project = getProject(); - if (project == null) { - EditorsPlugin.log(IStatus.ERROR, "Failed to get project for UiPackageAttribute"); //$NON-NLS-1$ - return; - } - - IWorkbenchPartSite site = getUiParent().getEditor().getSite(); - if (site == null) { - EditorsPlugin.log(IStatus.ERROR, "Failed to get editor site for UiPackageAttribute"); //$NON-NLS-1$ - return; - } - - for (IPackageFragmentRoot root : getPackageFragmentRoots(project)) { - IPackageFragment fragment = root.getPackageFragment(package_name); - if (fragment != null && fragment.exists()) { - ShowInPackageViewAction action = new ShowInPackageViewAction(site); - action.run(fragment); - // This action's run() doesn't provide the status (although internally it could) - // so we just assume it worked. - return; - } - } - } - } - - /** - * Utility method that returns the project for the current file being edited. - * - * @return The IProject for the current file being edited or null. - */ - private IProject getProject() { - UiElementNode uiNode = getUiParent(); - AndroidEditor editor = uiNode.getEditor(); - IEditorInput input = editor.getEditorInput(); - if (input instanceof IFileEditorInput) { - // from the file editor we can get the IFile object, and from it, the IProject. - IFile file = ((IFileEditorInput)input).getFile(); - return file.getProject(); - } - - return null; - } - - /** - * Utility method that computes and returns the list of {@link IPackageFragmentRoot} - * corresponding to the source folder of the specified project. - * - * @param project the project - * @return an array of IPackageFragmentRoot. Can be empty but not null. - */ - private IPackageFragmentRoot[] getPackageFragmentRoots(IProject project) { - ArrayList result = new ArrayList(); - try { - IJavaProject javaProject = JavaCore.create(project); - IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); - for (int i = 0; i < roots.length; i++) { - IClasspathEntry entry = roots[i].getRawClasspathEntry(); - if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { - result.add(roots[i]); - } - } - } catch (JavaModelException e) { - } - - return result.toArray(new IPackageFragmentRoot[result.size()]); - } - - /** - * Utility method that sets the package's text field to the package fragment's name. - * */ - private void setPackageTextField(IPackageFragment type) { - Text text = getTextWidget(); - - String name = type.getElementName(); - - text.setText(name); - } - - - /** - * Displays and handles a "Create Package Wizard". - * - * This is invoked by doLabelClick() when clicking on the hyperlink label with an - * empty package text field. - */ - private void createNewPackage() { - OpenNewPackageWizardAction action = new OpenNewPackageWizardAction(); - - IProject project = getProject(); - action.setSelection(new StructuredSelection(project)); - action.run(); - - IJavaElement element = action.getCreatedElement(); - if (element != null && - element.exists() && - element.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { - setPackageTextField((IPackageFragment) element); - } - } - - @Override - public String[] getPossibleValues() { - // TODO: compute a list of existing packages for content assist completion - return null; - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java deleted file mode 100644 index 25bdb0e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationAttributesPart.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.ui.UiElementPart; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; - -/** - * Application's attributes section part for Application page. - *

              - * This part is displayed at the top of the application page and displays all the possible - * attributes of an application node in the AndroidManifest (icon, class name, label, etc.) - */ -final class ApplicationAttributesPart extends UiElementPart { - - /** Listen to changes to the UI node for and updates the UI */ - private AppNodeUpdateListener mAppNodeUpdateListener; - /** ManagedForm needed to create the UI controls */ - private IManagedForm mManagedForm; - - public ApplicationAttributesPart(Composite body, FormToolkit toolkit, ManifestEditor editor, - UiElementNode applicationUiNode) { - super(body, toolkit, editor, applicationUiNode, - "Application Attributes", // section title - "Defines the attributes specific to the application.", // section description - Section.TWISTIE | Section.EXPANDED); - } - - /** - * Changes and refreshes the Application UI node handle by the this part. - */ - @Override - public void setUiElementNode(UiElementNode uiElementNode) { - super.setUiElementNode(uiElementNode); - - createUiAttributes(mManagedForm); - } - - /* (non-java doc) - * Create the controls to edit the attributes for the given ElementDescriptor. - *

              - * This MUST not be called by the constructor. Instead it must be called from - * initialize (i.e. right after the form part is added to the managed form.) - *

              - * Derived classes can override this if necessary. - * - * @param managedForm The owner managed form - */ - @Override - protected void createFormControls(final IManagedForm managedForm) { - mManagedForm = managedForm; - setTable(createTableLayout(managedForm.getToolkit(), 4 /* numColumns */)); - - mAppNodeUpdateListener = new AppNodeUpdateListener(); - getUiElementNode().addUpdateListener(mAppNodeUpdateListener); - - createUiAttributes(mManagedForm); - } - - @Override - public void dispose() { - super.dispose(); - if (getUiElementNode() != null && mAppNodeUpdateListener != null) { - getUiElementNode().removeUpdateListener(mAppNodeUpdateListener); - mAppNodeUpdateListener = null; - } - } - - @Override - protected void createUiAttributes(IManagedForm managedForm) { - Composite table = getTable(); - if (table == null || managedForm == null) { - return; - } - - // Remove any old UI controls - for (Control c : table.getChildren()) { - c.dispose(); - } - - UiElementNode uiElementNode = getUiElementNode(); - AttributeDescriptor[] attr_desc_list = uiElementNode.getAttributeDescriptors(); - - // Display the attributes in 2 columns: - // attr 0 | attr 4 - // attr 1 | attr 5 - // attr 2 | attr 6 - // attr 3 | attr 7 - // that is we have to fill the grid in order 0, 4, 1, 5, 2, 6, 3, 7 - // thus index = i/2 + (i is odd * n/2) - int n = attr_desc_list.length; - int n2 = (int) Math.ceil(n / 2.0); - for (int i = 0; i < n; i++) { - AttributeDescriptor attr_desc = attr_desc_list[i / 2 + (i & 1) * n2]; - if (attr_desc instanceof XmlnsAttributeDescriptor) { - // Do not show hidden attributes - continue; - } - - UiAttributeNode ui_attr = uiElementNode.findUiAttribute(attr_desc); - if (ui_attr != null) { - ui_attr.createUiControl(table, managedForm); - } else { - // The XML has an extra attribute which wasn't declared in - // AndroidManifestDescriptors. This is not a problem, we just ignore it. - EditorsPlugin.log(IStatus.WARNING, - "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ - attr_desc.getXmlLocalName(), - uiElementNode.getDescriptor().getXmlName()); - } - } - - if (n == 0) { - createLabel(table, managedForm.getToolkit(), - "No attributes to display, waiting for SDK to finish loading...", - null /* tooltip */ ); - } - - // Initialize the enabled/disabled state - if (mAppNodeUpdateListener != null) { - mAppNodeUpdateListener.uiElementNodeUpdated(uiElementNode, null /* state, not used */); - } - - // Tell the section that the layout has changed. - layoutChanged(); - } - - /** - * This listener synchronizes the UI with the actual presence of the application XML node. - */ - private class AppNodeUpdateListener implements IUiUpdateListener { - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - // The UiElementNode for the application XML node always exists, even - // if there is no corresponding XML node in the XML file. - // - // We enable the UI here if the XML node is not null. - Composite table = getTable(); - boolean exists = (ui_node.getXmlNode() != null); - if (table != null && table.getEnabled() != exists) { - table.setEnabled(exists); - for (Control c : table.getChildren()) { - c.setEnabled(exists); - } - } - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java deleted file mode 100644 index 1823278..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationPage.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.ScrolledForm; - - -/** - * Page for "Application" settings, part of the AndroidManifest form editor. - *

              - * Useful reference: - * - * http://www.eclipse.org/articles/Article-Forms/article.html - */ -public final class ApplicationPage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "application_page"; //$NON-NLS-1$ - - /** Container editor */ - ManifestEditor mEditor; - /** The Application Toogle part */ - private ApplicationToggle mTooglePart; - /** The Application Attributes part */ - private ApplicationAttributesPart mAttrPart; - /** The tree view block */ - private UiTreeBlock mTreeBlock; - - public ApplicationPage(ManifestEditor editor) { - super(editor, PAGE_ID, "Application"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Manifest Application"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode appUiNode = getUiApplicationNode(); - - Composite body = form.getBody(); - FormToolkit toolkit = managedForm.getToolkit(); - - // We usually prefer to have a ColumnLayout here. However - // MasterDetailsBlock.createContent() below will reset the body's layout to a grid layout. - mTooglePart = new ApplicationToggle(body, toolkit, mEditor, appUiNode); - mTooglePart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - managedForm.addPart(mTooglePart); - mAttrPart = new ApplicationAttributesPart(body, toolkit, mEditor, appUiNode); - mAttrPart.getSection().setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - managedForm.addPart(mAttrPart); - - mTreeBlock = new UiTreeBlock(mEditor, appUiNode, - false /* autoCreateRoot */, - null /* element filters */, - "Application Nodes", - "List of all elements in the application"); - mTreeBlock.createContent(managedForm); - } - - /** - * Retrieves the application UI node. Since this is a mandatory node, it *always* - * exists, even if there is no matching XML node. - */ - private UiElementNode getUiApplicationNode() { - ElementDescriptor desc = AndroidManifestDescriptors.APPLICATION_ELEMENT; - return mEditor.getUiRootNode().findUiChildNode(desc.getXmlName()); - } - - /** - * Changes and refreshes the Application UI node handled by the sub parts. - */ - public void refreshUiApplicationNode() { - UiElementNode appUiNode = getUiApplicationNode(); - if (mTooglePart != null) { - mTooglePart.setUiElementNode(appUiNode); - } - if (mAttrPart != null) { - mAttrPart.setUiElementNode(appUiNode); - } - if (mTreeBlock != null) { - mTreeBlock.changeRootAndDescriptors(appUiNode, - null /* element filters */, - true /* refresh */); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java deleted file mode 100644 index daec98c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/ApplicationToggle.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.ui.UiElementPart; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; -import org.eclipse.ui.forms.widgets.TableWrapData; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.Text; - -/** - * Appllication Toogle section part for application page. - */ -final class ApplicationToggle extends UiElementPart { - - /** Checkbox indicating whether an application node is present */ - private Button mCheckbox; - /** Listen to changes to the UI node for and updates the checkbox */ - private AppNodeUpdateListener mAppNodeUpdateListener; - /** Internal flag to know where we're programmatically modifying the checkbox and we want to - * avoid triggering the checkbox's callback. */ - public boolean mInternalModification; - private FormText mTooltipFormText; - - public ApplicationToggle(Composite body, FormToolkit toolkit, ManifestEditor editor, - UiElementNode applicationUiNode) { - super(body, toolkit, editor, applicationUiNode, - "Application Toggle", - null, /* description */ - Section.TWISTIE | Section.EXPANDED); - } - - @Override - public void dispose() { - super.dispose(); - if (getUiElementNode() != null && mAppNodeUpdateListener != null) { - getUiElementNode().removeUpdateListener(mAppNodeUpdateListener); - mAppNodeUpdateListener = null; - } - } - - /** - * Changes and refreshes the Application UI node handle by the this part. - */ - @Override - public void setUiElementNode(UiElementNode uiElementNode) { - super.setUiElementNode(uiElementNode); - - updateTooltip(); - - // Set the state of the checkbox - mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(), - UiUpdateState.CHILDREN_CHANGED); - } - - /** - * Create the controls to edit the attributes for the given ElementDescriptor. - *

              - * This MUST not be called by the constructor. Instead it must be called from - * initialize (i.e. right after the form part is added to the managed form.) - * - * @param managedForm The owner managed form - */ - @Override - protected void createFormControls(IManagedForm managedForm) { - FormToolkit toolkit = managedForm.getToolkit(); - Composite table = createTableLayout(toolkit, 1 /* numColumns */); - - mTooltipFormText = createFormText(table, toolkit, true, "

              ", - false /* setupLayoutData */); - updateTooltip(); - - mCheckbox = toolkit.createButton(table, - "Define an tag in the AndroidManifest.xml", - SWT.CHECK); - mCheckbox.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP)); - mCheckbox.setSelection(false); - mCheckbox.addSelectionListener(new CheckboxSelectionListener()); - - mAppNodeUpdateListener = new AppNodeUpdateListener(); - getUiElementNode().addUpdateListener(mAppNodeUpdateListener); - - // Initialize the state of the checkbox - mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(), - UiUpdateState.CHILDREN_CHANGED); - - // Tell the section that the layout has changed. - layoutChanged(); - } - - /** - * Updates the application tooltip in the form text. - * If there is no tooltip, the form text is hidden. - */ - private void updateTooltip() { - boolean isVisible = false; - - String tooltip = getUiElementNode().getDescriptor().getTooltip(); - if (tooltip != null) { - tooltip = DescriptorsUtils.formatFormText(tooltip, - getUiElementNode().getDescriptor(), - FrameworkResourceManager.getInstance().getDocumentationBaseUrl()); - - mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */); - mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, EditorsPlugin.getAndroidLogo()); - mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener()); - isVisible = true; - } - - mTooltipFormText.setVisible(isVisible); - } - - /** - * This listener synchronizes the XML application node when the checkbox - * is changed by the user. - */ - private class CheckboxSelectionListener extends SelectionAdapter { - private Node mUndoXmlNode; - private Node mUndoXmlParent; - private Node mUndoXmlNextNode; - private Node mUndoXmlNextElement; - private Document mUndoXmlDocument; - - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - if (!mInternalModification && getUiElementNode() != null) { - getUiElementNode().getEditor().editXmlModel(new Runnable() { - public void run() { - if (mCheckbox.getSelection()) { - // The user wants an node. Either restore a previous one - // or create a full new one. - boolean create = true; - if (mUndoXmlNode != null) { - create = !restoreApplicationNode(); - } - if (create) { - getUiElementNode().createXmlNode(); - } - } else { - // Users no longer wants the node. - removeApplicationNode(); - } - } - }); - } - } - - /** - * Restore a previously "saved" application node. - * - * @return True if the node could be restored, false otherwise. - */ - private boolean restoreApplicationNode() { - if (mUndoXmlDocument == null || mUndoXmlNode == null) { - return false; - } - - // Validate node references... - mUndoXmlParent = validateNode(mUndoXmlDocument, mUndoXmlParent); - mUndoXmlNextNode = validateNode(mUndoXmlDocument, mUndoXmlNextNode); - mUndoXmlNextElement = validateNode(mUndoXmlDocument, mUndoXmlNextElement); - - if (mUndoXmlParent == null){ - // If the parent node doesn't exist, try to find a new manifest node. - // If it doesn't exist, create it. - mUndoXmlParent = getUiElementNode().getUiParent().prepareCommit(); - mUndoXmlNextNode = null; - mUndoXmlNextElement = null; - } - - boolean success = false; - if (mUndoXmlParent != null) { - // If the parent is still around, reuse the same node. - - // Ideally we want to insert the node before what used to be its next sibling. - // If that's not possible, we try to insert it before its next sibling element. - // If that's not possible either, it will be inserted at the end of the parent's. - Node next = mUndoXmlNextNode; - if (next == null) { - next = mUndoXmlNextElement; - } - mUndoXmlParent.insertBefore(mUndoXmlNode, next); - if (next == null) { - Text sep = mUndoXmlDocument.createTextNode("\n"); //$NON-NLS-1$ - mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag - } - success = true; - } - - // Remove internal references to avoid using them twice - mUndoXmlParent = null; - mUndoXmlNextNode = null; - mUndoXmlNextElement = null; - mUndoXmlNode = null; - mUndoXmlDocument = null; - return success; - } - - /** - * Validates that the given xml_node is still either the root node or one of its - * direct descendants. - * - * @param root_node The root of the node hierarchy to examine. - * @param xml_node The XML node to find. - * @return Returns xml_node if it is, otherwise returns null. - */ - private Node validateNode(Node root_node, Node xml_node) { - if (root_node == xml_node) { - return xml_node; - } else { - for (Node node = root_node.getFirstChild(); node != null; - node = node.getNextSibling()) { - if (root_node == xml_node || validateNode(node, xml_node) != null) { - return xml_node; - } - } - } - return null; - } - - /** - * Removes the node from the hierarchy. - * Before doing that, we try to remember where it was so that we can put it back - * in the same place. - */ - private void removeApplicationNode() { - // Make sure the node actually exists... - Node xml_node = getUiElementNode().getXmlNode(); - if (xml_node == null) { - return; - } - - // Save its parent, next sibling and next element - mUndoXmlDocument = xml_node.getOwnerDocument(); - mUndoXmlParent = xml_node.getParentNode(); - mUndoXmlNextNode = xml_node.getNextSibling(); - mUndoXmlNextElement = mUndoXmlNextNode; - while (mUndoXmlNextElement != null && - mUndoXmlNextElement.getNodeType() != Node.ELEMENT_NODE) { - mUndoXmlNextElement = mUndoXmlNextElement.getNextSibling(); - } - - // Actually remove the node from the hierarchy and keep it here. - // The returned node looses its parents/siblings pointers. - mUndoXmlNode = getUiElementNode().deleteXmlNode(); - } - } - - /** - * This listener synchronizes the UI (i.e. the checkbox) with the - * actual presence of the application XML node. - */ - private class AppNodeUpdateListener implements IUiUpdateListener { - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - // The UiElementNode for the application XML node always exists, even - // if there is no corresponding XML node in the XML file. - // - // To update the checkbox to reflect the actual state, we just need - // to check if the XML node is null. - try { - mInternalModification = true; - boolean exists = ui_node.getXmlNode() != null; - if (mCheckbox.getSelection() != exists) { - mCheckbox.setSelection(exists); - } - } finally { - mInternalModification = false; - } - - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java deleted file mode 100644 index 8eb6765..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/InstrumentationPage.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Page for instrumentation settings, part of the AndroidManifest form editor. - */ -public final class InstrumentationPage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "instrumentation_page"; //$NON-NLS-1$ - - /** Container editor */ - ManifestEditor mEditor; - - public InstrumentationPage(ManifestEditor editor) { - super(editor, PAGE_ID, "Instrumentation"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Manifest Instrumentation"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode manifest = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, manifest, - true /* autoCreateRoot */, - new ElementDescriptor[] { AndroidManifestDescriptors.INTRUMENTATION_ELEMENT }, - "Instrumentation", - "List of instrumentations defined in the manifest"); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java deleted file mode 100644 index 4e6521c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewExportPart.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.common.project.ExportHelper; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.forms.events.HyperlinkAdapter; -import org.eclipse.ui.forms.events.HyperlinkEvent; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; -import org.eclipse.ui.part.FileEditorInput; - -/** - * Export section part for overview page. - */ -final class OverviewExportPart extends ManifestSectionPart { - - private final OverviewPage mOverviewPage; - - public OverviewExportPart(OverviewPage overviewPage, Composite body, FormToolkit toolkit, ManifestEditor editor) { - super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */); - mOverviewPage = overviewPage; - Section section = getSection(); - section.setText("Exporting"); - section.setDescription("To export the application for distribution, you have the following options:"); - - Composite table = createTableLayout(toolkit, 2 /* numColumns */); - - StringBuffer buf = new StringBuffer(); - buf.append("
            • "); //$NON-NLS-1$ - buf.append("Use the export wizard"); - buf.append("
            • "); //$NON-NLS-1$ - buf.append("Export an unsigned apk"); - buf.append(""); //$NON-NLS-1$ - buf.append(" and sign it manually"); - buf.append("
            • "); //$NON-NLS-1$ - - FormText text = createFormText(table, toolkit, true, buf.toString(), - false /* setupLayoutData */); - text.addHyperlinkListener(new HyperlinkAdapter() { - @Override - public void linkActivated(HyperlinkEvent e) { - // get the project from the editor - IEditorInput input = mOverviewPage.mEditor.getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - IFile file = fileInput.getFile(); - IProject project = file.getProject(); - - if ("manual".equals(e.data)) { //$NON-NLS-1$ - // now we can export an unsigned apk for the project. - ExportHelper.exportProject(project); - } else { - // call the export wizard - ExportHelper.startExportWizard(project); - } - } - } - }); - - layoutChanged(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java deleted file mode 100644 index 182c6f3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewInfoPart.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.UiElementPart; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; -import org.w3c.dom.Node; - -/** - * Generic info section part for overview page - */ -final class OverviewInfoPart extends UiElementPart { - - private IManagedForm mManagedForm; - - public OverviewInfoPart(Composite body, FormToolkit toolkit, ManifestEditor editor) { - super(body, toolkit, editor, - null, // uiElementNode - "General Information", // section title - "Defines general information about the AndroidManifest.xml", // section description - Section.TWISTIE | Section.EXPANDED); - } - - /** - * Retrieves the UiElementNode that this part will edit. The node must exist - * and can't be null, by design, because it's a mandatory node. - */ - private static UiElementNode getManifestUiNode(ManifestEditor editor) { - ElementDescriptor desc = AndroidManifestDescriptors.MANIFEST_ELEMENT; - if (editor.getUiRootNode().getDescriptor() == desc) { - return editor.getUiRootNode(); - } else { - return editor.getUiRootNode().findUiChildNode(desc.getXmlName()); - } - } - - /** - * Retrieves the uses-sdk UI node. Since this is a mandatory node, it *always* - * exists, even if there is no matching XML node. - */ - private UiElementNode getUsesSdkUiNode(ManifestEditor editor) { - ElementDescriptor desc = AndroidManifestDescriptors.USES_SDK_ELEMENT; - return editor.getUiRootNode().findUiChildNode(desc.getXmlName()); - } - - /** - * Overridden in order to capture the current managed form. - * - * {@inheritDoc} - */ - @Override - protected void createFormControls(final IManagedForm managedForm) { - mManagedForm = managedForm; - super.createFormControls(managedForm); - } - - /** - * Removes any existing Attribute UI widgets and recreate them if the SDK has changed. - *

              - * This is called by {@link OverviewPage#refreshUiApplicationNode()} when the - * SDK has changed. - */ - public void onSdkChanged() { - createUiAttributes(mManagedForm); - } - - /** - * Overridden to add the description and the ui attributes of both the - * manifest and uses-sdk UI nodes. - *

              - * {@inheritDoc} - */ - @Override - protected void fillTable(Composite table, IManagedForm managedForm) { - int n = 0; - - UiElementNode uiNode = getManifestUiNode(getEditor()); - n += insertUiAttributes(uiNode, table, managedForm); - - uiNode = getUsesSdkUiNode(getEditor()); - n += insertUiAttributes(uiNode, table, managedForm); - - if (n == 0) { - createLabel(table, managedForm.getToolkit(), - "No attributes to display, waiting for SDK to finish loading...", - null /* tooltip */ ); - } - - layoutChanged(); - } - - /** - * Overridden to tests whether either the manifest or uses-sdk nodes parts are dirty. - *

              - * {@inheritDoc} - * - * @return true if the part is dirty, false - * otherwise. - */ - @Override - public boolean isDirty() { - boolean dirty = super.isDirty(); - - if (!dirty) { - UiElementNode uiNode = getManifestUiNode(getEditor()); - if (uiNode != null) { - for (UiAttributeNode ui_attr : uiNode.getUiAttributes()) { - if (ui_attr.isDirty()) { - markDirty(); - dirty = true; - break; - } - } - } - } - - if (!dirty) { - UiElementNode uiNode = getUsesSdkUiNode(getEditor()); - if (uiNode != null) { - for (UiAttributeNode ui_attr : uiNode.getUiAttributes()) { - if (ui_attr.isDirty()) { - markDirty(); - dirty = true; - break; - } - } - } - } - - return dirty; - } - - /** - * Overridden to save both the manifest or uses-sdk nodes. - *

              - * {@inheritDoc} - */ - @Override - public void commit(boolean onSave) { - final UiElementNode manifestUiNode = getManifestUiNode(getEditor()); - final UiElementNode usesSdkUiNode = getUsesSdkUiNode(getEditor()); - - getEditor().editXmlModel(new Runnable() { - public void run() { - if (manifestUiNode != null && manifestUiNode.isDirty()) { - for (UiAttributeNode ui_attr : manifestUiNode.getUiAttributes()) { - ui_attr.commit(); - } - } - - if (usesSdkUiNode != null && usesSdkUiNode.isDirty()) { - for (UiAttributeNode ui_attr : usesSdkUiNode.getUiAttributes()) { - ui_attr.commit(); - } - - if (!usesSdkUiNode.isDirty()) { - // Remove the XML element if it is empty. - // Rather than rely on the internal UI state, actually check that the - // XML element has no attributes and no child element so that we don't - // trash some user-generated content. - Node element = usesSdkUiNode.prepareCommit(); - if (element != null && - !element.hasAttributes() && - !element.hasChildNodes()) { - // Important note: we MUST NOT use usesSdkUiNode.deleteXmlNode() - // here, as it would clear the UiAttribute list and thus break the - // link between the controls and the ui attribute field. - // Instead what we want is simply to remove the XML node and let the - // UiElementNode node know. - Node parent = element.getParentNode(); - if (parent != null) { - parent.removeChild(element); - usesSdkUiNode.loadFromXmlNode(null /*xml_node*/); - } - } - } - } - } - }); - - // We need to call super's commit after we synchronized the nodes to make sure we - // reset the dirty flag after all the side effects from committing have occurred. - super.commit(onSave); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java deleted file mode 100644 index 7675fa2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewLinksPart.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; - -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; - -/** - * Links section part for overview page. - */ -final class OverviewLinksPart extends ManifestSectionPart { - - public OverviewLinksPart(Composite body, FormToolkit toolkit, ManifestEditor editor) { - super(body, toolkit, Section.TWISTIE | Section.EXPANDED, true /* description */); - Section section = getSection(); - section.setText("Links"); - section.setDescription("The content of the Android Manifest is made up of three sections. You can also edit the XML directly."); - - Composite table = createTableLayout(toolkit, 2 /* numColumns */); - - StringBuffer buf = new StringBuffer(); - buf.append(String.format("

            • ", // $NON-NLS-1$ - ApplicationPage.PAGE_ID)); - buf.append("Application"); - buf.append(""); //$NON-NLS-1$ - buf.append(": Activities, intent filters, providers, services and receivers."); - buf.append("
            • "); //$NON-NLS-1$ - - buf.append(String.format("
            • ", // $NON-NLS-1$ - PermissionPage.PAGE_ID)); - buf.append("Permission"); - buf.append(""); //$NON-NLS-1$ - buf.append(": Permissions defined and permissions used."); - buf.append("
            • "); //$NON-NLS-1$ - - buf.append(String.format("
            • ", // $NON-NLS-1$ - InstrumentationPage.PAGE_ID)); - buf.append("Instrumentation"); - buf.append(""); //$NON-NLS-1$ - buf.append(": Instrumentation defined."); - buf.append("
            • "); //$NON-NLS-1$ - - buf.append(String.format("
            • ", // $NON-NLS-1$ - ManifestEditor.TEXT_EDITOR_ID)); - buf.append("XML Source"); - buf.append(""); //$NON-NLS-1$ - buf.append(": Directly edit the AndroidManifest.xml file."); - buf.append("
            • "); //$NON-NLS-1$ - - buf.append("
            • "); // $NON-NLS-1$ - buf.append("Documentation: Documentation from the Android SDK for AndroidManifest.xml."); // $NON-NLS-1$ - buf.append("
            • "); //$NON-NLS-1$ - buf.append("
              "); //$NON-NLS-1$ - - FormText text = createFormText(table, toolkit, true, buf.toString(), - false /* setupLayoutData */); - text.setImage("android_img", EditorsPlugin.getAndroidLogo()); - text.setImage("app_img", getIcon(AndroidManifestDescriptors.APPLICATION_ELEMENT)); - text.setImage("perm_img", getIcon(AndroidManifestDescriptors.PERMISSION_ELEMENT)); - text.setImage("inst_img", getIcon(AndroidManifestDescriptors.INTRUMENTATION_ELEMENT)); - text.addHyperlinkListener(editor.createHyperlinkListener()); - } - - private Image getIcon(ElementDescriptor desc) { - if (desc != null && desc.getIcon() != null) { - return desc.getIcon(); - } - - return EditorsPlugin.getAndroidLogo(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java deleted file mode 100644 index 9e8925a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/OverviewPage.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ColumnLayout; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.ScrolledForm; - - -/** - * Page for overview settings, part of the AndroidManifest form editor. - *

              - * Useful reference: - * - * http://www.eclipse.org/articles/Article-Forms/article.html - */ -public final class OverviewPage extends FormPage { - - /** Page id used for switching tabs programmatically */ - final static String PAGE_ID = "overview_page"; //$NON-NLS-1$ - - /** Container editor */ - ManifestEditor mEditor; - /** Overview part (attributes for manifest) */ - private OverviewInfoPart mOverviewPart; - - public OverviewPage(ManifestEditor editor) { - super(editor, PAGE_ID, "Overview"); // tab's label, user visible, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Manifest Overview"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - Composite body = form.getBody(); - FormToolkit toolkit = managedForm.getToolkit(); - ColumnLayout cl = new ColumnLayout(); - cl.minNumColumns = cl.maxNumColumns = 1; - body.setLayout(cl); - mOverviewPart = new OverviewInfoPart(body, toolkit, mEditor); - managedForm.addPart(mOverviewPart); - managedForm.addPart(new OverviewExportPart(this, body, toolkit, mEditor)); - managedForm.addPart(new OverviewLinksPart(body, toolkit, mEditor)); - } - - /** - * Changes and refreshes the Application UI node handle by the sub parts. - */ - public void refreshUiApplicationNode() { - if (mOverviewPart != null) { - mOverviewPart.onSdkChanged(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java deleted file mode 100644 index a7c0ad5..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/manifest/pages/PermissionPage.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.manifest.pages; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Page for permissions settings, part of the AndroidManifest form editor. - *

              - * Useful reference: - * - * http://www.eclipse.org/articles/Article-Forms/article.html - */ -public final class PermissionPage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "permission_page"; //$NON-NLS-1$ - - /** Container editor */ - ManifestEditor mEditor; - - public PermissionPage(ManifestEditor editor) { - super(editor, PAGE_ID, "Permissions"); // tab label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Manifest Permissions"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode manifest = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, manifest, - true /* autoCreateRoot */, - new ElementDescriptor[] { - AndroidManifestDescriptors.PERMISSION_ELEMENT, - AndroidManifestDescriptors.USES_PERMISSION_ELEMENT, - AndroidManifestDescriptors.PERMISSION_GROUP_ELEMENT, - AndroidManifestDescriptors.PERMISSION_TREE_ELEMENT - }, - "Permissions", - "List of permissions defined and used by the manifest"); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java deleted file mode 100644 index 57b9a42..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuContentAssist.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.editors.menu; - -import com.android.ide.eclipse.editors.AndroidContentAssist; -import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; - -/** - * Content Assist Processor for /res/menu XML files - */ -class MenuContentAssist extends AndroidContentAssist { - - /** - * Constructor for LayoutContentAssist - */ - public MenuContentAssist() { - super(MenuDescriptors.getInstance().getDescriptor().getChildren()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuEditor.java deleted file mode 100644 index 4bf02fa..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuEditor.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * 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.editors.menu; - -import com.android.ide.eclipse.common.project.AndroidXPathFactory; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; - -/** - * Multi-page form editor for /res/menu XML files. - */ -public class MenuEditor extends AndroidEditor { - - public static final String ID = "com.android.ide.eclipse.editors.menu.MenuEditor"; //$NON-NLS-1$ - - /** Root node of the UI element hierarchy */ - private UiElementNode mUiRootNode; - /** Listener to update the root node if the resource framework changes */ - private Runnable mResourceRefreshListener; - - /** - * Creates the form editor for resources XML files. - */ - public MenuEditor() { - super(); - initUiRootNode(); - } - - /** - * Returns the root node of the UI element hierarchy, which here is - * the "menu" node. - */ - @Override - public UiElementNode getUiRootNode() { - return mUiRootNode; - } - - // ---- Base Class Overrides ---- - - @Override - public void dispose() { - if (mResourceRefreshListener != null) { - EditorsPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener = null; - } - super.dispose(); - } - - /** - * Returns whether the "save as" operation is supported by this editor. - *

              - * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return true; - } - - /** - * Create the various form pages. - */ - @Override - protected void createFormPages() { - try { - addPage(new MenuTreePage(this)); - } catch (PartInitException e) { - EditorsPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ - } - - } - - /* (non-java doc) - * Change the tab/title name to include the project name. - */ - @Override - protected void setInput(IEditorInput input) { - super.setInput(input); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput) input; - IFile file = fileInput.getFile(); - setPartName(String.format("%1$s", file.getName())); - } - } - - /** - * Processes the new XML Model, which XML root node is given. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - @Override - protected void xmlModelChanged(Document xml_doc) { - mUiRootNode.setXmlDocument(xml_doc); - if (xml_doc != null) { - ElementDescriptor root_desc = mUiRootNode.getDescriptor(); - try { - XPath xpath = AndroidXPathFactory.newXPath(); - Node node = (Node) xpath.evaluate("/" + root_desc.getXmlName(), //$NON-NLS-1$ - xml_doc, - XPathConstants.NODE); - if (node == null && root_desc.isMandatory()) { - // Create the root element if it doesn't exist yet (for empty new documents) - node = mUiRootNode.createXmlNode(); - } - - // Refresh the manifest UI node and all its descendants - mUiRootNode.loadFromXmlNode(node); - - // TODO ? startMonitoringMarkers(); - } catch (XPathExpressionException e) { - EditorsPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ - root_desc.getXmlName()); - } - } - - super.xmlModelChanged(xml_doc); - } - - - // ---- Local Methods ---- - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - */ - private void initUiRootNode() { - // The root UI node is always created, even if there's no corresponding XML node. - if (mUiRootNode == null) { - ElementDescriptor desc = MenuDescriptors.getInstance().getDescriptor(); - mUiRootNode = desc.createUiNode(); - mUiRootNode.setEditor(this); - - // Add a listener to refresh the root node if the resource framework changes - // by forcing it to parse its own XML - mResourceRefreshListener = new Runnable() { - public void run() { - commitPages(false /* onSave */); - - mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlNode()); - } - }; - EditorsPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener.run(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java deleted file mode 100644 index a5e3b09..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuSourceViewerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.editors.menu; - - -import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; - -/** - * Source Viewer Configuration that calls in MenuContentAssist. - */ -public class MenuSourceViewerConfig extends AndroidSourceViewerConfig { - - public MenuSourceViewerConfig() { - super(new MenuContentAssist()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java deleted file mode 100644 index 994074e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/MenuTreePage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.editors.menu; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Page for the menu form editor. - */ -public final class MenuTreePage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "layout_tree_page"; //$NON-NLS-1$ - - /** Container editor */ - MenuEditor mEditor; - - public MenuTreePage(MenuEditor editor) { - super(editor, PAGE_ID, "Layout"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Menu"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode rootNode = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, rootNode, - true /* autoCreateRoot */, - null /* no element filters */, - "Menu Elements", - "List of all menu elements in this XML file."); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java deleted file mode 100644 index 941f736..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/menu/descriptors/MenuDescriptors.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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.editors.menu.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; - -import java.util.ArrayList; -import java.util.Map; - - -/** - * Complete description of the menu structure. - */ -public class MenuDescriptors { - - public static final String MENU_ROOT_ELEMENT = "menu"; //$NON-NLS-1$ - - - - /** Singleton instance */ - private static MenuDescriptors sThis; - - /** The root element descriptor. */ - private ElementDescriptor mDescriptor = null; - - /** Returns a singleton instance of the {@link MenuDescriptors}. */ - public static synchronized MenuDescriptors getInstance() { - if (sThis == null) { - sThis = new MenuDescriptors(); - sThis.updateDescriptors(null); - } - return sThis; - } - - /** @return the root descriptor. */ - public ElementDescriptor getDescriptor() { - return mDescriptor; - } - - /** - * Updates the document descriptor. - *

              - * It first computes the new children of the descriptor and then updates them - * all at once. - * - * @param styleMap The map style => attributes from the attrs.xml file - */ - public synchronized void updateDescriptors(Map styleMap) { - - // There are 3 elements: menu, item and group. - // The root element MUST be a menu. - // A top menu can contain items or group: - // - top groups can contain top items - // - top items can contain sub-menus - // A sub menu can contains sub items or sub groups: - // - sub groups can contain sub items - // - sub items cannot contain anything - - if (mDescriptor == null) { - mDescriptor = createElement(styleMap, - MENU_ROOT_ELEMENT, // xmlName - "Menu", // uiName, - null, // TODO SDK URL - null, // extraAttribute - null, // childrenElements, - true /* mandatory */); - } - - // -- sub menu can have sub_items, sub_groups but not sub_menus - - ElementDescriptor sub_item = createElement(styleMap, - "item", // xmlName //$NON-NLS-1$ - "Item", // uiName, - null, // TODO SDK URL - null, // extraAttribute - null, // childrenElements, - false /* mandatory */); - - ElementDescriptor sub_group = createElement(styleMap, - "group", // xmlName //$NON-NLS-1$ - "Group", // uiName, - null, // TODO SDK URL - null, // extraAttribute - new ElementDescriptor[] { sub_item }, // childrenElements, - false /* mandatory */); - - ElementDescriptor sub_menu = createElement(styleMap, - MENU_ROOT_ELEMENT, // xmlName //$NON-NLS-1$ - "Sub-Menu", // uiName, - null, // TODO SDK URL - null, // extraAttribute - new ElementDescriptor[] { sub_item, sub_group }, // childrenElements, - true /* mandatory */); - - // -- top menu can have all top groups and top items (which can have sub menus) - - ElementDescriptor top_item = createElement(styleMap, - "item", // xmlName //$NON-NLS-1$ - "Item", // uiName, - null, // TODO SDK URL - null, // extraAttribute - new ElementDescriptor[] { sub_menu }, // childrenElements, - false /* mandatory */); - - ElementDescriptor top_group = createElement(styleMap, - "group", // xmlName //$NON-NLS-1$ - "Group", // uiName, - null, // TODO SDK URL - null, // extraAttribute - new ElementDescriptor[] { top_item }, // childrenElements, - false /* mandatory */); - - XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor("android", //$NON-NLS-1$ - AndroidConstants.NS_RESOURCES); - - updateElement(mDescriptor, styleMap, "Menu", xmlns); //$NON-NLS-1$ - mDescriptor.setChildren(new ElementDescriptor[] { top_item, top_group }); - } - - /** - * Returns a new ElementDescriptor constructed from the information given here - * and the javadoc & attributes extracted from the style map if any. - */ - private ElementDescriptor createElement( - Map styleMap, - String xmlName, String uiName, String sdkUrl, - AttributeDescriptor extraAttribute, - ElementDescriptor[] childrenElements, boolean mandatory) { - - ElementDescriptor element = new ElementDescriptor(xmlName, uiName, null, sdkUrl, - null, childrenElements, mandatory); - - return updateElement(element, styleMap, - getStyleName(xmlName), - extraAttribute); - } - - /** - * Updates an ElementDescriptor with the javadoc & attributes extracted from the style - * map if any. - */ - private ElementDescriptor updateElement(ElementDescriptor element, - Map styleMap, - String styleName, - AttributeDescriptor extraAttribute) { - ArrayList descs = new ArrayList(); - - DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; - if (style != null) { - DescriptorsUtils.appendAttributes(descs, - null, // elementName - AndroidConstants.NS_RESOURCES, - style.getAttributes(), - null, // requiredAttributes - null); // overrides - element.setTooltip(style.getJavaDoc()); - } - - if (extraAttribute != null) { - descs.add(extraAttribute); - } - - element.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); - return element; - } - - /** - * Returns the style name (i.e. the name found in attrs.xml) - * for a given XML element name. - *

              - * The rule is that all elements have for style name: - * - their xml name capitalized - * - a "Menu" prefix, except for

              itself which is just "Menu". - */ - private String getStyleName(String xmlName) { - String styleName = DescriptorsUtils.capitalize(xmlName); - - // This is NOT the UI Name but the expected internal style name - final String MENU_STYLE_BASE_NAME = "Menu"; //$NON-NLS-1$ - - if (!styleName.equals(MENU_STYLE_BASE_NAME)) { - styleName = MENU_STYLE_BASE_NAME + styleName; - } - return styleName; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java deleted file mode 100644 index 9fe15ab..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesContentAssist.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources; - -import com.android.ide.eclipse.editors.AndroidContentAssist; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; - -/** - * Content Assist Processor for /res/values and /res/drawable XML files - */ -class ResourcesContentAssist extends AndroidContentAssist { - - /** - * Constructor for ResourcesContentAssist - */ - public ResourcesContentAssist() { - super(new ElementDescriptor[] { ResourcesDescriptors.RESOURCES_ELEMENT }); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java deleted file mode 100644 index bad5699..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesEditor.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources; - -import com.android.ide.eclipse.common.project.AndroidXPathFactory; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -import javax.xml.xpath.XPath; -import javax.xml.xpath.XPathConstants; -import javax.xml.xpath.XPathExpressionException; - -/** - * Multi-page form editor for /res/values and /res/drawable XML files. - */ -public class ResourcesEditor extends AndroidEditor { - - public static final String ID = "com.android.ide.eclipse.editors.resources.ResourcesEditor"; //$NON-NLS-1$ - - /** Root node of the UI element hierarchy */ - private UiElementNode mUiResourcesNode; - - - /** - * Creates the form editor for resources XML files. - */ - public ResourcesEditor() { - super(); - initUiResourcesNode(); - } - - /** - * Returns the root node of the UI element hierarchy, which - * here is the "resources" node. - */ - @Override - public UiElementNode getUiRootNode() { - return mUiResourcesNode; - } - - // ---- Base Class Overrides ---- - - /** - * Returns whether the "save as" operation is supported by this editor. - *

              - * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return true; - } - - /** - * Create the various form pages. - */ - @Override - protected void createFormPages() { - try { - addPage(new ResourcesTreePage(this)); - } catch (PartInitException e) { - EditorsPlugin.log(IStatus.ERROR, "Error creating nested page"); //$NON-NLS-1$ - EditorsPlugin.getDefault().getLog().log(e.getStatus()); - } - } - - /* (non-java doc) - * Change the tab/title name to include the project name. - */ - @Override - protected void setInput(IEditorInput input) { - super.setInput(input); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput) input; - IFile file = fileInput.getFile(); - setPartName(String.format("%1$s", - file.getName())); - } - } - - /** - * Processes the new XML Model, which XML root node is given. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - @Override - protected void xmlModelChanged(Document xml_doc) { - mUiResourcesNode.setXmlDocument(xml_doc); - if (xml_doc != null) { - ElementDescriptor resources_desc = ResourcesDescriptors.RESOURCES_ELEMENT; - try { - XPath xpath = AndroidXPathFactory.newXPath(); - Node node = (Node) xpath.evaluate("/" + resources_desc.getXmlName(), //$NON-NLS-1$ - xml_doc, - XPathConstants.NODE); - assert node != null && node.getNodeName().equals(resources_desc.getXmlName()); - - // Refresh the manifest UI node and all its descendants - mUiResourcesNode.loadFromXmlNode(node); - } catch (XPathExpressionException e) { - EditorsPlugin.log(e, "XPath error when trying to find '%s' element in XML.", //$NON-NLS-1$ - resources_desc.getXmlName()); - } - } - - super.xmlModelChanged(xml_doc); - } - - - // ---- Local Methods ---- - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - */ - private void initUiResourcesNode() { - // The manifest UI node is always created, even if there's no corresponding XML node. - if (mUiResourcesNode == null) { - ElementDescriptor resources_desc = ResourcesDescriptors.RESOURCES_ELEMENT; - mUiResourcesNode = resources_desc.createUiNode(); - mUiResourcesNode.setEditor(this); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java deleted file mode 100644 index 1804312..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesSourceViewerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources; - - -import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; - -/** - * Source Viewer Configuration that calls in ResourcesContentAssist. - */ -public class ResourcesSourceViewerConfig extends AndroidSourceViewerConfig { - - public ResourcesSourceViewerConfig() { - super(new ResourcesContentAssist()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java deleted file mode 100644 index 8cabeca..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/ResourcesTreePage.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolder; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.resources.IFile; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; -import org.eclipse.ui.part.FileEditorInput; - -/** - * Page for instrumentation settings, part of the AndroidManifest form editor. - */ -public final class ResourcesTreePage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "res_tree_page"; //$NON-NLS-1$ - - /** Container editor */ - ResourcesEditor mEditor; - - public ResourcesTreePage(ResourcesEditor editor) { - super(editor, PAGE_ID, "Resources"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - - String configText = null; - IEditorInput input = mEditor.getEditorInput(); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput)input; - IFile iFile = fileInput.getFile(); - - ResourceFolder resFolder = ResourceManager.getInstance().getResourceFolder(iFile); - if (resFolder != null) { - configText = resFolder.getConfiguration().toDisplayString(); - } - } - - if (configText != null) { - form.setText(String.format("Android Resources (%1$s)", configText)); - } else { - form.setText("Android Resources"); - } - - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode resources = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, resources, - true /* autoCreateRoot */, - null /* no element filters */, - "Resources Elements", - "List of all resources elements in this XML file."); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java deleted file mode 100644 index 7670fa2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/CountryCodeQualifier.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Mobile Country Code. - */ -public final class CountryCodeQualifier extends ResourceQualifier { - /** Default pixel density value. This means the property is not set. */ - private final static int DEFAULT_CODE = -1; - - private final static Pattern sCountryCodePattern = Pattern.compile("^mcc(\\d{3})$");//$NON-NLS-1$ - - private int mCode = DEFAULT_CODE; - - public static final String NAME = "Mobile Country Code"; - - /** - * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, - * null is returned. - * @param segment the folder segment from which to create a qualifier. - * @return a new {@link CountryCodeQualifier} object or null - */ - public static CountryCodeQualifier getQualifier(String segment) { - Matcher m = sCountryCodePattern.matcher(segment); - if (m.matches()) { - String v = m.group(1); - - int code = -1; - try { - code = Integer.parseInt(v); - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid number. - return null; - } - - CountryCodeQualifier qualifier = new CountryCodeQualifier(); - qualifier.mCode = code; - return qualifier; - } - - return null; - } - - /** - * Returns the folder name segment for the given value. This is equivalent to calling - * {@link #toString()} on a {@link CountryCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. - */ - public static String getFolderSegment(int code) { - if (code != DEFAULT_CODE && code >= 100 && code <=999) { // code is 3 digit.) { - return String.format("mcc%1$d", code); //$NON-NLS-1$ - } - - return ""; //$NON-NLS-1$ - } - - public int getCode() { - return mCode; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Country Code"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("world"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mCode != DEFAULT_CODE; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - CountryCodeQualifier qualifier = getQualifier(value); - if (qualifier != null) { - config.setCountryCodeQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof CountryCodeQualifier) { - return mCode == ((CountryCodeQualifier)qualifier).mCode; - } - - return false; - } - - @Override - public int hashCode() { - return mCode; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - return getFolderSegment(mCode); - } - - @Override - public String getStringValue() { - if (mCode != DEFAULT_CODE) { - return String.format("MCC %1$d", mCode); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java deleted file mode 100644 index 3c3e11f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/FolderConfiguration.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; - - -/** - * Represents the configuration for Resource Folders. All the properties have a default - * value which means that the property is not set. - */ -public final class FolderConfiguration implements Comparable { - public final static String QUALIFIER_SEP = "-"; //$NON-NLS-1$ - - private final ResourceQualifier[] mQualifiers = new ResourceQualifier[INDEX_COUNT]; - - private final static int INDEX_COUNTRY_CODE = 0; - private final static int INDEX_NETWORK_CODE = 1; - private final static int INDEX_LANGUAGE = 2; - private final static int INDEX_REGION = 3; - private final static int INDEX_SCREEN_ORIENTATION = 4; - private final static int INDEX_PIXEL_DENSITY = 5; - private final static int INDEX_TOUCH_TYPE = 6; - private final static int INDEX_KEYBOARD_STATE = 7; - private final static int INDEX_TEXT_INPUT_METHOD = 8; - private final static int INDEX_NAVIGATION_METHOD = 9; - private final static int INDEX_SCREEN_DIMENSION = 10; - private final static int INDEX_COUNT = 11; - - /** - * Sets the config from the qualifiers of a given config. - * @param config - */ - public void set(FolderConfiguration config) { - for (int i = 0 ; i < INDEX_COUNT ; i++) { - mQualifiers[i] = config.mQualifiers[i]; - } - } - - /** - * Removes the qualifiers from the receiver if they are present (and valid) - * in the given configuration. - */ - public void substract(FolderConfiguration config) { - for (int i = 0 ; i < INDEX_COUNT ; i++) { - if (config.mQualifiers[i] != null && config.mQualifiers[i].isValid()) { - mQualifiers[i] = null; - } - } - } - - /** - * Returns the first invalid qualifier, or null if they are all valid (or if none - * exists). - */ - public ResourceQualifier getInvalidQualifier() { - for (int i = 0 ; i < INDEX_COUNT ; i++) { - if (mQualifiers[i] != null && mQualifiers[i].isValid() == false) { - return mQualifiers[i]; - } - } - - // all allocated qualifiers are valid, we return null. - return null; - } - - /** - * Returns whether the Region qualifier is valid. Region qualifier can only be present if a - * Language qualifier is present as well. - * @return true if the Region qualifier is valid. - */ - public boolean checkRegion() { - if (mQualifiers[INDEX_LANGUAGE] == null && mQualifiers[INDEX_REGION] != null) { - return false; - } - - return true; - } - - /** - * Adds a qualifier to the {@link FolderConfiguration} - * @param qualifier the {@link ResourceQualifier} to add. - */ - public void addQualifier(ResourceQualifier qualifier) { - if (qualifier instanceof CountryCodeQualifier) { - mQualifiers[INDEX_COUNTRY_CODE] = qualifier; - } else if (qualifier instanceof NetworkCodeQualifier) { - mQualifiers[INDEX_NETWORK_CODE] = qualifier; - } else if (qualifier instanceof LanguageQualifier) { - mQualifiers[INDEX_LANGUAGE] = qualifier; - } else if (qualifier instanceof RegionQualifier) { - mQualifiers[INDEX_REGION] = qualifier; - } else if (qualifier instanceof ScreenOrientationQualifier) { - mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier; - } else if (qualifier instanceof PixelDensityQualifier) { - mQualifiers[INDEX_PIXEL_DENSITY] = qualifier; - } else if (qualifier instanceof TouchScreenQualifier) { - mQualifiers[INDEX_TOUCH_TYPE] = qualifier; - } else if (qualifier instanceof KeyboardStateQualifier) { - mQualifiers[INDEX_KEYBOARD_STATE] = qualifier; - } else if (qualifier instanceof TextInputMethodQualifier) { - mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier; - } else if (qualifier instanceof NavigationMethodQualifier) { - mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier; - } else if (qualifier instanceof ScreenDimensionQualifier) { - mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier; - } - } - - /** - * Removes a given qualifier from the {@link FolderConfiguration}. - * @param qualifier the {@link ResourceQualifier} to remove. - */ - public void removeQualifier(ResourceQualifier qualifier) { - for (int i = 0 ; i < INDEX_COUNT ; i++) { - if (mQualifiers[i] == qualifier) { - mQualifiers[i] = null; - return; - } - } - } - - public void setCountryCodeQualifier(CountryCodeQualifier qualifier) { - mQualifiers[INDEX_COUNTRY_CODE] = qualifier; - } - - public CountryCodeQualifier getCountryCodeQualifier() { - return (CountryCodeQualifier)mQualifiers[INDEX_COUNTRY_CODE]; - } - - public void setNetworkCodeQualifier(NetworkCodeQualifier qualifier) { - mQualifiers[INDEX_NETWORK_CODE] = qualifier; - } - - public NetworkCodeQualifier getNetworkCodeQualifier() { - return (NetworkCodeQualifier)mQualifiers[INDEX_NETWORK_CODE]; - } - - public void setLanguageQualifier(LanguageQualifier qualifier) { - mQualifiers[INDEX_LANGUAGE] = qualifier; - } - - public LanguageQualifier getLanguageQualifier() { - return (LanguageQualifier)mQualifiers[INDEX_LANGUAGE]; - } - - public void setRegionQualifier(RegionQualifier qualifier) { - mQualifiers[INDEX_REGION] = qualifier; - } - - public RegionQualifier getRegionQualifier() { - return (RegionQualifier)mQualifiers[INDEX_REGION]; - } - - public void setScreenOrientationQualifier(ScreenOrientationQualifier qualifier) { - mQualifiers[INDEX_SCREEN_ORIENTATION] = qualifier; - } - - public ScreenOrientationQualifier getScreenOrientationQualifier() { - return (ScreenOrientationQualifier)mQualifiers[INDEX_SCREEN_ORIENTATION]; - } - - public void setPixelDensityQualifier(PixelDensityQualifier qualifier) { - mQualifiers[INDEX_PIXEL_DENSITY] = qualifier; - } - - public PixelDensityQualifier getPixelDensityQualifier() { - return (PixelDensityQualifier)mQualifiers[INDEX_PIXEL_DENSITY]; - } - - public void setTouchTypeQualifier(TouchScreenQualifier qualifier) { - mQualifiers[INDEX_TOUCH_TYPE] = qualifier; - } - - public TouchScreenQualifier getTouchTypeQualifier() { - return (TouchScreenQualifier)mQualifiers[INDEX_TOUCH_TYPE]; - } - - public void setKeyboardStateQualifier(KeyboardStateQualifier qualifier) { - mQualifiers[INDEX_KEYBOARD_STATE] = qualifier; - } - - public KeyboardStateQualifier getKeyboardStateQualifier() { - return (KeyboardStateQualifier)mQualifiers[INDEX_KEYBOARD_STATE]; - } - - public void setTextInputMethodQualifier(TextInputMethodQualifier qualifier) { - mQualifiers[INDEX_TEXT_INPUT_METHOD] = qualifier; - } - - public TextInputMethodQualifier getTextInputMethodQualifier() { - return (TextInputMethodQualifier)mQualifiers[INDEX_TEXT_INPUT_METHOD]; - } - - public void setNavigationMethodQualifier(NavigationMethodQualifier qualifier) { - mQualifiers[INDEX_NAVIGATION_METHOD] = qualifier; - } - - public NavigationMethodQualifier getNavigationMethodQualifier() { - return (NavigationMethodQualifier)mQualifiers[INDEX_NAVIGATION_METHOD]; - } - - public void setScreenDimensionQualifier(ScreenDimensionQualifier qualifier) { - mQualifiers[INDEX_SCREEN_DIMENSION] = qualifier; - } - - public ScreenDimensionQualifier getScreenDimensionQualifier() { - return (ScreenDimensionQualifier)mQualifiers[INDEX_SCREEN_DIMENSION]; - } - - /** - * Returns whether an object is equals to the receiver. - */ - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - - if (obj instanceof FolderConfiguration) { - FolderConfiguration fc = (FolderConfiguration)obj; - for (int i = 0 ; i < INDEX_COUNT ; i++) { - ResourceQualifier qualifier = mQualifiers[i]; - ResourceQualifier fcQualifier = fc.mQualifiers[i]; - if (qualifier != null) { - if (qualifier.equals(fcQualifier) == false) { - return false; - } - } else if (fcQualifier != null) { - return false; - } - } - - return true; - } - - return false; - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - /** - * Returns whether the Configuration has only default values. - */ - public boolean isDefault() { - for (ResourceQualifier irq : mQualifiers) { - if (irq != null) { - return false; - } - } - - return true; - } - - /** - * Returns the name of a folder with the configuration. - */ - public String getFolderName(ResourceFolderType folder) { - StringBuilder result = new StringBuilder(folder.getName()); - - for (ResourceQualifier qualifier : mQualifiers) { - if (qualifier != null) { - result.append(QUALIFIER_SEP); - result.append(qualifier.toString()); - } - } - - return result.toString(); - } - - /** - * Returns a string valid for usage in a folder name, or null if the configuration - * is default. - */ - @Override - public String toString() { - StringBuilder result = null; - - for (ResourceQualifier irq : mQualifiers) { - if (irq != null) { - if (result == null) { - result = new StringBuilder(); - } else { - result.append(QUALIFIER_SEP); - } - result.append(irq.toString()); - } - } - - if (result != null) { - return result.toString(); - } else { - return null; - } - } - - /** - * Returns a string valid for display purpose. - */ - public String toDisplayString() { - if (isDefault()) { - return "default"; - } - - StringBuilder result = null; - int index = 0; - ResourceQualifier qualifier = null; - - // pre- language/region qualifiers - while (index < INDEX_LANGUAGE) { - qualifier = mQualifiers[index++]; - if (qualifier != null) { - if (result == null) { - result = new StringBuilder(); - } else { - result.append(", "); //$NON-NLS-1$ - } - result.append(qualifier.getStringValue()); - - } - } - - // process the language/region qualifier in a custom way, if there are both non null. - if (mQualifiers[INDEX_LANGUAGE] != null && mQualifiers[INDEX_REGION] != null) { - String language = mQualifiers[INDEX_LANGUAGE].getStringValue(); - String region = mQualifiers[INDEX_REGION].getStringValue(); - - if (result == null) { - result = new StringBuilder(); - } else { - result.append(", "); //$NON-NLS-1$ - } - result.append(String.format("%s_%s", language, region)); //$NON-NLS-1$ - - index += 2; - } - - // post language/region qualifiers. - while (index < INDEX_COUNT) { - qualifier = mQualifiers[index++]; - if (qualifier != null) { - if (result == null) { - result = new StringBuilder(); - } else { - result.append(", "); //$NON-NLS-1$ - } - result.append(qualifier.getStringValue()); - - } - } - - return result.toString(); - } - - public int compareTo(FolderConfiguration folderConfig) { - // default are always at the top. - if (isDefault()) { - if (folderConfig.isDefault()) { - return 0; - } - return -1; - } - - // now we compare the qualifiers - for (int i = 0 ; i < INDEX_COUNT; i++) { - ResourceQualifier qualifier1 = mQualifiers[i]; - ResourceQualifier qualifier2 = folderConfig.mQualifiers[i]; - - if (qualifier1 == null) { - if (qualifier2 == null) { - continue; - } else { - return -1; - } - } else { - if (qualifier2 == null) { - return 1; - } else { - int result = qualifier1.compareTo(qualifier2); - - if (result == 0) { - continue; - } - - return result; - } - } - } - - // if we arrive here, all the qualifier matches - return 0; - } - - /** - * Returns whether the configuration match the given reference config. - *

              A match means that: - *

                - *
              • This config does not use any qualifier not used by the reference config
              • - *
              • The qualifier used by this config have the same values as the qualifiers of - * the reference config.
              • - *
              - * @param referenceConfig The reference configuration to test against. - * @return the number of matching qualifiers or -1 if the configurations are not compatible. - */ - public int match(FolderConfiguration referenceConfig) { - int matchCount = 0; - - for (int i = 0 ; i < INDEX_COUNT ; i++) { - ResourceQualifier testQualifier = mQualifiers[i]; - ResourceQualifier referenceQualifier = referenceConfig.mQualifiers[i]; - - // we only care if testQualifier is non null. If it's null, it's a match but - // without increasing the matchCount. - if (testQualifier != null) { - if (referenceQualifier == null) { - return -1; - } else if (testQualifier.equals(referenceQualifier) == false) { - return -1; - } - - // the qualifier match, increment the count - matchCount++; - } - } - return matchCount; - } - - /** - * Returns the index of the first non null {@link ResourceQualifier} starting at index - * startIndex - * @param startIndex - * @return -1 if no qualifier was found. - */ - public int getHighestPriorityQualifier(int startIndex) { - for (int i = startIndex ; i < INDEX_COUNT ; i++) { - if (mQualifiers[i] != null) { - return i; - } - } - - return -1; - } - - /** - * Create default qualifiers. - */ - public void createDefault() { - mQualifiers[INDEX_COUNTRY_CODE] = new CountryCodeQualifier(); - mQualifiers[INDEX_NETWORK_CODE] = new NetworkCodeQualifier(); - mQualifiers[INDEX_LANGUAGE] = new LanguageQualifier(); - mQualifiers[INDEX_REGION] = new RegionQualifier(); - mQualifiers[INDEX_SCREEN_ORIENTATION] = new ScreenOrientationQualifier(); - mQualifiers[INDEX_PIXEL_DENSITY] = new PixelDensityQualifier(); - mQualifiers[INDEX_TOUCH_TYPE] = new TouchScreenQualifier(); - mQualifiers[INDEX_KEYBOARD_STATE] = new KeyboardStateQualifier(); - mQualifiers[INDEX_TEXT_INPUT_METHOD] = new TextInputMethodQualifier(); - mQualifiers[INDEX_NAVIGATION_METHOD] = new NavigationMethodQualifier(); - mQualifiers[INDEX_SCREEN_DIMENSION] = new ScreenDimensionQualifier(); - } - - /** - * Returns an array of all the non null qualifiers. - */ - public ResourceQualifier[] getQualifiers() { - int count = 0; - for (int i = 0 ; i < INDEX_COUNT ; i++) { - if (mQualifiers[i] != null) { - count++; - } - } - - ResourceQualifier[] array = new ResourceQualifier[count]; - int index = 0; - for (int i = 0 ; i < INDEX_COUNT ; i++) { - if (mQualifiers[i] != null) { - array[index++] = mQualifiers[i]; - } - } - - return array; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java deleted file mode 100644 index ad232ed..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/KeyboardStateQualifier.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - - - -/** - * Resource Qualifier for keyboard state. - */ -public final class KeyboardStateQualifier extends ResourceQualifier { - - public static final String NAME = "Keyboard State"; - - private KeyboardState mValue = null; - - /** - * Screen Orientation enum. - */ - public static enum KeyboardState { - EXPOSED("keysexposed", "Exposed"), //$NON-NLS-1$ - HIDDEN("keyshidden", "Hidden"); //$NON-NLS-1$ - - private String mValue; - private String mDisplayValue; - - private KeyboardState(String value, String displayValue) { - mValue = value; - mDisplayValue = displayValue; - } - - /** - * Returns the enum for matching the provided qualifier value. - * @param value The qualifier value. - * @return the enum for the qualifier value or null if no matching was found. - */ - static KeyboardState getEnum(String value) { - for (KeyboardState orient : values()) { - if (orient.mValue.equals(value)) { - return orient; - } - } - - return null; - } - - public String getValue() { - return mValue; - } - - public String getDisplayValue() { - return mDisplayValue; - } - - public static int getIndex(KeyboardState value) { - int i = 0; - for (KeyboardState input : values()) { - if (value == input) { - return i; - } - - i++; - } - - return -1; - } - - public static KeyboardState getByIndex(int index) { - int i = 0; - for (KeyboardState value : values()) { - if (i == index) { - return value; - } - i++; - } - return null; - } - } - - public KeyboardStateQualifier() { - // pass - } - - public KeyboardStateQualifier(KeyboardState value) { - mValue = value; - } - - public KeyboardState getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Keyboard"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("keyboard"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - KeyboardState orientation = KeyboardState.getEnum(value); - if (orientation != null) { - KeyboardStateQualifier qualifier = new KeyboardStateQualifier(); - qualifier.mValue = orientation; - config.setKeyboardStateQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof KeyboardStateQualifier) { - return mValue == ((KeyboardStateQualifier)qualifier).mValue; - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return mValue.getValue(); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue.getDisplayValue(); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java deleted file mode 100644 index 99c3a43..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/LanguageQualifier.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Language. - */ -public final class LanguageQualifier extends ResourceQualifier { - private final static Pattern sLanguagePattern = Pattern.compile("^[a-z]{2}$"); //$NON-NLS-1$ - - public static final String NAME = "Language"; - - private String mValue; - - /** - * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, - * null is returned. - * @param segment the folder segment from which to create a qualifier. - * @return a new {@link LanguageQualifier} object or null - */ - public static LanguageQualifier getQualifier(String segment) { - if (sLanguagePattern.matcher(segment).matches()) { - LanguageQualifier qualifier = new LanguageQualifier(); - qualifier.mValue = segment; - - return qualifier; - } - return null; - } - - /** - * Returns the folder name segment for the given value. This is equivalent to calling - * {@link #toString()} on a {@link LanguageQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getValue()}. - */ - public static String getFolderSegment(String value) { - String segment = value.toLowerCase(); - if (sLanguagePattern.matcher(segment).matches()) { - return segment; - } - - return null; - } - - public String getValue() { - if (mValue != null) { - return mValue; - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return NAME; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("language"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - LanguageQualifier qualifier = getQualifier(value); - if (qualifier != null) { - config.setLanguageQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof LanguageQualifier) { - if (mValue == null) { - return ((LanguageQualifier)qualifier).mValue == null; - } - return mValue.equals(((LanguageQualifier)qualifier).mValue); - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return getFolderSegment(mValue); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue; - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java deleted file mode 100644 index 1a2cf53..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NavigationMethodQualifier.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - - - -/** - * Resource Qualifier for Navigation Method. - */ -public final class NavigationMethodQualifier extends ResourceQualifier { - - public static final String NAME = "Navigation Method"; - - private NavigationMethod mValue; - - /** - * Navigation Method enum. - */ - public static enum NavigationMethod { - DPAD("dpad", "D-pad"), //$NON-NLS-1$ - TRACKBALL("trackball", "Trackball"), //$NON-NLS-1$ - WHEEL("wheel", "Wheel"), //$NON-NLS-1$ - NONAV("nonav", "No Navigation"); //$NON-NLS-1$ - - private String mValue; - private String mDisplay; - - private NavigationMethod(String value, String display) { - mValue = value; - mDisplay = display; - } - - /** - * Returns the enum for matching the provided qualifier value. - * @param value The qualifier value. - * @return the enum for the qualifier value or null if no matching was found. - */ - static NavigationMethod getEnum(String value) { - for (NavigationMethod orient : values()) { - if (orient.mValue.equals(value)) { - return orient; - } - } - - return null; - } - - public String getValue() { - return mValue; - } - - public String getDisplayValue() { - return mDisplay; - } - - public static int getIndex(NavigationMethod value) { - int i = 0; - for (NavigationMethod nav : values()) { - if (nav == value) { - return i; - } - - i++; - } - - return -1; - } - - public static NavigationMethod getByIndex(int index) { - int i = 0; - for (NavigationMethod value : values()) { - if (i == index) { - return value; - } - i++; - } - return null; - } - } - - public NavigationMethodQualifier() { - // pass - } - - public NavigationMethodQualifier(NavigationMethod value) { - mValue = value; - } - - public NavigationMethod getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Navigation"; - } - - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("navpad"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - NavigationMethod method = NavigationMethod.getEnum(value); - if (method != null) { - NavigationMethodQualifier qualifier = new NavigationMethodQualifier(); - qualifier.mValue = method; - config.setNavigationMethodQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof NavigationMethodQualifier) { - return mValue == ((NavigationMethodQualifier)qualifier).mValue; - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return mValue.getValue(); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue.getDisplayValue(); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java deleted file mode 100644 index ce527a4..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/NetworkCodeQualifier.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Mobile Network Code Pixel Density. - */ -public final class NetworkCodeQualifier extends ResourceQualifier { - /** Default pixel density value. This means the property is not set. */ - private final static int DEFAULT_CODE = -1; - - private final static Pattern sNetworkCodePattern = Pattern.compile("^mnc(\\d{1,3})$"); //$NON-NLS-1$ - - private int mCode = DEFAULT_CODE; - - public final static String NAME = "Mobile Network Code"; - - /** - * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, - * null is returned. - * @param segment the folder segment from which to create a qualifier. - * @return a new {@link CountryCodeQualifier} object or null - */ - public static NetworkCodeQualifier getQualifier(String segment) { - Matcher m = sNetworkCodePattern.matcher(segment); - if (m.matches()) { - String v = m.group(1); - - int code = -1; - try { - code = Integer.parseInt(v); - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid number. - return null; - } - - NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); - qualifier.mCode = code; - return qualifier; - } - - return null; - } - - /** - * Returns the folder name segment for the given value. This is equivalent to calling - * {@link #toString()} on a {@link NetworkCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. - */ - public static String getFolderSegment(int code) { - if (code != DEFAULT_CODE && code >= 1 && code <= 999) { // code is 1-3 digit. - return String.format("mnc%1$d", code); //$NON-NLS-1$ - } - - return ""; //$NON-NLS-1$ - } - - public int getCode() { - return mCode; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Network Code"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("mnc"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mCode != DEFAULT_CODE; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - Matcher m = sNetworkCodePattern.matcher(value); - if (m.matches()) { - String v = m.group(1); - - int code = -1; - try { - code = Integer.parseInt(v); - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid number. - return false; - } - - NetworkCodeQualifier qualifier = new NetworkCodeQualifier(); - qualifier.mCode = code; - config.setNetworkCodeQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof NetworkCodeQualifier) { - return mCode == ((NetworkCodeQualifier)qualifier).mCode; - } - - return false; - } - - @Override - public int hashCode() { - return mCode; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - return getFolderSegment(mCode); - } - - @Override - public String getStringValue() { - if (mCode != DEFAULT_CODE) { - return String.format("MNC %1$d", mCode); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java deleted file mode 100644 index 0fd05bf..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/PixelDensityQualifier.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Screen Pixel Density. - */ -public final class PixelDensityQualifier extends ResourceQualifier { - /** Default pixel density value. This means the property is not set. */ - private final static int DEFAULT_DENSITY = -1; - - private final static Pattern sPixelDensityPattern = Pattern.compile("^(\\d+)dpi$");//$NON-NLS-1$ - - public static final String NAME = "Pixel Density"; - - private int mValue = DEFAULT_DENSITY; - - /** - * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, - * null is returned. - * @param segment the folder segment from which to create a qualifier. - * @return a new {@link CountryCodeQualifier} object or null - */ - public static PixelDensityQualifier getQualifier(String folderSegment) { - Matcher m = sPixelDensityPattern.matcher(folderSegment); - if (m.matches()) { - String v = m.group(1); - - int density = -1; - try { - density = Integer.parseInt(v); - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid number. - return null; - } - - PixelDensityQualifier qualifier = new PixelDensityQualifier(); - qualifier.mValue = density; - - return qualifier; - } - return null; - } - - /** - * Returns the folder name segment for the given value. This is equivalent to calling - * {@link #toString()} on a {@link NetworkCodeQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getCode()}. - */ - public static String getFolderSegment(int value) { - if (value != DEFAULT_DENSITY) { - return String.format("%1$ddpi", value); //$NON-NLS-1$ - } - - return ""; //$NON-NLS-1$ - } - - public int getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return NAME; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("dpi"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != DEFAULT_DENSITY; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - PixelDensityQualifier qualifier = getQualifier(value); - if (qualifier != null) { - config.setPixelDensityQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof PixelDensityQualifier) { - return mValue == ((PixelDensityQualifier)qualifier).mValue; - } - - return false; - } - - @Override - public int hashCode() { - return mValue; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - return getFolderSegment(mValue); - } - - @Override - public String getStringValue() { - if (mValue != DEFAULT_DENSITY) { - return String.format("%1$d dpi", mValue); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java deleted file mode 100644 index dc4d5fa..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/RegionQualifier.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Region. - */ -public final class RegionQualifier extends ResourceQualifier { - private final static Pattern sRegionPattern = Pattern.compile("^r([A-Z]{2})$"); //$NON-NLS-1$ - - public static final String NAME = "Region"; - - private String mValue; - - /** - * Creates and returns a qualifier from the given folder segment. If the segment is incorrect, - * null is returned. - * @param segment the folder segment from which to create a qualifier. - * @return a new {@link RegionQualifier} object or null - */ - public static RegionQualifier getQualifier(String segment) { - Matcher m = sRegionPattern.matcher(segment); - if (m.matches()) { - RegionQualifier qualifier = new RegionQualifier(); - qualifier.mValue = m.group(1); - - return qualifier; - } - return null; - } - - /** - * Returns the folder name segment for the given value. This is equivalent to calling - * {@link #toString()} on a {@link RegionQualifier} object. - * @param value the value of the qualifier, as returned by {@link #getValue()}. - */ - public static String getFolderSegment(String value) { - if (value != null) { - String segment = "r" + value.toUpperCase(); //$NON-NLS-1$ - if (sRegionPattern.matcher(segment).matches()) { - return segment; - } - } - - return ""; //$NON-NLS-1$ - } - - public String getValue() { - if (mValue != null) { - return mValue; - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return NAME; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("region"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - RegionQualifier qualifier = getQualifier(value); - if (qualifier != null) { - config.setRegionQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof RegionQualifier) { - if (mValue == null) { - return ((RegionQualifier)qualifier).mValue == null; - } - return mValue.equals(((RegionQualifier)qualifier).mValue); - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - return getFolderSegment(mValue); - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue; - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java deleted file mode 100644 index 0257afa..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ResourceQualifier.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import org.eclipse.swt.graphics.Image; - -/** - * Base class for resource qualifiers. - *

              The resource qualifier classes are designed as immutable. - */ -public abstract class ResourceQualifier implements Comparable { - - /** - * Returns the human readable name of the qualifier. - */ - public abstract String getName(); - - /** - * Returns a shorter human readable name for the qualifier. - * @see #getName() - */ - public abstract String getShortName(); - - /** - * Returns the icon for the qualifier. - */ - public abstract Image getIcon(); - - /** - * Returns whether the qualifier has a valid filter value. - */ - public abstract boolean isValid(); - - /** - * Check if the value is valid for this qualifier, and if so sets the value - * into a Folder Configuration. - * @param value The value to check and set. Must not be null. - * @param config The folder configuration to receive the value. Must not be null. - * @return true if the value was valid and was set. - */ - public abstract boolean checkAndSet(String value, FolderConfiguration config); - - /** - * Returns a string formated to be used in a folder name. - *

              This is declared as abstract to force children classes to implement it. - */ - @Override - public abstract String toString(); - - /** - * Returns a string formatted for display purpose. - */ - public abstract String getStringValue(); - - /** - * Returns true if both objects are equal. - *

              This is declared as abstract to force children classes to implement it. - */ - @Override - public abstract boolean equals(Object object); - - /** - * Returns a hash code value for the object. - *

              This is declared as abstract to force children classes to implement it. - */ - @Override - public abstract int hashCode(); - - public final int compareTo(ResourceQualifier o) { - return toString().compareTo(o.toString()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java deleted file mode 100644 index a2cc789..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenDimensionQualifier.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resource Qualifier for Screen Dimension. - */ -public final class ScreenDimensionQualifier extends ResourceQualifier { - /** Default screen size value. This means the property is not set */ - final static int DEFAULT_SIZE = -1; - - private final static Pattern sDimensionPattern = Pattern.compile( - "^(\\d+)x(\\d+)$"); //$NON-NLS-1$ - - public static final String NAME = "Screen Dimension"; - - /** Screen size 1 value. This is not size X or Y because the folder name always - * contains the biggest size first. So if the qualifier is 400x200, size 1 will always be - * 400 but that'll be X in landscape and Y in portrait. - * Default value is DEFAULT_SIZE */ - private int mValue1 = DEFAULT_SIZE; - - /** Screen size 2 value. This is not size X or Y because the folder name always - * contains the biggest size first. So if the qualifier is 400x200, size 2 will always be - * 200 but that'll be Y in landscape and X in portrait. - * Default value is DEFAULT_SIZE */ - private int mValue2 = DEFAULT_SIZE; - - public int getValue1() { - return mValue1; - } - - public int getValue2() { - return mValue2; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Dimension"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("dimension"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue1 != DEFAULT_SIZE && mValue2 != DEFAULT_SIZE; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - Matcher m = sDimensionPattern.matcher(value); - if (m.matches()) { - String d1 = m.group(1); - String d2 = m.group(2); - - ScreenDimensionQualifier qualifier = getQualifier(d1, d2); - if (qualifier != null) { - config.setScreenDimensionQualifier(qualifier); - return true; - } - } - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof ScreenDimensionQualifier) { - ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier; - return (mValue1 == q.mValue1 && mValue2 == q.mValue2); - } - - return false; - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - public static ScreenDimensionQualifier getQualifier(String size1, String size2) { - try { - int s1 = Integer.parseInt(size1); - int s2 = Integer.parseInt(size2); - - ScreenDimensionQualifier qualifier = new ScreenDimensionQualifier(); - - if (s1 > s2) { - qualifier.mValue1 = s1; - qualifier.mValue2 = s2; - } else { - qualifier.mValue1 = s2; - qualifier.mValue2 = s1; - } - - return qualifier; - } catch (NumberFormatException e) { - // looks like the string we extracted wasn't a valid number. - } - - return null; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - return String.format("%1$dx%2$d", mValue1, mValue2); //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue1 != -1 && mValue2 != -1) { - return String.format("%1$dx%2$d", mValue1, mValue2); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java deleted file mode 100644 index e30930f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/ScreenOrientationQualifier.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - -/** - * Resource Qualifier for Screen Orientation. - */ -public final class ScreenOrientationQualifier extends ResourceQualifier { - - public static final String NAME = "Screen Orientation"; - - private ScreenOrientation mValue = null; - - /** - * Screen Orientation enum. - */ - public static enum ScreenOrientation { - PORTRAIT("port", "Portrait"), //$NON-NLS-1$ - LANDSCAPE("land", "Landscape"), //$NON-NLS-1$ - SQUARE("square", "Square"); //$NON-NLS-1$ - - private String mValue; - private String mDisplayValue; - - private ScreenOrientation(String value, String displayValue) { - mValue = value; - mDisplayValue = displayValue; - } - - /** - * Returns the enum for matching the provided qualifier value. - * @param value The qualifier value. - * @return the enum for the qualifier value or null if no matching was found. - */ - static ScreenOrientation getEnum(String value) { - for (ScreenOrientation orient : values()) { - if (orient.mValue.equals(value)) { - return orient; - } - } - - return null; - } - - public String getValue() { - return mValue; - } - - public String getDisplayValue() { - return mDisplayValue; - } - - public static int getIndex(ScreenOrientation orientation) { - int i = 0; - for (ScreenOrientation orient : values()) { - if (orient == orientation) { - return i; - } - - i++; - } - - return -1; - } - - public static ScreenOrientation getByIndex(int index) { - int i = 0; - for (ScreenOrientation orient : values()) { - if (i == index) { - return orient; - } - i++; - } - - return null; - } - } - - public ScreenOrientationQualifier() { - } - - public ScreenOrientationQualifier(ScreenOrientation value) { - mValue = value; - } - - public ScreenOrientation getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Orientation"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("orientation"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - ScreenOrientation orientation = ScreenOrientation.getEnum(value); - if (orientation != null) { - ScreenOrientationQualifier qualifier = new ScreenOrientationQualifier(orientation); - config.setScreenOrientationQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof ScreenOrientationQualifier) { - return mValue == ((ScreenOrientationQualifier)qualifier).mValue; - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return mValue.getValue(); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue.getDisplayValue(); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java deleted file mode 100644 index de40138..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TextInputMethodQualifier.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - - - - -/** - * Resource Qualifier for Text Input Method. - */ -public final class TextInputMethodQualifier extends ResourceQualifier { - - public static final String NAME = "Text Input Method"; - - private TextInputMethod mValue; - - /** - * Screen Orientation enum. - */ - public static enum TextInputMethod { - NOKEY("nokeys", "No Keys"), //$NON-NLS-1$ - QWERTY("qwerty", "Qwerty"), //$NON-NLS-1$ - TWELVEKEYS("12key", "12 Key"); //$NON-NLS-1$ - - private String mValue; - private String mDisplayValue; - - private TextInputMethod(String value, String displayValue) { - mValue = value; - mDisplayValue = displayValue; - } - - /** - * Returns the enum for matching the provided qualifier value. - * @param value The qualifier value. - * @return the enum for the qualifier value or null if no matching was found. - */ - static TextInputMethod getEnum(String value) { - for (TextInputMethod orient : values()) { - if (orient.mValue.equals(value)) { - return orient; - } - } - - return null; - } - - public String getValue() { - return mValue; - } - - public String getDisplayValue() { - return mDisplayValue; - } - - public static int getIndex(TextInputMethod value) { - int i = 0; - for (TextInputMethod input : values()) { - if (value == input) { - return i; - } - - i++; - } - - return -1; - } - - public static TextInputMethod getByIndex(int index) { - int i = 0; - for (TextInputMethod value : values()) { - if (i == index) { - return value; - } - i++; - } - return null; - } - } - - public TextInputMethodQualifier() { - // pass - } - - public TextInputMethodQualifier(TextInputMethod value) { - mValue = value; - } - - public TextInputMethod getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return "Text Input"; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("text_input"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - TextInputMethod method = TextInputMethod.getEnum(value); - if (method != null) { - TextInputMethodQualifier qualifier = new TextInputMethodQualifier(); - qualifier.mValue = method; - config.setTextInputMethodQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof TextInputMethodQualifier) { - return mValue == ((TextInputMethodQualifier)qualifier).mValue; - } - - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return mValue.getValue(); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue.getDisplayValue(); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java deleted file mode 100644 index 2390e2c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/configurations/TouchScreenQualifier.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.configurations; - -import com.android.ide.eclipse.editors.IconFactory; - -import org.eclipse.swt.graphics.Image; - - -/** - * Resource Qualifier for Touch Screen type. - */ -public final class TouchScreenQualifier extends ResourceQualifier { - - public static final String NAME = "Touch Screen"; - - private TouchScreenType mValue; - - /** - * Screen Orientation enum. - */ - public static enum TouchScreenType { - NOTOUCH("notouch", "No Touch"), //$NON-NLS-1$ - STYLUS("stylus", "Stylus"), //$NON-NLS-1$ - FINGER("finger", "Finger"); //$NON-NLS-1$ - - private String mValue; - private String mDisplayValue; - - private TouchScreenType(String value, String displayValue) { - mValue = value; - mDisplayValue = displayValue; - } - - /** - * Returns the enum for matching the provided qualifier value. - * @param value The qualifier value. - * @return the enum for the qualifier value or null if no matching was found. - */ - static TouchScreenType getEnum(String value) { - for (TouchScreenType orient : values()) { - if (orient.mValue.equals(value)) { - return orient; - } - } - - return null; - } - - public String getValue() { - return mValue; - } - - public String getDisplayValue() { - return mDisplayValue; - } - - public static int getIndex(TouchScreenType touch) { - int i = 0; - for (TouchScreenType t : values()) { - if (t == touch) { - return i; - } - - i++; - } - - return -1; - } - - public static TouchScreenType getByIndex(int index) { - int i = 0; - for (TouchScreenType value : values()) { - if (i == index) { - return value; - } - i++; - } - - return null; - } - } - - public TouchScreenQualifier() { - // pass - } - - public TouchScreenQualifier(TouchScreenType touchValue) { - mValue = touchValue; - } - - public TouchScreenType getValue() { - return mValue; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public String getShortName() { - return NAME; - } - - @Override - public Image getIcon() { - return IconFactory.getInstance().getIcon("touch"); //$NON-NLS-1$ - } - - @Override - public boolean isValid() { - return mValue != null; - } - - @Override - public boolean checkAndSet(String value, FolderConfiguration config) { - TouchScreenType type = TouchScreenType.getEnum(value); - if (type != null) { - TouchScreenQualifier qualifier = new TouchScreenQualifier(); - qualifier.mValue = type; - config.setTouchTypeQualifier(qualifier); - return true; - } - - return false; - } - - @Override - public boolean equals(Object qualifier) { - if (qualifier instanceof TouchScreenQualifier) { - return mValue == ((TouchScreenQualifier)qualifier).mValue; - } - return false; - } - - @Override - public int hashCode() { - if (mValue != null) { - return mValue.hashCode(); - } - - return 0; - } - - /** - * Returns the string used to represent this qualifier in the folder name. - */ - @Override - public String toString() { - if (mValue != null) { - return mValue.getValue(); - } - - return ""; //$NON-NLS-1$ - } - - @Override - public String getStringValue() { - if (mValue != null) { - return mValue.getDisplayValue(); - } - - return ""; //$NON-NLS-1$ - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java deleted file mode 100644 index 92288ba..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ColorValueDescriptor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.descriptors; - -import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; -import com.android.ide.eclipse.editors.resources.uimodel.UiColorValueNode; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; - -/** - * Describes a Color XML element value displayed by an {@link UiColorValueNode}. - */ -public final class ColorValueDescriptor extends TextValueDescriptor { - - public ColorValueDescriptor(String uiName, String tooltip) { - super(uiName, tooltip); - } - - /** - * @return A new {@link UiResourceAttributeNode} linked to this theme descriptor. - */ - @Override - public UiAttributeNode createUiNode(UiElementNode uiParent) { - return new UiColorValueNode(this, uiParent); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java deleted file mode 100644 index bf83d52..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ItemElementDescriptor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.editors.resources.descriptors; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.resources.uimodel.UiItemElementNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -/** - * {@link ItemElementDescriptor} is a special version of {@link ElementDescriptor} that - * uses a specialized {@link UiItemElementNode} for display. - */ -public class ItemElementDescriptor extends ElementDescriptor { - - /** - * Constructs a new {@link ItemElementDescriptor} based on its XML name, UI name, - * tooltip, SDK url, attributes list, children list and mandatory. - * - * @param xml_name The XML element node name. Case sensitive. - * @param ui_name The XML element name for the user interface, typically capitalized. - * @param tooltip An optional tooltip. Can be null or empty. - * @param sdk_url An optional SKD URL. Can be null or empty. - * @param attributes The list of allowed attributes. Can be null or empty. - * @param children The list of allowed children. Can be null or empty. - * @param mandatory Whether this node must always exist (even for empty models). A mandatory - * UI node is never deleted and it may lack an actual XML node attached. A non-mandatory - * UI node MUST have an XML node attached and it will cease to exist when the XML node - * ceases to exist. - */ - public ItemElementDescriptor(String xml_name, String ui_name, - String tooltip, String sdk_url, AttributeDescriptor[] attributes, - ElementDescriptor[] children, boolean mandatory) { - super(xml_name, ui_name, tooltip, sdk_url, attributes, children, mandatory); - } - - @Override - public UiElementNode createUiNode() { - return new UiItemElementNode(this); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java deleted file mode 100644 index 4769cef..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/descriptors/ResourcesDescriptors.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.descriptors; - -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.FlagAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; - - -/** - * Complete description of the AndroidManifest.xml structure. - */ -public class ResourcesDescriptors { - - - // Public attributes names, attributes descriptors and elements descriptors - - public static final String ROOT_ELEMENT = "resources"; //$NON-NLS-1$ - - public static final String NAME_ATTR = "name"; //$NON-NLS-1$ - public static final String TYPE_ATTR = "type"; //$NON-NLS-1$ - - /** The {@link ElementDescriptor} for the root Manifest element. */ - public static final ElementDescriptor RESOURCES_ELEMENT; - - - static { - - // Common attributes used in many placed - - - // Elements - - ElementDescriptor color_element = new ElementDescriptor( - "color", //$NON-NLS-1$ - "Color", - "A @color@ value specifies an RGB value with an alpha channel, which can be used in various places such as specifying a solid color for a Drawable or the color to use for text. It always begins with a # character and then is followed by the alpha-red-green-blue information in one of the following formats: #RGB, #ARGB, #RRGGBB or #AARRGGBB.", - "http://code.google.com/android/reference/available-resources.html#colorvals", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this color."), - new ColorValueDescriptor( - "Value*", - "A mandatory color value.") - }, - null, // no child nodes - false /* not mandatory */); - - ElementDescriptor string_element = new ElementDescriptor( - "string", //$NON-NLS-1$ - "String", - "@Strings@, with optional simple formatting, can be stored and retrieved as resources. You can add formatting to your string by using three standard HTML tags: b, i, and u. If you use an apostrophe or a quote in your string, you must either escape it or enclose the whole string in the other kind of enclosing quotes.", - "http://code.google.com/android/reference/available-resources.html#stringresources", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this string."), - new TextValueDescriptor( - "Value*", - "A mandatory string value.") - }, - null, // no child nodes - false /* not mandatory */); - - ElementDescriptor item_element = new ItemElementDescriptor( - "item", //$NON-NLS-1$ - "Item", - null, // TODO find javadoc - null, // TODO find link to javadoc - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this resource."), - new ListAttributeDescriptor(TYPE_ATTR, - "Type*", - null /* nsUri */, - "The mandatory type of this resource.", - ResourceType.getNames() - ), - new FlagAttributeDescriptor("format", - "Format", - null /* nsUri */, - "The optional format of this resource.", - new String[] { - "boolean", //$NON-NLS-1$ - "color", //$NON-NLS-1$ - "dimension", //$NON-NLS-1$ - "float", //$NON-NLS-1$ - "fraction", //$NON-NLS-1$ - "integer", //$NON-NLS-1$ - "reference", //$NON-NLS-1$ - "string" //$NON-NLS-1$ - }), - new TextValueDescriptor( - "Value", - "A standard string, hex color value, or reference to any other resource type.") - }, - null, // no child nodes - false /* not mandatory */); - - ElementDescriptor drawable_element = new ElementDescriptor( - "drawable", //$NON-NLS-1$ - "Drawable", - "A @drawable@ defines a rectangle of color. Android accepts color values written in various web-style formats -- a hexadecimal constant in any of the following forms: #RGB, #ARGB, #RRGGBB, #AARRGGBB. Zero in the alpha channel means transparent. The default value is opaque.", - "http://code.google.com/android/reference/available-resources.html#colordrawableresources", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this drawable."), - new TextValueDescriptor( - "Value*", - "A mandatory color value in the form #RGB, #ARGB, #RRGGBB or #AARRGGBB.") - }, - null, // no child nodes - false /* not mandatory */); - - ElementDescriptor dimen_element = new ElementDescriptor( - "dimen", //$NON-NLS-1$ - "Dimension", - "You can create common dimensions to use for various screen elements by defining @dimension@ values in XML. A dimension resource is a number followed by a unit of measurement. Supported units are px (pixels), in (inches), mm (millimeters), pt (points at 72 DPI), dp (density-independent pixels) and sp (scale-independent pixels)", - "http://code.google.com/android/reference/available-resources.html#dimension", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this dimension."), - new TextValueDescriptor( - "Value*", - "A mandatory dimension value is a number followed by a unit of measurement. For example: 10px, 2in, 5sp.") - }, - null, // no child nodes - false /* not mandatory */); - - ElementDescriptor style_element = new ElementDescriptor( - "style", //$NON-NLS-1$ - "Style/Theme", - "Both @styles and themes@ are defined in a style block containing one or more string or numerical values (typically color values), or references to other resources (drawables and so on).", - "http://code.google.com/android/reference/available-resources.html#stylesandthemes", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this theme."), - new TextAttributeDescriptor("parent", // $NON-NLS-1$ - "Parent", - null /* nsUri */, - "An optional parent theme. All values from the specified theme will be inherited into this theme. Any values with identical names that you specify will override inherited values."), - }, - new ElementDescriptor[] { - new ElementDescriptor( - "item", //$NON-NLS-1$ - "Item", - "A value to use in this @theme@. It can be a standard string, a hex color value, or a reference to any other resource type.", - "http://code.google.com/android/reference/available-resources.html#stylesandthemes", //$NON-NLS-1$ - new AttributeDescriptor[] { - new TextAttributeDescriptor(NAME_ATTR, - "Name*", - null /* nsUri */, - "The mandatory name used in referring to this item."), - new TextValueDescriptor( - "Value*", - "A mandatory standard string, hex color value, or reference to any other resource type.") - }, - null, // no child nodes - false /* not mandatory */) - }, - false /* not mandatory */); - - RESOURCES_ELEMENT = new ElementDescriptor( - ROOT_ELEMENT, - "Resources", - null, - "http://code.google.com/android/reference/available-resources.html", //$NON-NLS-1$ - null, // no attributes - new ElementDescriptor[] { - string_element, - color_element, - dimen_element, - drawable_element, - style_element, - item_element - }, - true /* mandatory */); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java deleted file mode 100644 index cc7bec8..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/explorer/ResourceExplorerView.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.explorer; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.resources.manager.ProjectResourceItem; -import com.android.ide.eclipse.editors.resources.manager.ProjectResources; -import com.android.ide.eclipse.editors.resources.manager.ResourceFile; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IResourceEventListener; -import com.android.ide.eclipse.editors.wizards.ResourceContentProvider; -import com.android.ide.eclipse.editors.wizards.ResourceLabelProvider; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.jdt.core.IJavaElement; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.ControlListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeColumn; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.ISelectionListener; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.ide.IDE; -import org.eclipse.ui.part.ViewPart; - -import java.util.Iterator; - -/** - * Resource Explorer View. - *

              - * This contains a basic Tree view, and uses a TreeViewer to handle the data. - *

              - * The view listener to change in selection in the workbench, and update to show the resource - * of the project of the current selected item (either item in the package explorer, or of the - * current editor). - * - * @see ResourceContentProvider - */ -public class ResourceExplorerView extends ViewPart implements ISelectionListener, - IResourceEventListener { - - private final static String PREFS_COLUMN_RES = - AndroidConstants.EDITORS_PLUGIN_ID + "ResourceExplorer.Col1"; //$NON-NLS-1$ - private final static String PREFS_COLUMN_2 = - AndroidConstants.EDITORS_PLUGIN_ID + "ResourceExplorer.Col2"; //$NON-NLS-1$ - - private Tree mTree; - private TreeViewer mTreeViewer; - - private IProject mCurrentProject; - - public ResourceExplorerView() { - } - - @Override - public void createPartControl(Composite parent) { - mTree = new Tree(parent, SWT.SINGLE | SWT.VIRTUAL); - mTree.setLayoutData(new GridData(GridData.FILL_BOTH)); - mTree.setHeaderVisible(true); - mTree.setLinesVisible(true); - - final IPreferenceStore store = EditorsPlugin.getDefault().getPreferenceStore(); - - // create 2 columns. The main one with the resources, and an "info" column. - createTreeColumn(mTree, "Resources", SWT.LEFT, - "abcdefghijklmnopqrstuvwxz", -1, PREFS_COLUMN_RES, store); //$NON-NLS-1$ - createTreeColumn(mTree, "Info", SWT.LEFT, - "0123456789", -1, PREFS_COLUMN_2, store); //$NON-NLS-1$ - - // create the jface wrapper - mTreeViewer = new TreeViewer(mTree); - - mTreeViewer.setContentProvider(new ResourceContentProvider(true /* fullLevels */)); - mTreeViewer.setLabelProvider(new ResourceLabelProvider()); - - // listen to selection change in the workbench. - IWorkbenchPage page = getSite().getPage(); - - page.addSelectionListener(this); - - // init with current selection - selectionChanged(getSite().getPart(), page.getSelection()); - - // add support for double click. - mTreeViewer.addDoubleClickListener(new IDoubleClickListener() { - public void doubleClick(DoubleClickEvent event) { - ISelection sel = event.getSelection(); - - if (sel instanceof IStructuredSelection) { - IStructuredSelection selection = (IStructuredSelection) sel; - - if (selection.size() == 1) { - Object element = selection.getFirstElement(); - - // if it's a resourceFile, we directly open it. - if (element instanceof ResourceFile) { - try { - IDE.openEditor(getSite().getWorkbenchWindow().getActivePage(), - ((ResourceFile)element).getFile().getIFile()); - } catch (PartInitException e) { - } - } else if (element instanceof ProjectResourceItem) { - // if it's a ResourceItem, we open the first file, but only if - // there's no alternate files. - ProjectResourceItem item = (ProjectResourceItem)element; - - if (item.isEditableDirectly()) { - ResourceFile[] files = item.getSourceFileArray(); - if (files[0] != null) { - try { - IDE.openEditor( - getSite().getWorkbenchWindow().getActivePage(), - files[0].getFile().getIFile()); - } catch (PartInitException e) { - } - } - } - } - } - } - } - }); - - // set up the resource manager to send us resource change notification - EditorsPlugin.getDefault().getResourceMonitor().addResourceEventListener(this); - } - - @Override - public void setFocus() { - mTree.setFocus(); - } - - /** - * Processes a new selection. - */ - public void selectionChanged(IWorkbenchPart part, ISelection selection) { - // first we test if the part is an editor. - if (part instanceof IEditorPart) { - // if it is, we check if it's a file editor. - IEditorInput input = ((IEditorPart)part).getEditorInput(); - - if (input instanceof IFileEditorInput) { - // from the file editor we can get the IFile object, and from it, the IProject. - IFile file = ((IFileEditorInput)input).getFile(); - - // get the file project - IProject project = file.getProject(); - - handleProjectSelection(project); - } - } else if (selection instanceof IStructuredSelection) { - // if it's not an editor, we look for structured selection. - for (Iterator it = ((IStructuredSelection) selection).iterator(); - it.hasNext();) { - Object element = it.next(); - IProject project = null; - - // if we are in the navigator or package explorer, the selection could contain a - // IResource object. - if (element instanceof IResource) { - project = ((IResource) element).getProject(); - } else if (element instanceof IJavaElement) { - // if we are in the package explorer on a java element, we handle that too. - IJavaElement javaElement = (IJavaElement)element; - IJavaProject javaProject = javaElement.getJavaProject(); - if (javaProject != null) { - project = javaProject.getProject(); - } - } else if (element instanceof IAdaptable) { - // finally we try to get a project object from IAdaptable. - project = (IProject) ((IAdaptable) element) - .getAdapter(IProject.class); - } - - // if we found a project, handle it, and return. - if (project != null) { - if (handleProjectSelection(project)) { - return; - } - } - } - } - } - - /** - * Handles a project selection. - * @param project the new selected project - * @return true if the project could be processed. - */ - private boolean handleProjectSelection(IProject project) { - try { - // if it's an android project, then we get its resources, and feed them - // to the tree viewer. - if (project.hasNature(AndroidConstants.NATURE)) { - if (mCurrentProject != project) { - ProjectResources projRes = ResourceManager.getInstance().getProjectResources( - project); - if (projRes != null) { - mTreeViewer.setInput(projRes); - mCurrentProject = project; - return true; - } - } - } - } catch (CoreException e) { - } - - return false; - } - - /** - * Create a TreeColumn with the specified parameters. If a - * PreferenceStore object and a preference entry name String - * object are provided then the column will listen to change in its width - * and update the preference store accordingly. - * - * @param parent The Table parent object - * @param header The header string - * @param style The column style - * @param sample_text A sample text to figure out column width if preference - * value is missing - * @param fixedSize a fixed size. If != -1 the column is non resizable - * @param pref_name The preference entry name for column width - * @param prefs The preference store - */ - public void createTreeColumn(Tree parent, String header, int style, - String sample_text, int fixedSize, final String pref_name, - final IPreferenceStore prefs) { - - // create the column - TreeColumn col = new TreeColumn(parent, style); - - if (fixedSize != -1) { - col.setWidth(fixedSize); - col.setResizable(false); - } else { - // if there is no pref store or the entry is missing, we use the sample - // text and pack the column. - // Otherwise we just read the width from the prefs and apply it. - if (prefs == null || prefs.contains(pref_name) == false) { - col.setText(sample_text); - col.pack(); - - // init the prefs store with the current value - if (prefs != null) { - prefs.setValue(pref_name, col.getWidth()); - } - } else { - col.setWidth(prefs.getInt(pref_name)); - } - - // if there is a pref store and a pref entry name, then we setup a - // listener to catch column resize to put the new width value into the store. - if (prefs != null && pref_name != null) { - col.addControlListener(new ControlListener() { - public void controlMoved(ControlEvent e) { - } - - public void controlResized(ControlEvent e) { - // get the new width - int w = ((TreeColumn)e.widget).getWidth(); - - // store in pref store - prefs.setValue(pref_name, w); - } - }); - } - } - - // set the header - col.setText(header); - } - - /** - * Processes a start in a resource event change. - */ - public void resourceChangeEventStart() { - // pass - } - - /** - * Processes the end of a resource change event. - */ - public void resourceChangeEventEnd() { - try { - mTree.getDisplay().asyncExec(new Runnable() { - public void run() { - if (mTree.isDisposed() == false) { - mTreeViewer.refresh(); - } - } - }); - } catch (SWTException e) { - // display is disposed. nothing to do. - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java deleted file mode 100644 index 366f4fd..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/CompiledResourcesMonitor.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.AndroidManifestHelper; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.runtime.CoreException; - -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.Map; - -/** - * A monitor for the compiled resources. This only monitors changes in the resources of type - * {@link ResourceType#ID}. - */ -public final class CompiledResourcesMonitor implements IFileListener, IProjectListener { - - private final static CompiledResourcesMonitor sThis = new CompiledResourcesMonitor(); - - /** - * Sets up the monitoring system. - * @param monitor The main Resource Monitor. - */ - public static void setupMonitor(ResourceMonitor monitor) { - monitor.addFileListener(sThis, IResourceDelta.ADDED | IResourceDelta.CHANGED); - monitor.addProjectListener(sThis); - } - - /** - * private constructor to prevent construction. - */ - private CompiledResourcesMonitor() { - } - - - /* (non-Javadoc) - * Sent when a file changed : if the file is the R class, then it is parsed again to update - * the internal data. - * - * @param file The file that changed. - * @param markerDeltas The marker deltas for the file. - * @param kind The change kind. This is equivalent to - * {@link IResourceDelta#accept(IResourceDeltaVisitor)} - * - * @see IFileListener#fileChanged - */ - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - if (file.getName().equals(AndroidConstants.FN_COMPILED_RESOURCE_CLASS)) { - loadAndParseRClass(file.getProject()); - } - } - - /** - * Processes project close event. - */ - public void projectClosed(IProject project) { - // the ProjectResources object will be removed by the ResourceManager. - } - - /** - * Processes project delete event. - */ - public void projectDeleted(IProject project) { - // the ProjectResources object will be removed by the ResourceManager. - } - - /** - * Processes project open event. - */ - public void projectOpened(IProject project) { - // when the project is opened, we get an ADDED event for each file, so we don't - // need to do anything here. - } - - /** - * Processes existing project at init time. - */ - public void projectOpenedWithWorkspace(IProject project) { - try { - // check this is an android project - if (project.hasNature(AndroidConstants.NATURE)) { - loadAndParseRClass(project); - } - } catch (CoreException e) { - // pass - } - } - - private void loadAndParseRClass(IProject project) { - try { - // first check there's a ProjectResources to store the content - ProjectResources projectResources = ResourceManager.getInstance().getProjectResources( - project); - - if (projectResources != null) { - // create the classname - String className = getRClassName(project); - - // create a temporary class loader to load it. - ProjectClassLoader loader = new ProjectClassLoader(null /* parentClassLoader */, - project); - - Class clazz = loader.loadClass(className); - - if (clazz != null) { - // create the maps to store the result of the parsing - Map> resourceValueMap = - new HashMap>(); - Map genericValueToNameMap = - new HashMap(); - Map styleableValueToNameMap = - new HashMap(); - - // parse the class - if (parseClass(clazz, genericValueToNameMap, styleableValueToNameMap, - resourceValueMap)) { - // now we associate the maps to the project. - projectResources.setCompiledResources(genericValueToNameMap, - styleableValueToNameMap, resourceValueMap); - } - } - } - } catch (ClassNotFoundException e) { - // pass - } - } - - /** - * Parses a R class, and fills maps. - * @param rClass the class to parse - * @param genericValueToNameMap - * @param styleableValueToNameMap - * @param resourceValueMap - * @return - */ - private boolean parseClass(Class rClass, Map genericValueToNameMap, - Map styleableValueToNameMap, Map> resourceValueMap) { - try { - for (Class inner : rClass.getDeclaredClasses()) { - String resType = inner.getSimpleName(); - - Map fullMap = new HashMap(); - resourceValueMap.put(resType, fullMap); - - for (Field f : inner.getDeclaredFields()) { - // only process static final fields. - int modifiers = f.getModifiers(); - if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) { - Class type = f.getType(); - if (type.isArray() && type.getComponentType() == int.class) { - // if the object is an int[] we put it in the styleable map - styleableValueToNameMap.put(new IntArrayWrapper((int[]) f.get(null)), - f.getName()); - } else if (type == int.class) { - Integer value = (Integer) f.get(null); - genericValueToNameMap.put(value, new String[] { f.getName(), resType }); - fullMap.put(f.getName(), value); - } else { - assert false; - } - } - } - } - - return true; - } catch (IllegalArgumentException e) { - } catch (IllegalAccessException e) { - } - return false; - } - - private String getRClassName(IProject project) { - // create the classname - AndroidManifestHelper manifest = new AndroidManifestHelper(project); - String javaPackage = manifest.getPackageName(); - - return javaPackage + ".R"; //$NON-NLS-1$ - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java deleted file mode 100644 index 57c17fc..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ConfigurableResourceItem.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -/** - * Represents a resource item that can exist in multiple "alternate" versions. - */ -public class ConfigurableResourceItem extends ProjectResourceItem { - - /** - * Constructs a new Resource Item. - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ConfigurableResourceItem(String name) { - super(name); - } - - /** - * Returns if the resource item has at least one non-default configuration. - */ - public boolean hasAlternates() { - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault() == false) { - return true; - } - } - - return false; - } - - /** - * Returns whether the resource has a default version, with no qualifier. - */ - public boolean hasDefault() { - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault()) { - return true; - } - } - - // We only want to return false if there's no default and more than 0 items. - return (mFiles.size() == 0); - } - - /** - * Returns the number of alternate versions of this resource. - */ - public int getAlternateCount() { - int count = 0; - for (ResourceFile file : mFiles) { - if (file.getFolder().getConfiguration().isDefault() == false) { - count++; - } - } - - return count; - } - - /* - * (non-Javadoc) - * Returns whether the item can be edited directly (ie it does not have alternate versions). - */ - @Override - public boolean isEditableDirectly() { - return hasAlternates() == false; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java deleted file mode 100644 index a9f80bd..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/FolderTypeRelationship.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceType; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Set; - -/** - * This class gives access to the bi directional relationship between {@link ResourceType} and - * {@link ResourceFolderType}. - */ -public final class FolderTypeRelationship { - - private final static HashMap mTypeToFolderMap = - new HashMap(); - - private final static HashMap mFolderToTypeMap = - new HashMap(); - - // generate the relationships. - static { - HashMap> typeToFolderMap = - new HashMap>(); - - HashMap> folderToTypeMap = - new HashMap>(); - - add(ResourceType.ANIM, ResourceFolderType.ANIM, typeToFolderMap, folderToTypeMap); - add(ResourceType.ARRAY, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.COLOR, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.COLOR, ResourceFolderType.COLOR, typeToFolderMap, folderToTypeMap); - add(ResourceType.DIMEN, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.DRAWABLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.DRAWABLE, ResourceFolderType.DRAWABLE, typeToFolderMap, folderToTypeMap); - add(ResourceType.ID, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.LAYOUT, ResourceFolderType.LAYOUT, typeToFolderMap, folderToTypeMap); - add(ResourceType.MENU, ResourceFolderType.MENU, typeToFolderMap, folderToTypeMap); - add(ResourceType.RAW, ResourceFolderType.RAW, typeToFolderMap, folderToTypeMap); - add(ResourceType.STRING, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.STYLE, ResourceFolderType.VALUES, typeToFolderMap, folderToTypeMap); - add(ResourceType.XML, ResourceFolderType.XML, typeToFolderMap, folderToTypeMap); - - optimize(typeToFolderMap, folderToTypeMap); - } - - /** - * Returns a list of {@link ResourceType}s that can be generated from files inside a folder - * of the specified type. - * @param folderType The folder type. - * @return an array of {@link ResourceType} - */ - public static ResourceType[] getRelatedResourceTypes(ResourceFolderType folderType) { - ResourceType[] array = mFolderToTypeMap.get(folderType); - if (array != null) { - return array; - } - return new ResourceType[0]; - } - - /** - * Returns a list of {@link ResourceFolderType} that can contain files generating resources - * of the specified type. - * @param resType the type of resource. - * @return an array of {@link ResourceFolderType} - */ - public static ResourceFolderType[] getRelatedFolders(ResourceType resType) { - ResourceFolderType[] array = mTypeToFolderMap.get(resType); - if (array != null) { - return array; - } - return new ResourceFolderType[0]; - } - - /** - * Returns true if the {@link ResourceType} and the {@link ResourceFolderType} values match. - * @param resType the resource type. - * @param folderType the folder type. - * @return true if files inside the folder of the specified {@link ResourceFolderType} - * could generate a resource of the specified {@link ResourceType} - */ - public static boolean match(ResourceType resType, ResourceFolderType folderType) { - ResourceFolderType[] array = mTypeToFolderMap.get(resType); - - if (array != null && array.length > 0) { - for (ResourceFolderType fType : array) { - if (fType == folderType) { - return true; - } - } - } - - return false; - } - - /** - * Adds a {@link ResourceType} - {@link ResourceFolderType} relationship. this indicates that - * a file in the folder can generate a resource of the specified type. - * @param type The resourceType - * @param folder The {@link ResourceFolderType} - * @param folderToTypeMap - * @param typeToFolderMap - */ - private static void add(ResourceType type, ResourceFolderType folder, - HashMap> typeToFolderMap, - HashMap> folderToTypeMap) { - // first we add the folder to the list associated with the type. - List folderList = typeToFolderMap.get(type); - if (folderList == null) { - folderList = new ArrayList(); - typeToFolderMap.put(type, folderList); - } - if (folderList.indexOf(folder) == -1) { - folderList.add(folder); - } - - // now we add the type to the list associated with the folder. - List typeList = folderToTypeMap.get(folder); - if (typeList == null) { - typeList = new ArrayList(); - folderToTypeMap.put(folder, typeList); - } - if (typeList.indexOf(type) == -1) { - typeList.add(type); - } - } - - /** - * Optimize the map to contains array instead of lists (since the api returns arrays) - * @param typeToFolderMap - * @param folderToTypeMap - */ - private static void optimize(HashMap> typeToFolderMap, - HashMap> folderToTypeMap) { - Set types = typeToFolderMap.keySet(); - for (ResourceType type : types) { - List list = typeToFolderMap.get(type); - mTypeToFolderMap.put(type, list.toArray(new ResourceFolderType[list.size()])); - } - - Set folders = folderToTypeMap.keySet(); - for (ResourceFolderType folder : folders) { - List list = folderToTypeMap.get(folder); - mFolderToTypeMap.put(folder, list.toArray(new ResourceType[list.size()])); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java deleted file mode 100644 index 552aec9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IdResourceItem.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.IIdResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; - -/** - * Represents a resource item of type {@link ResourceType#ID} - */ -public class IdResourceItem extends ProjectResourceItem implements IIdResourceItem { - - private final boolean mIsDeclaredInline; - - /** - * Constructs a new ResourceItem. - * @param name the name of the resource as it appears in the XML and R.java files. - * @param isDeclaredInline Whether this id was declared inline. - */ - IdResourceItem(String name, boolean isDeclaredInline) { - super(name); - mIsDeclaredInline = isDeclaredInline; - } - - /* - * (non-Javadoc) - * Returns whether the ID resource has been declared inline inside another resource XML file. - */ - public boolean isDeclaredInline() { - return mIsDeclaredInline; - } - - /* (non-Javadoc) - * Returns whether the item can be edited (ie, the id was not declared inline). - */ - @Override - public boolean isEditableDirectly() { - return !mIsDeclaredInline; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java deleted file mode 100644 index 25eb112..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/IntArrayWrapper.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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.editors.resources.manager; - -import java.util.Arrays; - - -/** - * Wrapper around a int[] to provide hashCode/equals support. - */ -public final class IntArrayWrapper { - - private int[] mData; - - public IntArrayWrapper(int[] data) { - mData = data; - } - - public void set(int[] data) { - mData = data; - } - - @Override - public int hashCode() { - return Arrays.hashCode(mData); - } - - @Override - public boolean equals(Object obj) { - if (getClass().equals(obj.getClass())) { - return Arrays.equals(mData, ((IntArrayWrapper)obj).mData); - } - - return super.equals(obj); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java deleted file mode 100644 index 72438a6..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/MultiResourceFile.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.utils.ResourceValue; -import com.android.layoutlib.utils.ValueResourceParser; -import com.android.layoutlib.utils.ValueResourceParser.IValueResourceRepository; - -import org.eclipse.core.runtime.CoreException; -import org.xml.sax.SAXException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Set; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Represents a resource file able to declare multiple resources, which could be of - * different {@link ResourceType}. - *

              - * This is typically an XML file inside res/values. - */ -public final class MultiResourceFile extends ResourceFile implements IValueResourceRepository { - - private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); - - private final HashMap> mResourceItems = - new HashMap>(); - - public MultiResourceFile(IAbstractFile file, ResourceFolder folder) { - super(file, folder); - } - - @Override - public ResourceType[] getResourceTypes() { - update(); - - Set keys = mResourceItems.keySet(); - - return keys.toArray(new ResourceType[keys.size()]); - } - - @Override - public boolean hasResources(ResourceType type) { - update(); - - HashMap list = mResourceItems.get(type); - return (list != null && list.size() > 0); - } - - @Override - public Collection getResources(ResourceType type, - ProjectResources projectResources) { - update(); - - HashMap list = mResourceItems.get(type); - - ArrayList items = new ArrayList(); - - if (list != null) { - Collection values = list.values(); - for (ResourceValue res : values) { - ProjectResourceItem item = projectResources.findResourceItem(type, res.getName()); - - if (item == null) { - if (type == ResourceType.ID) { - item = new IdResourceItem(res.getName(), false /* isDeclaredInline */); - } else { - item = new ConfigurableResourceItem(res.getName()); - } - items.add(item); - } - - item.add(this); - } - } - - return items; - } - - /** - * Updates the Resource items if necessary. - */ - private void update() { - if (isTouched() == true) { - // reset current content. - mResourceItems.clear(); - - // need to parse the file and find the content. - parseFile(); - - resetTouch(); - } - } - - /** - * Parses the file and creates a list of {@link ResourceType}. - */ - private void parseFile() { - try { - SAXParser parser = sParserFactory.newSAXParser(); - parser.parse(getFile().getContents(), new ValueResourceParser(this, isFramework())); - } catch (ParserConfigurationException e) { - } catch (SAXException e) { - } catch (IOException e) { - } catch (CoreException e) { - } - } - - /** - * Adds a resource item to the list - * @param resType The type of the resource - * @param name The name of the resource. - */ - public void addResourceValue(String resType, ResourceValue value) { - ResourceType type = ResourceType.getEnum(resType); - if (type != null) { - HashMap list = mResourceItems.get(type); - - // if the list does not exist, create it. - if (list == null) { - list = new HashMap(); - mResourceItems.put(type, list); - } else { - // look for a possible value already existing. - ResourceValue oldValue = list.get(value.getName()); - - if (oldValue != null) { - oldValue.replaceWith(value); - return; - } - } - - // empty list or no match found? add the given resource - list.put(value.getName(), value); - } - } - - @Override - public IResourceValue getValue(ResourceType type, String name) { - update(); - - // get the list for the given type - HashMap list = mResourceItems.get(type); - - if (list != null) { - return list.get(name); - } - - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java deleted file mode 100644 index 5658224..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectClassLoader.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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.editors.resources.manager; - -import com.android.ide.eclipse.common.AndroidConstants; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.IPath; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.ArrayList; - -/** - * ClassLoader able to load class from output of an Eclipse project. - */ -public final class ProjectClassLoader extends ClassLoader { - - private final IJavaProject mJavaProject; - private URLClassLoader mJarClassLoader; - private boolean mInsideJarClassLoader = false; - - public ProjectClassLoader(ClassLoader parentClassLoader, IProject project) { - super(parentClassLoader); - mJavaProject = JavaCore.create(project); - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - try { - // get the project output folder. - IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); - IPath outputLocation = mJavaProject.getOutputLocation(); - IResource outRes = root.findMember(outputLocation); - if (outRes == null) { - throw new ClassNotFoundException(name); - } - - File outFolder = new File(outRes.getLocation().toOSString()); - - // get the class name segments - String[] segments = name.split("\\."); //$NON-NLS-1$ - - File classFile = getFile(outFolder, segments, 0); - if (classFile == null) { - if (mInsideJarClassLoader == false) { - // if no file matching the class name was found, look in the 3rd party jars - return loadClassFromJar(name); - } else { - throw new ClassNotFoundException(name); - } - } - - // load the content of the file and create the class. - FileInputStream fis = new FileInputStream(classFile); - byte[] data = new byte[(int)classFile.length()]; - int read = 0; - try { - read = fis.read(data); - } catch (IOException e) { - data = null; - } - fis.close(); - - if (data != null) { - Class clazz = defineClass(null, data, 0, read); - if (clazz != null) { - return clazz; - } - } - } catch (Exception e) { - throw new ClassNotFoundException(e.getMessage()); - } - - throw new ClassNotFoundException(name); - } - - /** - * Returns the File matching the a certain path from a root {@link File}. - *

              The methods checks that the file ends in .class even though the last segment - * does not. - * @param parent the root of the file. - * @param segments the segments containing the path of the file - * @param index the offset at which to start looking into segments. - * @return - * @throws FileNotFoundException - */ - private File getFile(File parent, String[] segments, int index) - throws FileNotFoundException { - // reached the end with no match? - if (index == segments.length) { - throw new FileNotFoundException(); - } - - String toMatch = segments[index]; - File[] files = parent.listFiles(); - - // we're at the last segments. we look for a matching .class - if (index == segments.length - 1) { - toMatch = toMatch + ".class"; - - if (files != null) { - for (File file : files) { - if (file.isFile() && file.getName().equals(toMatch)) { - return file; - } - } - } - - // no match? abort. - throw new FileNotFoundException(); - } - - String innerClassName = null; - - if (files != null) { - for (File file : files) { - if (file.isDirectory()) { - if (toMatch.equals(file.getName())) { - return getFile(file, segments, index+1); - } - } else if (file.getName().startsWith(toMatch)) { - if (innerClassName == null) { - StringBuilder sb = new StringBuilder(segments[index]); - for (int i = index + 1 ; i < segments.length ; i++) { - sb.append('$'); - sb.append(segments[i]); - } - sb.append(".class"); - - innerClassName = sb.toString(); - } - - if (file.getName().equals(innerClassName)) { - return file; - } - } - } - } - - return null; - } - - /** - * Loads a class from the 3rd party jar present in the project - * @param name - * @return - * @throws ClassNotFoundException - */ - private Class loadClassFromJar(String name) throws ClassNotFoundException { - if (mJarClassLoader == null) { - // get the OS path to all the external jars - URL[] jars = getExternalJars(); - - mJarClassLoader = new URLClassLoader(jars, this /* parent */); - } - - try { - // because a class loader always look in its parent loader first, we need to know - // that we are querying the jar classloader. This will let us know to not query - // it again for classes we don't find, or this would create an infinite loop. - mInsideJarClassLoader = true; - return mJarClassLoader.loadClass(name); - } finally { - mInsideJarClassLoader = false; - } - } - - /** - * Returns an array of external jar files used by the project. - * @return an array of OS-specific absolute file paths - */ - private final URL[] getExternalJars() { - // get a java project from it - IJavaProject javaProject = JavaCore.create(mJavaProject.getProject()); - - IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); - - ArrayList oslibraryList = new ArrayList(); - IClasspathEntry[] classpaths = javaProject.readRawClasspath(); - if (classpaths != null) { - for (IClasspathEntry e : classpaths) { - if (e.getEntryKind() == IClasspathEntry.CPE_LIBRARY || - e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - // if this is a classpath variable reference, we resolve it. - if (e.getEntryKind() == IClasspathEntry.CPE_VARIABLE) { - e = JavaCore.getResolvedClasspathEntry(e); - } - - // get the IPath - IPath path = e.getPath(); - - // get the file name. if it's the framework jar, we ignore that file. - // since we now use classpath container, this is here for legacy purpose only. - if (AndroidConstants.FN_FRAMEWORK_LIBRARY.equals(path.lastSegment())) { - continue; - } - - // check the name ends with .jar - if (AndroidConstants.EXT_JAR.equalsIgnoreCase(path.getFileExtension())) { - boolean local = false; - IResource resource = wsRoot.findMember(path); - if (resource != null && resource.exists() && - resource.getType() == IResource.FILE) { - local = true; - try { - oslibraryList.add( - new File(resource.getLocation().toOSString()).toURL()); - } catch (MalformedURLException mue) { - // pass - } - } - - if (local == false) { - // if the jar path doesn't match a workspace resource, - // then we get an OSString and check if this links to a valid file. - String osFullPath = path.toOSString(); - - File f = new File(osFullPath); - if (f.exists()) { - try { - oslibraryList.add(f.toURL()); - } catch (MalformedURLException mue) { - // pass - } - } - } - } - } - } - } - - return oslibraryList.toArray(new URL[oslibraryList.size()]); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java deleted file mode 100644 index ba770b2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResourceItem.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.android.ide.eclipse.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Base class for Resource Item coming from an Android Project. - */ -public abstract class ProjectResourceItem extends ResourceItem { - - private final static Comparator sComparator = new Comparator() { - public int compare(ResourceFile file1, ResourceFile file2) { - // get both FolderConfiguration and compare them - FolderConfiguration fc1 = file1.getFolder().getConfiguration(); - FolderConfiguration fc2 = file2.getFolder().getConfiguration(); - - return fc1.compareTo(fc2); - } - }; - - /** - * List of files generating this ResourceItem. - */ - protected final ArrayList mFiles = new ArrayList(); - - /** - * Constructs a new ResourceItem. - * @param name the name of the resource as it appears in the XML and R.java files. - */ - public ProjectResourceItem(String name) { - super(name); - } - - /** - * Returns whether the resource item is editable directly. - *

              - * This is typically the case for resources that don't have alternate versions, or resources - * of type {@link ResourceType#ID} that aren't declared inline. - */ - public abstract boolean isEditableDirectly(); - - /** - * Adds a new version of this resource item, by adding its {@link ResourceFile}. - * @param file the {@link ResourceFile} object. - */ - protected void add(ResourceFile file) { - mFiles.add(file); - } - - /** - * Reset the item by emptying its version list. - */ - protected void reset() { - mFiles.clear(); - } - - /** - * Returns the sorted list of {@link ResourceItem} objects for this resource item. - */ - public ResourceFile[] getSourceFileArray() { - ArrayList list = new ArrayList(); - list.addAll(mFiles); - - Collections.sort(list, sComparator); - - return list.toArray(new ResourceFile[list.size()]); - } - - /** - * Returns the list of {@link ResourceItem} objects for this resource item. - */ - public List getSourceFileList() { - return Collections.unmodifiableList(mFiles); - } - - - /** - * Replaces the content of the receiver with the ResourceItem received as parameter. - * @param item - */ - protected void replaceWith(ProjectResourceItem item) { - mFiles.clear(); - mFiles.addAll(item.mFiles); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java deleted file mode 100644 index b0881fa..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ProjectResources.java +++ /dev/null @@ -1,810 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.utils.ResourceValue; - -import org.eclipse.core.resources.IFolder; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Represents the resources of a project. This is a file view of the resources, with handling - * for the alternate resource types. For a compiled view use CompiledResources. - */ -public class ProjectResources implements IResourceRepository { - private final HashMap> mFolderMap = - new HashMap>(); - - private final HashMap> mResourceMap = - new HashMap>(); - - /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */ - private Map> mResourceValueMap; - /** Map of (id, [name, resType]) for all resources coming from R.java */ - private Map mResIdValueToNameMap; - /** Map of (int[], name) for styleable resources coming from R.java */ - private Map mStyleableValueToNameMap; - - /** Cached list of {@link IdResourceItem}. This is mix of IdResourceItem created by - * {@link MultiResourceFile} for ids coming from XML files under res/values and - * {@link IdResourceItem} created manually, from the list coming from R.java */ - private final ArrayList mIdResourceList = new ArrayList(); - - private final boolean mIsFrameworkRepository; - - private final IntArrayWrapper mWrapper = new IntArrayWrapper(null); - - public ProjectResources(boolean isFrameworkRepository) { - mIsFrameworkRepository = isFrameworkRepository; - } - - public boolean isSystemRepository() { - return mIsFrameworkRepository; - } - - /** - * Adds a Folder Configuration to the project. - * @param type The resource type. - * @param config The resource configuration. - * @param folder The workspace folder object. - * @return the {@link ResourceFolder} object associated to this folder. - */ - protected ResourceFolder add(ResourceFolderType type, FolderConfiguration config, - IAbstractFolder folder) { - // get the list for the resource type - List list = mFolderMap.get(type); - - if (list == null) { - list = new ArrayList(); - - ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); - list.add(cf); - - mFolderMap.put(type, list); - - return cf; - } - - // look for an already existing folder configuration. - for (ResourceFolder cFolder : list) { - if (cFolder.mConfiguration.equals(config)) { - // config already exist. Nothing to be done really, besides making sure - // the IFolder object is up to date. - cFolder.mFolder = folder; - return cFolder; - } - } - - // If we arrive here, this means we didn't find a matching configuration. - // So we add one. - ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository); - list.add(cf); - - return cf; - } - - /** - * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}. - * @param type The type of the folder - * @param folder the IFolder object. - */ - protected void removeFolder(ResourceFolderType type, IFolder folder) { - // get the list of folders for the resource type. - List list = mFolderMap.get(type); - - if (list != null) { - int count = list.size(); - for (int i = 0 ; i < count ; i++) { - ResourceFolder resFolder = list.get(i); - if (resFolder.getFolder().getIFolder().equals(folder)) { - // we found the matching ResourceFolder. we need to remove it. - list.remove(i); - - // we now need to invalidate this resource type. - // The easiest way is to touch one of the other folders of the same type. - if (list.size() > 0) { - list.get(0).touch(); - } else { - // if the list is now empty, and we have a single ResouceType out of this - // ResourceFolderType, then we are done. - // However, if another ResourceFolderType can generate similar ResourceType - // than this, we need to update those ResourceTypes as well. - // For instance, if the last "drawable-*" folder is deleted, we need to - // refresh the ResourceItem associated with ResourceType.DRAWABLE. - // Those can be found in ResourceFolderType.DRAWABLE but also in - // ResourceFolderType.VALUES. - // If we don't find a single folder to touch, then it's fine, as the top - // level items (the list of generated resource types) is not cached - // (for now) - - // get the lists of ResourceTypes generated by this ResourceFolderType - ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( - type); - - // for each of those, make sure to find one folder to touch so that the - // list of ResourceItem associated with the type is rebuilt. - for (ResourceType resType : resTypes) { - // get the list of folder that can generate this type - ResourceFolderType[] folderTypes = - FolderTypeRelationship.getRelatedFolders(resType); - - // we only need to touch one folder in any of those (since it's one - // folder per type, not per folder type). - for (ResourceFolderType folderType : folderTypes) { - List resFolders = mFolderMap.get(folderType); - - if (resFolders != null && resFolders.size() > 0) { - resFolders.get(0).touch(); - break; - } - } - } - } - - // we're done updating/touching, we can stop - break; - } - } - } - } - - - /** - * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}. - * @param type The {@link ResourceFolderType} - */ - public List getFolders(ResourceFolderType type) { - return mFolderMap.get(type); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getAvailableResourceTypes() - */ - public ResourceType[] getAvailableResourceTypes() { - ArrayList list = new ArrayList(); - - // For each key, we check if there's a single ResourceType match. - // If not, we look for the actual content to give us the resource type. - - for (ResourceFolderType folderType : mFolderMap.keySet()) { - ResourceType types[] = FolderTypeRelationship.getRelatedResourceTypes(folderType); - if (types.length == 1) { - // before we add it we check if it's not already present, since a ResourceType - // could be created from multiple folders, even for the folders that only create - // one type of resource (drawable for instance, can be created from drawable/ and - // values/) - if (list.indexOf(types[0]) == -1) { - list.add(types[0]); - } - } else { - // there isn't a single resource type out of this folder, so we look for all - // content. - List folders = mFolderMap.get(folderType); - if (folders != null) { - for (ResourceFolder folder : folders) { - Collection folderContent = folder.getResourceTypes(); - - // then we add them, but only if they aren't already in the list. - for (ResourceType folderResType : folderContent) { - if (list.indexOf(folderResType) == -1) { - list.add(folderResType); - } - } - } - } - } - } - - // in case ResourceType.ID haven't been added yet because there's no id defined - // in XML, we check on the list of compiled id resources. - if (list.indexOf(ResourceType.ID) == -1 && mResourceValueMap != null) { - Map map = mResourceValueMap.get(ResourceType.ID.getName()); - if (map != null && map.size() > 0) { - list.add(ResourceType.ID); - } - } - - // at this point the list is full of ResourceType defined in the files. - // We need to sort it. - Collections.sort(list); - - return list.toArray(new ResourceType[list.size()]); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getResources(com.android.ide.eclipse.common.resources.ResourceType) - */ - public ProjectResourceItem[] getResources(ResourceType type) { - checkAndUpdate(type); - - if (type == ResourceType.ID) { - synchronized (mIdResourceList) { - return mIdResourceList.toArray(new ProjectResourceItem[mIdResourceList.size()]); - } - } - - List items = mResourceMap.get(type); - - return items.toArray(new ProjectResourceItem[items.size()]); - } - - /* (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.IResourceRepository#hasResources(com.android.ide.eclipse.common.resources.ResourceType) - */ - public boolean hasResources(ResourceType type) { - checkAndUpdate(type); - - if (type == ResourceType.ID) { - synchronized (mIdResourceList) { - return mIdResourceList.size() > 0; - } - } - - List items = mResourceMap.get(type); - return (items != null && items.size() > 0); - } - - /** - * Returns the {@link ResourceFolder} associated with a {@link IFolder}. - * @param folder The {@link IFolder} object. - * @return the {@link ResourceFolder} or null if it was not found. - */ - public ResourceFolder getResourceFolder(IFolder folder) { - for (List list : mFolderMap.values()) { - for (ResourceFolder resFolder : list) { - if (resFolder.getFolder().getIFolder().equals(folder)) { - return resFolder; - } - } - } - - return null; - } - - /** - * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and - * configuration. - *

              This only works with files generating one resource named after the file (for instance, - * layouts, bitmap based drawable, xml, anims). - * @return the matching file or null if no match was found. - */ - public ResourceFile getMatchingFile(String name, ResourceFolderType type, - FolderConfiguration config) { - // get the folders for the given type - List folders = mFolderMap.get(type); - - // look for folders containing a file with the given name. - ArrayList matchingFolders = new ArrayList(); - - // remove the folders that do not have a file with the given name, or if their config - // is incompatible. - for (int i = 0 ; i < folders.size(); i++) { - ResourceFolder folder = folders.get(i); - - if (folder.hasFile(name) == true) { - matchingFolders.add(folder); - } - } - - // from those, get the folder with a config matching the given reference configuration. - Resource match = findMatchingConfiguredResource(matchingFolders, config); - - // do we have a matching folder? - if (match instanceof ResourceFolder) { - // get the ResourceFile from the filename - return ((ResourceFolder)match).getFile(name); - } - - return null; - } - - /** - * Returns the resources values matching a given {@link FolderConfiguration}. - * @param referenceConfig the configuration that each value must match. - */ - public Map> getConfiguredResources( - FolderConfiguration referenceConfig) { - - Map> map = - new HashMap>(); - - // special case for Id since there's a mix of compiled id (declared inline) and id declared - // in the XML files. - if (mIdResourceList.size() > 0) { - Map idMap = new HashMap(); - String idType = ResourceType.ID.getName(); - for (IdResourceItem id : mIdResourceList) { - // FIXME: cache the ResourceValue! - idMap.put(id.getName(), new ResourceValue(idType, id.getName(), - mIsFrameworkRepository)); - } - - map.put(ResourceType.ID.getName(), idMap); - } - - Set keys = mResourceMap.keySet(); - for (ResourceType key : keys) { - // we don't process ID resources since we already did it above. - if (key != ResourceType.ID) { - map.put(key.getName(), getConfiguredResource(key, referenceConfig)); - } - } - - return map; - } - - /** - * Loads all the resources. Essentially this forces to load the values from the - * {@link ResourceFile} objects to make sure they are up to date and loaded - * in {@link #mResourceMap}. - */ - public void loadAll() { - // gets all the resource types available. - ResourceType[] types = getAvailableResourceTypes(); - - // loop on them and load them - for (ResourceType type: types) { - checkAndUpdate(type); - } - } - - /** - * Resolves a compiled resource id into the resource name and type - * @param id - * @return an array of 2 strings { name, type } or null if the id could not be resolved - */ - public String[] resolveResourceValue(int id) { - if (mResIdValueToNameMap != null) { - return mResIdValueToNameMap.get(id); - } - - return null; - } - - /** - * Resolves a compiled resource id of type int[] into the resource name. - * @param id - * @return - */ - public String resolveResourceValue(int[] id) { - if (mStyleableValueToNameMap != null) { - mWrapper.set(id); - return mStyleableValueToNameMap.get(mWrapper); - } - - return null; - } - - /** - * Returns the value of a resource by its type and name. - * @param type - * @param name - * @return - */ - public Integer getResourceValue(String type, String name) { - if (mResourceValueMap != null) { - Map map = mResourceValueMap.get(type); - if (map != null) { - return map.get(name); - } - } - - return null; - } - - /** - * Returns the list of languages used in the resources. - */ - public Set getLanguages() { - Set set = new HashSet(); - - Collection> folderList = mFolderMap.values(); - for (List folderSubList : folderList) { - for (ResourceFolder folder : folderSubList) { - FolderConfiguration config = folder.getConfiguration(); - LanguageQualifier lang = config.getLanguageQualifier(); - if (lang != null) { - set.add(lang.getStringValue()); - } - } - } - - return set; - } - - /** - * Returns the list of regions used in the resources with the given language. - * @param currentLanguage the current language the region must be associated with - * @return - */ - public Set getRegions(String currentLanguage) { - Set set = new HashSet(); - - Collection> folderList = mFolderMap.values(); - for (List folderSubList : folderList) { - for (ResourceFolder folder : folderSubList) { - FolderConfiguration config = folder.getConfiguration(); - - // get the language - LanguageQualifier lang = config.getLanguageQualifier(); - if (lang != null && lang.getStringValue().equals(currentLanguage)) { - RegionQualifier region = config.getRegionQualifier(); - if (region != null) { - set.add(region.getStringValue()); - } - } - } - } - - return set; - } - - /** - * Returns a map of (resource name, resource value) for the given {@link ResourceType}. - *

              The values returned are taken from the resource files best matching a given - * {@link FolderConfiguration}. - * @param type the type of the resources. - * @param referenceConfig the configuration to best match. - */ - private Map getConfiguredResource(ResourceType type, - FolderConfiguration referenceConfig) { - // get the resource item for the given type - List items = mResourceMap.get(type); - - // create the map - HashMap map = new HashMap(); - - for (ProjectResourceItem item : items) { - // get the source files generating this resource - List list = item.getSourceFileList(); - - // look for the best match for the given configuration - Resource match = findMatchingConfiguredResource(list, referenceConfig); - - if (match instanceof ResourceFile) { - ResourceFile matchResFile = (ResourceFile)match; - - // get the value of this configured resource. - IResourceValue value = matchResFile.getValue(type, item.getName()); - - if (value != null) { - map.put(item.getName(), value); - } - } - } - - return map; - } - - /** - * Returns the best matching {@link Resource}. - * @param resources the list of {@link Resource} to choose from. - * @param referenceConfig the {@link FolderConfiguration} to match. - */ - private Resource findMatchingConfiguredResource(List resources, - FolderConfiguration referenceConfig) { - // look for resources with the maximum number of qualifier match. - int currentMax = -1; - ArrayList matchingResources = new ArrayList(); - for (int i = 0 ; i < resources.size(); i++) { - Resource res = resources.get(i); - - int count = res.getConfiguration().match(referenceConfig); - if (count > currentMax) { - matchingResources.clear(); - matchingResources.add(res); - currentMax = count; - } else if (count != -1 && count == currentMax) { - matchingResources.add(res); - } - } - - // if we have more than one match, we look for the match with the qualifiers with the - // highest priority. - Resource resMatch = null; - if (matchingResources.size() == 1) { - resMatch = matchingResources.get(0); - } else if (matchingResources.size() > 1) { - // More than one resource with the same number of qualifier match. - // We loop, looking for the resource with the highest priority qualifiers. - ArrayList tmpResources = new ArrayList(); - int startIndex = 0; - while (matchingResources.size() > 1) { - int highest = -1; - for (int i = 0 ; i < matchingResources.size() ; i++) { - Resource folder = matchingResources.get(i); - - // get highest priority qualifiers. - int m = folder.getConfiguration().getHighestPriorityQualifier(startIndex); - - // add to the list if highest. - if (m != -1) { - if (highest == -1 || m == highest) { - tmpResources.add(folder); - highest = m; - } else if (m < highest) { // highest priority == lowest index. - tmpResources.clear(); - tmpResources.add(folder); - } - } - } - - // at this point, we have a list with 1+ resources that all have the same highest - // priority qualifiers. Go through the list again looking for the next highest - // priority qualifier. - startIndex = highest + 1; - - // this should not happen, but it's better to check. - if (matchingResources.size() == tmpResources.size() && highest == -1) { - // this means all the resources match with the same qualifiers - // (highest == -1 means we reached the end of the qualifier list) - // In this case, we arbitrarily take the first resource. - matchingResources.clear(); - matchingResources.add(tmpResources.get(0)); - } else { - matchingResources.clear(); - matchingResources.addAll(tmpResources); - } - tmpResources.clear(); - } - - // we should have only one match here. - resMatch = matchingResources.get(0); - } - - return resMatch; - } - - /** - * Checks if the list of {@link ResourceItem}s for the specified {@link ResourceType} needs - * to be updated. - * @param type the Resource Type. - */ - private void checkAndUpdate(ResourceType type) { - // get the list of folder that can output this type - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - - for (ResourceFolderType folderType : folderTypes) { - List folders = mFolderMap.get(folderType); - - if (folders != null) { - for (ResourceFolder folder : folders) { - if (folder.isTouched()) { - // if this folder is touched we need to update all the types that can - // be generated from a file in this folder. - // This will include 'type' obviously. - ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes( - folderType); - for (ResourceType resType : resTypes) { - update(resType); - } - return; - } - } - } - } - } - - /** - * Updates the list of {@link ResourceItem} objects associated with a {@link ResourceType}. - * This will reset the touch status of all the folders that can generate this resource type. - * @param type the Resource Type. - */ - private void update(ResourceType type) { - // get the cache list, and lets make a backup - List items = mResourceMap.get(type); - List backup = new ArrayList(); - - if (items == null) { - items = new ArrayList(); - mResourceMap.put(type, items); - } else { - // backup the list - backup.addAll(items); - - // we reset the list itself. - items.clear(); - } - - // get the list of folder that can output this type - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - - for (ResourceFolderType folderType : folderTypes) { - List folders = mFolderMap.get(folderType); - - if (folders != null) { - for (ResourceFolder folder : folders) { - items.addAll(folder.getResources(type, this)); - folder.resetTouch(); - } - } - } - - // now items contains the new list. We "merge" it with the backup list. - // Basically, we need to keep the old instances of ResourceItem (where applicable), - // but replace them by the content of the new items. - // This will let the resource explorer keep the expanded state of the nodes whose data - // is a ResourceItem object. - if (backup.size() > 0) { - // this is not going to change as we're only replacing instances. - int count = items.size(); - - for (int i = 0 ; i < count;) { - // get the "new" item - ProjectResourceItem item = items.get(i); - - // look for a similar item in the old list. - ProjectResourceItem foundOldItem = null; - for (ProjectResourceItem oldItem : backup) { - if (oldItem.getName().equals(item.getName())) { - foundOldItem = oldItem; - break; - } - } - - if (foundOldItem != null) { - // erase the data of the old item with the data from the new one. - foundOldItem.replaceWith(item); - - // remove the old and new item from their respective lists - items.remove(i); - backup.remove(foundOldItem); - - // add the old item to the new list - items.add(foundOldItem); - } else { - // this is a new item, we skip to the next object - i++; - } - } - } - - // if this is the ResourceType.ID, we create the actual list, from this list and - // the compiled resource list. - if (type == ResourceType.ID) { - mergeIdResources(); - } else { - // else this is the list that will actually be displayed, so we sort it. - Collections.sort(items); - } - } - - /** - * Looks up an existing {@link ProjectResourceItem} by {@link ResourceType} and name. - * @param type the Resource Type. - * @param name the Resource name. - * @return the existing ResourceItem or null if no match was found. - */ - protected ProjectResourceItem findResourceItem(ResourceType type, String name) { - List list = mResourceMap.get(type); - - for (ProjectResourceItem item : list) { - if (name.equals(item.getName())) { - return item; - } - } - - return null; - } - - /** - * Sets compiled resource information. - * @param resIdValueToNameMap a map of compiled resource id to resource name. - * The map is acquired by the {@link ProjectResources} object. - * @param styleableValueMap - * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}. - * The list is acquired by the {@link ProjectResources} object. - */ - void setCompiledResources(Map resIdValueToNameMap, - Map styleableValueMap, - Map> resourceValueMap) { - mResourceValueMap = resourceValueMap; - mResIdValueToNameMap = resIdValueToNameMap; - mStyleableValueToNameMap = styleableValueMap; - mergeIdResources(); - } - - /** - * Merges the list of ID resource coming from R.java and the list of ID resources - * coming from XML declaration into the cached list {@link #mIdResourceList}. - */ - void mergeIdResources() { - // get the list of IDs coming from XML declaration. Those ids are present in - // mCompiledIdResources already, so we'll need to use those instead of creating - // new IdResourceItem - List xmlIdResources = mResourceMap.get(ResourceType.ID); - - synchronized (mIdResourceList) { - // copy the currently cached items. - ArrayList oldItems = new ArrayList(); - oldItems.addAll(mIdResourceList); - - // empty the current list - mIdResourceList.clear(); - - // get the list of compile id resources. - Map idMap = null; - if (mResourceValueMap != null) { - idMap = mResourceValueMap.get(ResourceType.ID.getName()); - } - - if (idMap == null) { - if (xmlIdResources != null) { - for (ProjectResourceItem resourceItem : xmlIdResources) { - // check the actual class just for safety. - if (resourceItem instanceof IdResourceItem) { - mIdResourceList.add((IdResourceItem)resourceItem); - } - } - } - } else { - // loop on the full list of id, and look for a match in the old list, - // in the list coming from XML (in case a new XML item was created.) - - Set idSet = idMap.keySet(); - - idLoop: for (String idResource : idSet) { - // first look in the XML list in case an id went from inline to XML declared. - if (xmlIdResources != null) { - for (ProjectResourceItem resourceItem : xmlIdResources) { - if (resourceItem instanceof IdResourceItem && - resourceItem.getName().equals(idResource)) { - mIdResourceList.add((IdResourceItem)resourceItem); - continue idLoop; - } - } - } - - // if we haven't found it, look in the old items. - int count = oldItems.size(); - for (int i = 0 ; i < count ; i++) { - IdResourceItem resourceItem = oldItems.get(i); - if (resourceItem.getName().equals(idResource)) { - oldItems.remove(i); - mIdResourceList.add(resourceItem); - continue idLoop; - } - } - - // if we haven't found it, it looks like it's a new id that was - // declared inline. - mIdResourceList.add(new IdResourceItem(idResource, - true /* isDeclaredInline */)); - } - } - - // now we sort the list - Collections.sort(mIdResourceList); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/Resource.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/Resource.java deleted file mode 100644 index dd8d080..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/Resource.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; - -/** - * Base class for file system resource items (Folders, Files). - */ -public abstract class Resource { - private boolean mTouched = true; - - /** - * Returns the {@link FolderConfiguration} for this object. - */ - public abstract FolderConfiguration getConfiguration(); - - /** - * Indicates that the underlying file was changed. - */ - public final void touch() { - mTouched = true; - } - - public final boolean isTouched() { - return mTouched; - } - - public final void resetTouch() { - mTouched = false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java deleted file mode 100644 index f927a9a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFile.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; -import com.android.layoutlib.api.IResourceValue; - -import java.util.Collection; - -/** - * Represents a Resource file (a file under $Project/res/) - */ -public abstract class ResourceFile extends Resource { - - private final IAbstractFile mFile; - private final ResourceFolder mFolder; - - protected ResourceFile(IAbstractFile file, ResourceFolder folder) { - mFile = file; - mFolder = folder; - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() - */ - @Override - public FolderConfiguration getConfiguration() { - return mFolder.getConfiguration(); - } - - /** - * Returns the IFile associated with the ResourceFile. - */ - public final IAbstractFile getFile() { - return mFile; - } - - /** - * Returns the parent folder as a {@link ResourceFolder}. - */ - public final ResourceFolder getFolder() { - return mFolder; - } - - /** - * Returns whether the resource is a framework resource. - */ - public final boolean isFramework() { - return mFolder.isFramework(); - } - - /** - * Returns the list of {@link ResourceType} generated by the file. - */ - public abstract ResourceType[] getResourceTypes(); - - /** - * Returns whether the file generated a resource of a specific type. - * @param type The {@link ResourceType} - */ - public abstract boolean hasResources(ResourceType type); - - /** - * Get the list of {@link ProjectResourceItem} of a specific type generated by the file. - * This method must make sure not to create duplicate. - * @param type The type of {@link ProjectResourceItem} to return. - * @param projectResources The global Project Resource object, allowing the implementation to - * query for already existing {@link ProjectResourceItem} - * @return The list of new {@link ProjectResourceItem} - * @see ProjectResources#findResourceItem(ResourceType, String) - */ - public abstract Collection getResources(ResourceType type, - ProjectResources projectResources); - - /** - * Returns the value of a resource generated by this file by {@link ResourceType} and name. - *

              If no resource match, null is returned. - * @param type the type of the resource. - * @param name the name of the resource. - */ - public abstract IResourceValue getValue(ResourceType type, String name); -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java deleted file mode 100644 index 6db0d94..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolder.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; - -import java.util.ArrayList; -import java.util.Collection; - -/** - * Resource Folder class. Contains list of {@link ResourceFile}s, - * the {@link FolderConfiguration}, and a link to the workspace {@link IFolder} object. - */ -public final class ResourceFolder extends Resource { - ResourceFolderType mType; - FolderConfiguration mConfiguration; - IAbstractFolder mFolder; - ArrayList mFiles = null; - private final boolean mIsFramework; - - /** - * Creates a new {@link ResourceFolder} - * @param type The type of the folder - * @param config The configuration of the folder - * @param folder The associated {@link IAbstractFolder} object. - * @param isFrameworkRepository - */ - public ResourceFolder(ResourceFolderType type, FolderConfiguration config, - IAbstractFolder folder, boolean isFrameworkRepository) { - mType = type; - mConfiguration = config; - mFolder = folder; - mIsFramework = isFrameworkRepository; - } - - /** - * Adds a {@link ResourceFile} to the folder. - * @param file The {@link ResourceFile}. - */ - public void addFile(ResourceFile file) { - if (mFiles == null) { - mFiles = new ArrayList(); - } - - mFiles.add(file); - } - - /** - * Attempts to remove the {@link ResourceFile} associated with a specified {@link IFile}. - * @param file the IFile object. - */ - public void removeFile(IFile file) { - if (mFiles != null) { - int count = mFiles.size(); - for (int i = 0 ; i < count ; i++) { - ResourceFile resFile = mFiles.get(i); - if (resFile != null) { - IFile iFile = resFile.getFile().getIFile(); - if (iFile != null && iFile.equals(file)) { - mFiles.remove(i); - touch(); - } - } - } - } - } - - /** - * Returns the {@link IFolder} associated with this object. - */ - public IAbstractFolder getFolder() { - return mFolder; - } - - /** - * Returns the {@link ResourceFolderType} of this object. - */ - public ResourceFolderType getType() { - return mType; - } - - /** - * Returns whether the folder is a framework resource folder. - */ - public boolean isFramework() { - return mIsFramework; - } - - /** - * Returns the list of {@link ResourceType}s generated by the files inside this folder. - */ - public Collection getResourceTypes() { - ArrayList list = new ArrayList(); - - if (mFiles != null) { - for (ResourceFile file : mFiles) { - ResourceType[] types = file.getResourceTypes(); - - // loop through those and add them to the main list, - // if they are not already present - if (types != null) { - for (ResourceType resType : types) { - if (list.indexOf(resType) == -1) { - list.add(resType); - } - } - } - } - } - - return list; - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.Resource#getConfiguration() - */ - @Override - public FolderConfiguration getConfiguration() { - return mConfiguration; - } - - /** - * Returns whether the folder contains a file with the given name. - * @param name the name of the file. - */ - public boolean hasFile(String name) { - return mFolder.hasFile(name); - } - - /** - * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object. - * @param file The {@link IFile} object. - * @return the {@link ResourceFile} or null if no match was found. - */ - public ResourceFile getFile(IAbstractFile file) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - if (f.getFile().equals(file)) { - return f; - } - } - } - return null; - } - - /** - * Returns the {@link ResourceFile} matching a {@link IFile} object. - * @param file The {@link IFile} object. - * @return the {@link ResourceFile} or null if no match was found. - */ - public ResourceFile getFile(IFile file) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - IFile iFile = f.getFile().getIFile(); - if (iFile != null && iFile.equals(file)) { - return f; - } - } - } - return null; - } - - - /** - * Returns the {@link ResourceFile} matching a given name. - * @param file The name of the file to return. - * @return the {@link ResourceFile} or null if no match was found. - */ - public ResourceFile getFile(String filename) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - if (f.getFile().getName().equals(filename)) { - return f; - } - } - } - return null; - } - - /** - * Returns whether a file in the folder is generating a resource of a specified type. - * @param type The {@link ResourceType} being looked up. - */ - public boolean hasResources(ResourceType type) { - // Check if the folder type is able to generate resource of the type that was asked. - // this is a first check to avoid going through the files. - ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type); - - boolean valid = false; - for (ResourceFolderType rft : folderTypes) { - if (rft == mType) { - valid = true; - break; - } - } - - if (valid) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - if (f.hasResources(type)) { - return true; - } - } - } - } - return false; - } - - /** - * Get the list of {@link ResourceItem} of a specific type generated by all the files - * in the folder. - * This method must make sure not to create duplicates. - * @param type The type of {@link ResourceItem} to return. - * @param projectResources The global Project Resource object, allowing the implementation to - * query for already existing {@link ResourceItem} - * @return The list of new {@link ResourceItem} - * @see ProjectResources#findResourceItem(ResourceType, String) - */ - public Collection getResources(ResourceType type, - ProjectResources projectResources) { - Collection list = new ArrayList(); - if (mFiles != null) { - for (ResourceFile f : mFiles) { - list.addAll(f.getResources(type, projectResources)); - } - } - return list; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java deleted file mode 100644 index bd93301..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceFolderType.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; - -/** - * Enum representing a type of resource folder. - */ -public enum ResourceFolderType { - ANIM(AndroidConstants.FD_ANIM), - COLOR(AndroidConstants.FD_COLOR), - DRAWABLE(AndroidConstants.FD_DRAWABLE), - LAYOUT(AndroidConstants.FD_LAYOUT), - MENU(AndroidConstants.FD_MENU), - RAW(AndroidConstants.FD_RAW), - VALUES(AndroidConstants.FD_VALUES), - XML(AndroidConstants.FD_XML); - - private final String mName; - - ResourceFolderType(String name) { - mName = name; - } - - public String getName() { - return mName; - } - - /** - * Returns the enum by name. - * @param name The enum string value. - * @return the enum or null if not found. - */ - public static ResourceFolderType getTypeByName(String name) { - for (ResourceFolderType rType : values()) { - if (rType.mName.equals(name)) { - return rType; - } - } - return null; - } - - /** - * Returns the {@link ResourceFolderType} from the folder name - * @param folderName The name of the folder. This must be a valid folder name in the format - * resType[-resqualifiers[-resqualifiers[...]] - * @return the ResourceFolderType representing the type of the folder, or - * null if no matching type was found. - */ - public static ResourceFolderType getFolderType(String folderName) { - // split the name of the folder in segments. - String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); - - // get the enum for the resource type. - return getTypeByName(folderSegments[0]); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java deleted file mode 100644 index 64942ed..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceManager.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFileListener; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IFolderListener; -import com.android.ide.eclipse.editors.resources.manager.ResourceMonitor.IProjectListener; -import com.android.ide.eclipse.editors.resources.manager.files.FileWrapper; -import com.android.ide.eclipse.editors.resources.manager.files.FolderWrapper; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFolder; -import com.android.ide.eclipse.editors.resources.manager.files.IFileWrapper; -import com.android.ide.eclipse.editors.resources.manager.files.IFolderWrapper; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; - -public final class ResourceManager implements IProjectListener, IFolderListener, IFileListener { - - private final static ResourceManager sThis = new ResourceManager(); - - /** List of the qualifier object helping for the parsing of folder names */ - private final ResourceQualifier[] mQualifiers; - - /** - * Map associating project resource with project objects. - */ - private final HashMap mMap = - new HashMap(); - - private ProjectResources mFrameworkResources = null; - - /** - * Sets up the resource manager with the global resource monitor. - * @param monitor The global resource monitor - */ - public static void setup(ResourceMonitor monitor) { - monitor.addProjectListener(sThis); - int mask = IResourceDelta.ADDED | IResourceDelta.REMOVED | IResourceDelta.CHANGED; - monitor.addFolderListener(sThis, mask); - monitor.addFileListener(sThis, mask); - - CompiledResourcesMonitor.setupMonitor(monitor); - } - - /** - * Returns the singleton instance. - */ - public static ResourceManager getInstance() { - return sThis; - } - - /** - * Returns the resources of a project. - * @param project The project - * @return a ProjectResources object or null if none was found. - */ - public ProjectResources getProjectResources(IProject project) { - return mMap.get(project); - } - - /** - * Returns the resources of the framework. - *

              This could be null if the parsing failed. - */ - public ProjectResources getFrameworkResources() { - return mFrameworkResources; - } - - /** - * Processes folder event. - */ - public void folderChanged(IFolder folder, int kind) { - ProjectResources resources; - - final IProject project = folder.getProject(); - - try { - if (project.hasNature(AndroidConstants.NATURE) == false) { - return; - } - } catch (CoreException e) { - // can't get the project nature? return! - return; - } - - switch (kind) { - case IResourceDelta.ADDED: - // checks if the folder is under res. - IPath path = folder.getFullPath(); - - // the path will be project/res/ - if (path.segmentCount() == 3) { - if (isInResFolder(path)) { - // get the project and its resource object. - resources = mMap.get(project); - - // if it doesn't exist, we create it. - if (resources == null) { - resources = new ProjectResources(false /* isFrameworkRepository */); - mMap.put(project, resources); - } - - processFolder(new IFolderWrapper(folder), resources); - } - } - break; - case IResourceDelta.CHANGED: - resources = mMap.get(folder.getProject()); - if (resources != null) { - ResourceFolder resFolder = resources.getResourceFolder(folder); - if (resFolder != null) { - resFolder.touch(); - } - } - break; - case IResourceDelta.REMOVED: - resources = mMap.get(folder.getProject()); - if (resources != null) { - // lets get the folder type - ResourceFolderType type = ResourceFolderType.getFolderType(folder.getName()); - - resources.removeFolder(type, folder); - } - break; - } - } - - /* (non-Javadoc) - * Sent when a file changed. Depending on the file being changed, and the type of change (ADDED, - * REMOVED, CHANGED), the file change is processed to update the resource manager data. - * - * @param file The file that changed. - * @param markerDeltas The marker deltas for the file. - * @param kind The change kind. This is equivalent to - * {@link IResourceDelta#accept(IResourceDeltaVisitor)} - * - * @see IFileListener#fileChanged - */ - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { - ProjectResources resources; - - final IProject project = file.getProject(); - - try { - if (project.hasNature(AndroidConstants.NATURE) == false) { - return; - } - } catch (CoreException e) { - // can't get the project nature? return! - return; - } - - switch (kind) { - case IResourceDelta.ADDED: - // checks if the file is under res/something. - IPath path = file.getFullPath(); - - if (path.segmentCount() == 4) { - if (isInResFolder(path)) { - // get the project and its resources - resources = mMap.get(project); - - IContainer container = file.getParent(); - if (container instanceof IFolder && resources != null) { - - ResourceFolder folder = resources.getResourceFolder((IFolder)container); - - if (folder != null) { - processFile(new IFileWrapper(file), folder); - } - } - } - } - break; - case IResourceDelta.CHANGED: - // try to find a matching ResourceFile - resources = mMap.get(project); - if (resources != null) { - IContainer container = file.getParent(); - if (container instanceof IFolder) { - ResourceFolder resFolder = resources.getResourceFolder((IFolder)container); - - // we get the delete on the folder before the file, so it is possible - // the associated ResourceFolder doesn't exist anymore. - if (resFolder != null) { - // get the resourceFile, and touch it. - ResourceFile resFile = resFolder.getFile(file); - if (resFile != null) { - resFile.touch(); - } - } - } - } - break; - case IResourceDelta.REMOVED: - // try to find a matching ResourceFile - resources = mMap.get(project); - if (resources != null) { - IContainer container = file.getParent(); - if (container instanceof IFolder) { - ResourceFolder resFolder = resources.getResourceFolder((IFolder)container); - - // we get the delete on the folder before the file, so it is possible - // the associated ResourceFolder doesn't exist anymore. - if (resFolder != null) { - // remove the file - resFolder.removeFile(file); - } - } - } - break; - } - } - - public void projectClosed(IProject project) { - mMap.remove(project); - } - - public void projectDeleted(IProject project) { - mMap.remove(project); - } - - public void projectOpened(IProject project) { - createProject(project); - } - - public void projectOpenedWithWorkspace(IProject project) { - createProject(project); - } - - /** - * Returns the {@link ResourceFolder} for the given file or null if none exists. - */ - public ResourceFolder getResourceFolder(IFile file) { - IContainer container = file.getParent(); - if (container.getType() == IResource.FOLDER) { - IFolder parent = (IFolder)container; - IProject project = file.getProject(); - - ProjectResources resources = getProjectResources(project); - if (resources != null) { - return resources.getResourceFolder(parent); - } - } - - return null; - } - - /** - * Loads the framework resources. - * @param osFilePath the path to the folder containing all the versions of the framework - * resources - */ - public void loadFrameworkResources(String osResourcesPath) { - // for now only load the default framework resources - osResourcesPath += AndroidConstants.FD_DEFAULT_RES; - - File frameworkRes = new File(osResourcesPath); - if (frameworkRes.isDirectory()) { - mFrameworkResources = new ProjectResources(true /* isFrameworkRepository */); - - try { - File[] files = frameworkRes.listFiles(); - for (File file : files) { - if (file.isDirectory()) { - ResourceFolder resFolder = processFolder(new FolderWrapper(file), - mFrameworkResources); - - if (resFolder != null) { - // now we process the content of the folder - File[] children = file.listFiles(); - - for (File childRes : children) { - if (childRes.isFile()) { - processFile(new FileWrapper(childRes), resFolder); - } - } - } - - } - } - - // now that we have loaded the files, we need to force load the resources from them - mFrameworkResources.loadAll(); - - } catch (IOException e) { - // since we test that folders are folders, and files are files, this shouldn't - // happen. We can ignore it. - } - } - } - - /** - * Initial project parsing to gather resource info. - * @param project - */ - private void createProject(IProject project) { - if (project.isOpen()) { - try { - if (project.hasNature(AndroidConstants.NATURE) == false) { - return; - } - } catch (CoreException e1) { - // can't check the nature of the project? ignore it. - return; - } - - IFolder resourceFolder = project.getFolder(AndroidConstants.FD_RESOURCES); - - ProjectResources projectResources = mMap.get(project); - if (projectResources == null) { - projectResources = new ProjectResources(false /* isFrameworkRepository */); - mMap.put(project, projectResources); - } - - 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 = processFolder(new IFolderWrapper(folder), - projectResources); - - 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; - - processFile(new IFileWrapper(file), resFolder); - } - } - } - } - } - } 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. - } - } - } - } - - /** - * Creates a {@link FolderConfiguration} matching the folder segments. - * @param folderSegments The segments of the folder name. The first segments should contain - * the name of the folder - * @return a FolderConfiguration object, or null if the folder name isn't valid.. - */ - public FolderConfiguration getConfig(String[] folderSegments) { - FolderConfiguration config = new FolderConfiguration(); - - // we are going to loop through the segments, and match them with the first - // available qualifier. If the segment doesn't match we try with the next qualifier. - // Because the order of the qualifier is fixed, we do not reset the first qualifier - // after each sucessful segment. - // If we run out of qualifier before processing all the segments, we fail. - - int qualifierIndex = 0; - int qualifierCount = mQualifiers.length; - - for (int i = 1 ; i < folderSegments.length; i++) { - String seg = folderSegments[i]; - if (seg.length() > 0) { - while (qualifierIndex < qualifierCount && - mQualifiers[qualifierIndex].checkAndSet(seg, config) == false) { - qualifierIndex++; - } - - // if we reached the end of the qualifier we didn't find a matching qualifier. - if (qualifierIndex == qualifierCount) { - return null; - } - - } else { - return null; - } - } - - return config; - } - - /** - * Processes a folder and adds it to the list of the project resources. - * @param folder the folder to process - * @param project the folder's project. - * @return the ConfiguredFolder created from this folder, or null if the process failed. - */ - private ResourceFolder processFolder(IAbstractFolder folder, ProjectResources project) { - // split the name of the folder in segments. - String[] folderSegments = folder.getName().split(FolderConfiguration.QUALIFIER_SEP); - - // get the enum for the resource type. - ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]); - - if (type != null) { - // get the folder configuration. - FolderConfiguration config = getConfig(folderSegments); - - if (config != null) { - ResourceFolder configuredFolder = project.add(type, config, folder); - - return configuredFolder; - } - } - - return null; - } - - /** - * Processes a file and adds it to its parent folder resource. - * @param file - * @param folder - */ - private void processFile(IAbstractFile file, ResourceFolder folder) { - // get the type of the folder - ResourceFolderType type = folder.getType(); - - // look for this file if it's already been created - ResourceFile resFile = folder.getFile(file); - - if (resFile != null) { - // invalidate the file - resFile.touch(); - } else { - // create a ResourceFile for it. - - // check if that's a single or multi resource type folder. For now we define this by - // the number of possible resource type output by files in the folder. This does - // not make the difference between several resource types from a single file or - // the ability to have 2 files in the same folder generating 2 different types of - // resource. The former is handled by MultiResourceFile properly while we don't - // handle the latter. If we were to add this behavior we'd have to change this call. - ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(type); - - if (types.length == 1) { - resFile = new SingleResourceFile(file, folder); - } else { - resFile = new MultiResourceFile(file, folder); - } - - // add it to the folder - folder.addFile(resFile); - } - } - - /** - * Returns true if the path is under /project/res/ - * @param path a workspace relative path - * @return true if the path is under /project res/ - */ - private boolean isInResFolder(IPath path) { - return AndroidConstants.FD_RESOURCES.equalsIgnoreCase(path.segment(1)); - } - - /** - * Private constructor to enforce singleton design. - */ - ResourceManager() { - // get the default qualifiers. - FolderConfiguration defaultConfig = new FolderConfiguration(); - defaultConfig.createDefault(); - mQualifiers = defaultConfig.getQualifiers(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java deleted file mode 100644 index 45a020c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/ResourceMonitor.java +++ /dev/null @@ -1,334 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.project.BaseProjectHelper; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IMarkerDelta; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IResourceChangeEvent; -import org.eclipse.core.resources.IResourceChangeListener; -import org.eclipse.core.resources.IResourceDelta; -import org.eclipse.core.resources.IResourceDeltaVisitor; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.IWorkspaceRoot; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.jdt.core.IJavaModel; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; - -import java.util.ArrayList; - -/** - * Resource Monitor for the whole editor plugin. Other, more simple, listeners can register to - * that one. - */ -public class ResourceMonitor implements IResourceChangeListener { - - private final static ResourceMonitor sThis = new ResourceMonitor(); - - /** - * Classes which implement this interface provide a method that deals - * with file change events. - */ - public interface IFileListener { - /** - * Sent when a file changed. - * @param file The file that changed. - * @param markerDeltas The marker deltas for the file. - * @param kind The change kind. This is equivalent to - * {@link IResourceDelta#accept(IResourceDeltaVisitor)} - */ - public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind); - } - - /** - * Classes which implements this interface provide methods dealing with project events. - */ - public interface IProjectListener { - /** - * Sent for each opened android project at the time the listener is put in place. - * @param project the opened project. - */ - public void projectOpenedWithWorkspace(IProject project); - /** - * Sent when a project is opened. - * @param project the project being opened. - */ - public void projectOpened(IProject project); - /** - * Sent when a project is closed. - * @param project the project being closed. - */ - public void projectClosed(IProject project); - /** - * Sent when a project is deleted. - * @param project the project about to be deleted. - */ - public void projectDeleted(IProject project); - } - - /** - * Classes which implement this interface provide a method that deals - * with folder change events - */ - public interface IFolderListener { - /** - * Sent when a folder changed. - * @param folder The file that was changed - * @param kind The change kind. This is equivalent to {@link IResourceDelta#getKind()} - */ - public void folderChanged(IFolder folder, int kind); - } - - /** - * Interface for a listener to be notified when resource change event starts and ends. - */ - public interface IResourceEventListener { - public void resourceChangeEventStart(); - public void resourceChangeEventEnd(); - } - - /** - * Base listener bundle to associate a listener to an event mask. - */ - private static class ListenerBundle { - /** Mask value to accept all events */ - public final static int MASK_NONE = -1; - - /** - * Event mask. Values accepted are IResourceDelta.### - * @see IResourceDelta#ADDED - * @see IResourceDelta#REMOVED - * @see IResourceDelta#CHANGED - * @see IResourceDelta#ADDED_PHANTOM - * @see IResourceDelta#REMOVED_PHANTOM - * */ - int kindMask; - } - - /** - * Listener bundle for file event. - */ - private static class FileListenerBundle extends ListenerBundle { - - /** The file listener */ - IFileListener listener; - } - - /** - * Listener bundle for folder event. - */ - private static class FolderListenerBundle extends ListenerBundle { - /** The file listener */ - IFolderListener listener; - } - - private final ArrayList mFileListeners = - new ArrayList(); - - private final ArrayList mFolderListeners = - new ArrayList(); - - private final ArrayList mProjectListeners = new ArrayList(); - - private final ArrayList mEventListeners = - new ArrayList(); - - private IWorkspace mWorkspace; - - /** - * Delta visitor for resource changes. - */ - private final class DeltaVisitor implements IResourceDeltaVisitor { - - public boolean visit(IResourceDelta delta) { - IResource r = delta.getResource(); - int type = r.getType(); - if (type == IResource.FILE) { - int kind = delta.getKind(); - // notify the listeners. - for (FileListenerBundle bundle : mFileListeners) { - if (bundle.kindMask == ListenerBundle.MASK_NONE - || (bundle.kindMask & kind) != 0) { - bundle.listener.fileChanged((IFile)r, delta.getMarkerDeltas(), kind); - } - } - return false; - } else if (type == IResource.FOLDER) { - int kind = delta.getKind(); - // notify the listeners. - for (FolderListenerBundle bundle : mFolderListeners) { - if (bundle.kindMask == ListenerBundle.MASK_NONE - || (bundle.kindMask & kind) != 0) { - bundle.listener.folderChanged((IFolder)r, kind); - } - } - return true; - } else if (type == IResource.PROJECT) { - int flags = delta.getFlags(); - - if (flags == IResourceDelta.OPEN) { - // the project is opening or closing. - IProject project = (IProject)r; - - // the OPEN flag represent a toggle in the open/close state of the - // project, but this is sent before the project actually toggles - // its state. - // This means that if the project is closing, isOpen() will return true. - boolean isClosing = project.isOpen(); - if (isClosing) { - // notify the listeners. - for (IProjectListener pl : mProjectListeners) { - pl.projectClosed(project); - } - } else { - // notify the listeners. - for (IProjectListener pl : mProjectListeners) { - pl.projectOpened(project); - } - } - } - } - - return true; - } - } - - public static ResourceMonitor getMonitor() { - return sThis; - } - - - /** - * Starts the resource monitoring. - * @param ws The current workspace. - * @return The monitor object. - */ - public static ResourceMonitor startMonitoring(IWorkspace ws) { - if (sThis != null) { - ws.addResourceChangeListener(sThis, - IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_DELETE); - sThis.mWorkspace = ws; - } - return sThis; - } - - /** - * Stops the resource monitoring. - * @param ws The current workspace. - */ - public static void stopMonitoring(IWorkspace ws) { - if (sThis != null) { - ws.removeResourceChangeListener(sThis); - - sThis.mFileListeners.clear(); - sThis.mProjectListeners.clear(); - } - } - - /** - * Adds a file listener. - * @param listener The listener to receive the events. - * @param kindMask The event mask to filter out specific events. - * {@link ListenerBundle#MASK_NONE} will forward all events. - */ - public synchronized void addFileListener(IFileListener listener, int kindMask) { - FileListenerBundle bundle = new FileListenerBundle(); - bundle.listener = listener; - bundle.kindMask = kindMask; - - mFileListeners.add(bundle); - } - - /** - * Adds a folder listener. - * @param listener The listener to receive the events. - * @param kindMask The event mask to filter out specific events. - * {@link ListenerBundle#MASK_NONE} will forward all events. - */ - public synchronized void addFolderListener(IFolderListener listener, int kindMask) { - FolderListenerBundle bundle = new FolderListenerBundle(); - bundle.listener = listener; - bundle.kindMask = kindMask; - - mFolderListeners.add(bundle); - } - - /** - * Adds a project listener. - * @param listener The listener to receive the events. - */ - public synchronized void addProjectListener(IProjectListener listener) { - mProjectListeners.add(listener); - - // we need to look at the opened projects and give them to the listener. - - // get the list of opened android projects. - IWorkspaceRoot workspaceRoot = mWorkspace.getRoot(); - IJavaModel javaModel = JavaCore.create(workspaceRoot); - IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(javaModel); - - for (IJavaProject androidProject : androidProjects) { - listener.projectOpenedWithWorkspace(androidProject.getProject()); - } - } - - public synchronized void addResourceEventListener(IResourceEventListener listener) { - mEventListeners.add(listener); - } - - /** - * Processes the workspace resource change events. - */ - public void resourceChanged(IResourceChangeEvent event) { - // notify the event listeners of a start. - for (IResourceEventListener listener : mEventListeners) { - listener.resourceChangeEventStart(); - } - - if (event.getType() == IResourceChangeEvent.PRE_DELETE) { - // a project is being deleted. Lets get the project object and remove - // its compiled resource list. - IResource r = event.getResource(); - IProject project = r.getProject(); - - // notify the listeners. - for (IProjectListener pl : mProjectListeners) { - pl.projectDeleted(project); - } - } else { - // this a regular resource change. We get the delta and go through it with a visitor. - IResourceDelta delta = event.getDelta(); - - DeltaVisitor visitor = new DeltaVisitor(); - try { - delta.accept(visitor); - } catch (CoreException e) { - } - } - - // we're done, notify the event listeners. - for (IResourceEventListener listener : mEventListeners) { - listener.resourceChangeEventEnd(); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java deleted file mode 100644 index 1211236..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/SingleResourceFile.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.manager; - -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.manager.files.IAbstractFile; -import com.android.layoutlib.api.IResourceValue; -import com.android.layoutlib.utils.ResourceValue; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.xml.parsers.SAXParserFactory; - -/** - * Represents a resource file describing a single resource. - *

              - * This is typically an XML file inside res/anim, res/layout, or res/menu or an image file - * under res/drawable. - */ -public class SingleResourceFile extends ResourceFile { - - private final static SAXParserFactory sParserFactory = SAXParserFactory.newInstance(); - static { - sParserFactory.setNamespaceAware(true); - } - - private final static Pattern sXmlPattern = Pattern.compile("^(.+)\\.xml", //$NON-NLS-1$ - Pattern.CASE_INSENSITIVE); - - private final static Pattern[] sDrawablePattern = new Pattern[] { - Pattern.compile("^(.+)\\.9\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.png", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.jpg", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - Pattern.compile("^(.+)\\.gif", Pattern.CASE_INSENSITIVE), //$NON-NLS-1$ - }; - - private String mResourceName; - private ResourceType mType; - private IResourceValue mValue; - - public SingleResourceFile(IAbstractFile file, ResourceFolder folder) { - super(file, folder); - - // we need to infer the type of the resource from the folder type. - // This is easy since this is a single Resource file. - ResourceType[] types = FolderTypeRelationship.getRelatedResourceTypes(folder.getType()); - mType = types[0]; - - // compute the resource name - mResourceName = getResourceName(mType); - - mValue = new ResourceValue(mType.getName(), getResourceName(mType), file.getOsLocation(), - isFramework()); - } - - @Override - public ResourceType[] getResourceTypes() { - return FolderTypeRelationship.getRelatedResourceTypes(getFolder().getType()); - } - - @Override - public boolean hasResources(ResourceType type) { - return FolderTypeRelationship.match(type, getFolder().getType()); - } - - @Override - public Collection getResources(ResourceType type, - ProjectResources projectResources) { - - // looking for an existing ResourceItem with this name and type - ProjectResourceItem item = projectResources.findResourceItem(type, mResourceName); - - ArrayList items = new ArrayList(); - - if (item == null) { - item = new ConfigurableResourceItem(mResourceName); - items.add(item); - } - - // add this ResourceFile to the ResourceItem - item.add(this); - - return items; - } - - /* - * (non-Javadoc) - * @see com.android.ide.eclipse.editors.resources.manager.ResourceFile#getValue(com.android.ide.eclipse.common.resources.ResourceType, java.lang.String) - * - * This particular implementation does not care about the type or name since a - * SingleResourceFile represents a file generating only one resource. - * The value returned is the full absolute path of the file in OS form. - */ - @Override - public IResourceValue getValue(ResourceType type, String name) { - return mValue; - } - - /** - * Returns the name of the resources. - * @param type - * @return - */ - private String getResourceName(ResourceType type) { - // get the name from the filename. - String name = getFile().getName(); - - if (type == ResourceType.ANIM || type == ResourceType.LAYOUT || type == ResourceType.MENU || - type == ResourceType.COLOR || type == ResourceType.XML) { - Matcher m = sXmlPattern.matcher(name); - if (m.matches()) { - return m.group(1); - } - } else if (type == ResourceType.DRAWABLE) { - for (Pattern p : sDrawablePattern) { - Matcher m = p.matcher(name); - if (m.matches()) { - return m.group(1); - } - } - - // also try the Xml pattern for selector/shape based drawable. - Matcher m = sXmlPattern.matcher(name); - if (m.matches()) { - return m.group(1); - } - } - return name; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java deleted file mode 100644 index 0a14214..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FileWrapper.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -/** - * An implementation of {@link IAbstractFile} on top of a {@link File} object. - * - */ -public class FileWrapper implements IAbstractFile { - - private File mFile; - - /** - * Constructs a {@link FileWrapper} object. If {@link File#isFile()} returns false - * then an {@link IOException} is thrown. - */ - public FileWrapper(File file) throws IOException { - if (file.isFile() == false) { - throw new IOException("FileWrapper must wrap a File object representing an existing file!"); //$NON-NLS-1$ - } - - mFile = file; - } - - public InputStream getContents() throws CoreException { - try { - return new FileInputStream(mFile); - } catch (FileNotFoundException e) { - // we'll return null below. - } - - return null; - } - - public IFile getIFile() { - return null; - } - - public String getOsLocation() { - return mFile.getAbsolutePath(); - } - - public String getName() { - return mFile.getName(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof FileWrapper) { - return mFile.equals(((FileWrapper)obj).mFile); - } - - if (obj instanceof File) { - return mFile.equals((File)obj); - } - - return super.equals(obj); - } - - @Override - public int hashCode() { - return mFile.hashCode(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java deleted file mode 100644 index 8afea33..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/FolderWrapper.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFolder; - -import java.io.File; -import java.io.IOException; - -/** - * An implementation of {@link IAbstractFolder} on top of a {@link File} object. - */ -public class FolderWrapper implements IAbstractFolder { - - private File mFolder; - - /** - * Constructs a {@link FileWrapper} object. If {@link File#isDirectory()} returns - * false then an {@link IOException} is thrown. - */ - public FolderWrapper(File folder) throws IOException { - if (folder.isDirectory() == false) { - throw new IOException("FileWrapper must wrap a File object representing an existing folder!"); //$NON-NLS-1$ - } - - mFolder = folder; - } - - public boolean hasFile(String name) { - return false; - } - - public String getName() { - return mFolder.getName(); - } - - public IFolder getIFolder() { - return null; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof FolderWrapper) { - return mFolder.equals(((FolderWrapper)obj).mFolder); - } - - if (obj instanceof File) { - return mFolder.equals((File)obj); - } - - return super.equals(obj); - } - - @Override - public int hashCode() { - return mFolder.hashCode(); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java deleted file mode 100644 index 7e807f9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFile.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; - -import java.io.InputStream; - -/** - * A file. - */ -public interface IAbstractFile extends IAbstractResource { - - /** - * Returns an {@link InputStream} object on the file content. - * @throws CoreException - */ - InputStream getContents() throws CoreException; - - /** - * Returns the OS path of the file location. - */ - String getOsLocation(); - - /** - * Returns the {@link IFile} object that the receiver could represent. Can be null - */ - IFile getIFile(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java deleted file mode 100644 index b35283d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractFolder.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFolder; - -/** - * A folder. - */ -public interface IAbstractFolder extends IAbstractResource { - - /** - * Returns true if the receiver contains a file with a given name - * @param name the name of the file. This is the name without the path leading to the - * parent folder. - */ - boolean hasFile(String name); - - /** - * Returns the {@link IFolder} object that the receiver could represent. - * Can be null - */ - IFolder getIFolder(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java deleted file mode 100644 index daf243d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IAbstractResource.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFile; - -import java.io.File; - -/** - * Base representation of a file system resource.

              - * This somewhat limited interface is designed to let classes use file-system resources, without - * having the manually handle {@link IFile} and/or {@link File} manually. - */ -public interface IAbstractResource { - - /** - * Returns the name of the resource. - */ - String getName(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java deleted file mode 100644 index 441c65b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFileWrapper.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.runtime.CoreException; - -import java.io.InputStream; - -/** - * An implementation of {@link IAbstractFile} on top of an {@link IFile} object. - */ -public class IFileWrapper implements IAbstractFile { - - private IFile mFile; - - public IFileWrapper(IFile file) { - mFile = file; - } - - public InputStream getContents() throws CoreException { - return mFile.getContents(); - } - - public String getOsLocation() { - return mFile.getLocation().toOSString(); - } - - public String getName() { - return mFile.getName(); - } - - public IFile getIFile() { - return mFile; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof IFileWrapper) { - return mFile.equals(((IFileWrapper)obj).mFile); - } - - if (obj instanceof IFile) { - return mFile.equals((IFile)obj); - } - - return super.equals(obj); - } - - @Override - public int hashCode() { - return mFile.hashCode(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java deleted file mode 100644 index 92b5c07..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/manager/files/IFolderWrapper.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.editors.resources.manager.files; - -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; - -/** - * An implementation of {@link IAbstractFolder} on top of an {@link IFolder} object. - */ -public class IFolderWrapper implements IAbstractFolder { - - private IFolder mFolder; - - public IFolderWrapper(IFolder folder) { - mFolder = folder; - } - - public String getName() { - return mFolder.getName(); - } - - public boolean hasFile(String name) { - try { - IResource[] files = mFolder.members(); - for (IResource file : files) { - if (name.equals(file.getName())) { - return true; - } - } - } catch (CoreException e) { - // we'll return false below. - } - - return false; - } - - public IFolder getIFolder() { - return mFolder; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof IFolderWrapper) { - return mFolder.equals(((IFolderWrapper)obj).mFolder); - } - - if (obj instanceof IFolder) { - return mFolder.equals((IFolder)obj); - } - - return super.equals(obj); - } - - @Override - public int hashCode() { - return mFolder.hashCode(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java deleted file mode 100644 index 29453e9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiColorValueNode.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.resources.uimodel; - -import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; -import com.android.ide.eclipse.editors.uimodel.UiTextValueNode; - -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.widgets.Text; - -import java.util.regex.Pattern; - -/** - * Displays and edits a color XML element value with a custom validator. - *

              - * See {@link UiAttributeNode} for more information. - */ -public class UiColorValueNode extends UiTextValueNode { - - /** Accepted RGBA formats are one of #RGB, #ARGB, #RRGGBB or #AARRGGBB. */ - private static final Pattern RGBA_REGEXP = Pattern.compile( - "#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})"); //$NON-NLS-1$ - - public UiColorValueNode(TextValueDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /* (non-java doc) - * - * Add a modify listener that will check colors have the proper format, - * that is one of #RGB, #ARGB, #RRGGBB or #AARRGGBB. - */ - @Override - protected void onAddValidators(final Text text) { - ModifyListener listener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - String color = text.getText(); - if (RGBA_REGEXP.matcher(color).matches()) { - getManagedForm().getMessageManager().removeMessage(text, text); - } else { - getManagedForm().getMessageManager().addMessage(text, - "Accepted color formats are one of #RGB, #ARGB, #RRGGBB or #AARRGGBB.", - null /* data */, IMessageProvider.ERROR, text); - } - } - }; - - text.addModifyListener(listener); - - // Make sure the validator removes its message(s) when the widget is disposed - text.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - getManagedForm().getMessageManager().removeMessage(text, text); - } - }); - - // Finally call the validator once to make sure the initial value is processed - listener.modifyText(null); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java deleted file mode 100644 index 89649f5..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/resources/uimodel/UiItemElementNode.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.editors.resources.uimodel; - -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.resources.descriptors.ItemElementDescriptor; -import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * {@link UiItemElementNode} is apecial version of {@link UiElementNode} that - * customizes the element display to include the item type attribute if present. - */ -public class UiItemElementNode extends UiElementNode { - - /** - * Creates a new {@link UiElementNode} described by a given {@link ItemElementDescriptor}. - * - * @param elementDescriptor The {@link ItemElementDescriptor} for the XML node. Cannot be null. - */ - public UiItemElementNode(ItemElementDescriptor elementDescriptor) { - super(elementDescriptor); - } - - @Override - public String getShortDescription() { - Node xmlNode = getXmlNode(); - if (xmlNode != null && xmlNode instanceof Element && xmlNode.hasAttributes()) { - - Element elem = (Element) xmlNode; - String type = elem.getAttribute(ResourcesDescriptors.TYPE_ATTR); - String name = elem.getAttribute(ResourcesDescriptors.NAME_ATTR); - if (type != null && name != null && type.length() > 0 && name.length() > 0) { - type = DescriptorsUtils.capitalize(type); - return String.format("%1$s (%2$s %3$s)", name, type, getDescriptor().getUiName()); - } - } - - return super.getShortDescription(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java deleted file mode 100644 index 5fb479f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/EditableDialogCellEditor.java +++ /dev/null @@ -1,458 +0,0 @@ -/* - * 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.editors.ui; - -import org.eclipse.core.runtime.Assert; -import org.eclipse.jface.viewers.DialogCellEditor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusAdapter; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.KeyAdapter; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Text; - -import java.text.MessageFormat; - -/** - * Custom DialogCellEditor, replacing the Label with an editable {@link Text} widget. - *

              Also set the button to {@link SWT#FLAT} to make sure it looks good on MacOS X. - *

              Most of the code comes from TextCellEditor. - */ -public abstract class EditableDialogCellEditor extends DialogCellEditor { - - private Text text; - - private ModifyListener modifyListener; - - /** - * State information for updating action enablement - */ - private boolean isSelection = false; - - private boolean isDeleteable = false; - - private boolean isSelectable = false; - - EditableDialogCellEditor(Composite parent) { - super(parent); - } - - /* - * Re-implement this method only to properly set the style in the button, or it won't look - * good in MacOS X - */ - @Override - protected Button createButton(Composite parent) { - Button result = new Button(parent, SWT.DOWN | SWT.FLAT); - result.setText("..."); //$NON-NLS-1$ - return result; - } - - - @Override - protected Control createContents(Composite cell) { - text = new Text(cell, SWT.SINGLE); - text.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetDefaultSelected(SelectionEvent e) { - handleDefaultSelection(e); - } - }); - text.addKeyListener(new KeyAdapter() { - // hook key pressed - see PR 14201 - @Override - public void keyPressed(KeyEvent e) { - keyReleaseOccured(e); - - // as a result of processing the above call, clients may have - // disposed this cell editor - if ((getControl() == null) || getControl().isDisposed()) { - return; - } - checkSelection(); // see explanation below - checkDeleteable(); - checkSelectable(); - } - }); - text.addTraverseListener(new TraverseListener() { - public void keyTraversed(TraverseEvent e) { - if (e.detail == SWT.TRAVERSE_ESCAPE - || e.detail == SWT.TRAVERSE_RETURN) { - e.doit = false; - } - } - }); - // We really want a selection listener but it is not supported so we - // use a key listener and a mouse listener to know when selection changes - // may have occurred - text.addMouseListener(new MouseAdapter() { - @Override - public void mouseUp(MouseEvent e) { - checkSelection(); - checkDeleteable(); - checkSelectable(); - } - }); - text.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - EditableDialogCellEditor.this.focusLost(); - } - }); - text.setFont(cell.getFont()); - text.setBackground(cell.getBackground()); - text.setText("");//$NON-NLS-1$ - text.addModifyListener(getModifyListener()); - return text; - } - - /** - * Checks to see if the "deletable" state (can delete/ - * nothing to delete) has changed and if so fire an - * enablement changed notification. - */ - private void checkDeleteable() { - boolean oldIsDeleteable = isDeleteable; - isDeleteable = isDeleteEnabled(); - if (oldIsDeleteable != isDeleteable) { - fireEnablementChanged(DELETE); - } - } - - /** - * Checks to see if the "selectable" state (can select) - * has changed and if so fire an enablement changed notification. - */ - private void checkSelectable() { - boolean oldIsSelectable = isSelectable; - isSelectable = isSelectAllEnabled(); - if (oldIsSelectable != isSelectable) { - fireEnablementChanged(SELECT_ALL); - } - } - - /** - * Checks to see if the selection state (selection / - * no selection) has changed and if so fire an - * enablement changed notification. - */ - private void checkSelection() { - boolean oldIsSelection = isSelection; - isSelection = text.getSelectionCount() > 0; - if (oldIsSelection != isSelection) { - fireEnablementChanged(COPY); - fireEnablementChanged(CUT); - } - } - - /* (non-Javadoc) - * Method declared on CellEditor. - */ - @Override - protected void doSetFocus() { - if (text != null) { - text.selectAll(); - text.setFocus(); - checkSelection(); - checkDeleteable(); - checkSelectable(); - } - } - - /* - * (non-Javadoc) - * @see org.eclipse.jface.viewers.DialogCellEditor#updateContents(java.lang.Object) - */ - @Override - protected void updateContents(Object value) { - Assert.isTrue(text != null && (value == null || (value instanceof String))); - if (value != null) { - text.removeModifyListener(getModifyListener()); - text.setText((String) value); - text.addModifyListener(getModifyListener()); - } - } - - /** - * The TextCellEditor implementation of - * this CellEditor framework method returns - * the text string. - * - * @return the text string - */ - @Override - protected Object doGetValue() { - return text.getText(); - } - - - /** - * Processes a modify event that occurred in this text cell editor. - * This framework method performs validation and sets the error message - * accordingly, and then reports a change via fireEditorValueChanged. - * Subclasses should call this method at appropriate times. Subclasses - * may extend or reimplement. - * - * @param e the SWT modify event - */ - protected void editOccured(ModifyEvent e) { - String value = text.getText(); - if (value == null) { - value = "";//$NON-NLS-1$ - } - Object typedValue = value; - boolean oldValidState = isValueValid(); - boolean newValidState = isCorrect(typedValue); - - if (!newValidState) { - // try to insert the current value into the error message. - setErrorMessage(MessageFormat.format(getErrorMessage(), - new Object[] { value })); - } - valueChanged(oldValidState, newValidState); - } - - /** - * Return the modify listener. - */ - private ModifyListener getModifyListener() { - if (modifyListener == null) { - modifyListener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - editOccured(e); - } - }; - } - return modifyListener; - } - - /** - * Handles a default selection event from the text control by applying the editor - * value and deactivating this cell editor. - * - * @param event the selection event - * - * @since 3.0 - */ - protected void handleDefaultSelection(SelectionEvent event) { - // same with enter-key handling code in keyReleaseOccured(e); - fireApplyEditorValue(); - deactivate(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method returns true if - * the current selection is not empty. - */ - @Override - public boolean isCopyEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return text.getSelectionCount() > 0; - } - - /** - * The TextCellEditor implementation of this - * CellEditor method returns true if - * the current selection is not empty. - */ - @Override - public boolean isCutEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return text.getSelectionCount() > 0; - } - - /** - * The TextCellEditor implementation of this - * CellEditor method returns true - * if there is a selection or if the caret is not positioned - * at the end of the text. - */ - @Override - public boolean isDeleteEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return text.getSelectionCount() > 0 - || text.getCaretPosition() < text.getCharCount(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method always returns true. - */ - @Override - public boolean isPasteEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return true; - } - - /** - * Check if save all is enabled - * @return true if it is - */ - public boolean isSaveAllEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return true; - } - - /** - * Returns true if this cell editor is - * able to perform the select all action. - *

              - * This default implementation always returns - * false. - *

              - *

              - * Subclasses may override - *

              - * @return true if select all is possible, - * false otherwise - */ - @Override - public boolean isSelectAllEnabled() { - if (text == null || text.isDisposed()) { - return false; - } - return text.getCharCount() > 0; - } - - /** - * Processes a key release event that occurred in this cell editor. - *

              - * The TextCellEditor implementation of this framework method - * ignores when the RETURN key is pressed since this is handled in - * handleDefaultSelection. - * An exception is made for Ctrl+Enter for multi-line texts, since - * a default selection event is not sent in this case. - *

              - * - * @param keyEvent the key event - */ - @Override - protected void keyReleaseOccured(KeyEvent keyEvent) { - if (keyEvent.character == '\r') { // Return key - // Enter is handled in handleDefaultSelection. - // Do not apply the editor value in response to an Enter key event - // since this can be received from the IME when the intent is -not- - // to apply the value. - // See bug 39074 [CellEditors] [DBCS] canna input mode fires bogus event from Text Control - // - // An exception is made for Ctrl+Enter for multi-line texts, since - // a default selection event is not sent in this case. - if (text != null && !text.isDisposed() - && (text.getStyle() & SWT.MULTI) != 0) { - if ((keyEvent.stateMask & SWT.CTRL) != 0) { - super.keyReleaseOccured(keyEvent); - } - } - return; - } - super.keyReleaseOccured(keyEvent); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method copies the - * current selection to the clipboard. - */ - @Override - public void performCopy() { - text.copy(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method cuts the - * current selection to the clipboard. - */ - @Override - public void performCut() { - text.cut(); - checkSelection(); - checkDeleteable(); - checkSelectable(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method deletes the - * current selection or, if there is no selection, - * the character next character from the current position. - */ - @Override - public void performDelete() { - if (text.getSelectionCount() > 0) { - // remove the contents of the current selection - text.insert(""); //$NON-NLS-1$ - } else { - // remove the next character - int pos = text.getCaretPosition(); - if (pos < text.getCharCount()) { - text.setSelection(pos, pos + 1); - text.insert(""); //$NON-NLS-1$ - } - } - checkSelection(); - checkDeleteable(); - checkSelectable(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method pastes the - * the clipboard contents over the current selection. - */ - @Override - public void performPaste() { - text.paste(); - checkSelection(); - checkDeleteable(); - checkSelectable(); - } - - /** - * The TextCellEditor implementation of this - * CellEditor method selects all of the - * current text. - */ - @Override - public void performSelectAll() { - text.selectAll(); - checkSelection(); - checkDeleteable(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java deleted file mode 100644 index d095376..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ErrorImageComposite.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.android.ide.eclipse.editors.ui; - -import org.eclipse.jface.resource.CompositeImageDescriptor; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.DecorationOverlayIcon; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; - -/** - * ImageDescriptor that adds a error marker. - * Based on {@link DecorationOverlayIcon} only available in Eclipse 3.3 - */ -public class ErrorImageComposite extends CompositeImageDescriptor { - - private Image mBaseImage; - private ImageDescriptor mErrorImageDescriptor; - private Point mSize; - - public ErrorImageComposite(Image baseImage) { - mBaseImage = baseImage; - mErrorImageDescriptor = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( - ISharedImages.IMG_OBJS_ERROR_TSK); - mSize = new Point(baseImage.getBounds().width, baseImage.getBounds().height); - } - - @Override - protected void drawCompositeImage(int width, int height) { - ImageData baseData = mBaseImage.getImageData(); - drawImage(baseData, 0, 0); - - ImageData overlayData = mErrorImageDescriptor.getImageData(); - if (overlayData.width == baseData.width && baseData.height == baseData.height) { - overlayData = overlayData.scaledTo(14, 14); - drawImage(overlayData, -3, mSize.y - overlayData.height + 3); - } else { - drawImage(overlayData, 0, mSize.y - overlayData.height); - } - } - - @Override - protected Point getSize() { - return mSize; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java deleted file mode 100644 index ccae099..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/FlagValueCellEditor.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.editors.ui; - -import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** - * DialogCellEditor able to receive a {@link UiFlagAttributeNode} in the {@link #setValue(Object)} - * method. - *

              The dialog box opened is the same as the one in the ui created by - * {@link UiFlagAttributeNode#createUiControl(Composite, org.eclipse.ui.forms.IManagedForm)} - */ -public class FlagValueCellEditor extends EditableDialogCellEditor { - - private UiFlagAttributeNode mUiFlagAttribute; - - public FlagValueCellEditor(Composite parent) { - super(parent); - } - - @Override - protected Object openDialogBox(Control cellEditorWindow) { - if (mUiFlagAttribute != null) { - String currentValue = (String)getValue(); - return mUiFlagAttribute.showDialog(cellEditorWindow.getShell(), currentValue); - } - - return null; - } - - @Override - protected void doSetValue(Object value) { - if (value instanceof UiFlagAttributeNode) { - mUiFlagAttribute = (UiFlagAttributeNode)value; - super.doSetValue(mUiFlagAttribute.getCurrentValue()); - return; - } - - super.doSetValue(value); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java deleted file mode 100644 index 304dd14..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.editors.ui; - -import com.android.ide.eclipse.editors.uimodel.UiListAttributeNode; - -import org.eclipse.jface.viewers.ComboBoxCellEditor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.CCombo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** - * ComboBoxCellEditor able to receive a {@link UiListAttributeNode} in the {@link #setValue(Object)} - * method, and returning a {@link String} in {@link #getValue()} instead of an {@link Integer}. - */ -public class ListValueCellEditor extends ComboBoxCellEditor { - private String[] mItems; - private CCombo mCombo; - - public ListValueCellEditor(Composite parent) { - super(parent, new String[0], SWT.DROP_DOWN); - } - - @Override - protected Control createControl(Composite parent) { - mCombo = (CCombo) super.createControl(parent); - return mCombo; - } - - @Override - protected void doSetValue(Object value) { - if (value instanceof UiListAttributeNode) { - UiListAttributeNode uiListAttribute = (UiListAttributeNode)value; - - // set the possible values in the combo - String[] items = uiListAttribute.getPossibleValues(); - mItems = new String[items.length]; - System.arraycopy(items, 0, mItems, 0, items.length); - setItems(mItems); - - // now edit the current value of the attribute - String attrValue = uiListAttribute.getCurrentValue(); - mCombo.setText(attrValue); - - return; - } - - // default behavior - super.doSetValue(value); - } - - @Override - protected Object doGetValue() { - String comboText = mCombo.getText(); - if (comboText == null) { - return ""; //$NON-NLS-1$ - } - return comboText; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java deleted file mode 100644 index 4fc0ab3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/ResourceValueCellEditor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.editors.ui; - -import com.android.ide.eclipse.editors.uimodel.UiFlagAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiResourceAttributeNode; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** - * DialogCellEditor able to receive a {@link UiFlagAttributeNode} in the {@link #setValue(Object)} - * method. - *

              The dialog box opened is the same as the one in the ui created by - * {@link UiFlagAttributeNode#createUiControl(Composite, org.eclipse.ui.forms.IManagedForm)} - */ -public class ResourceValueCellEditor extends EditableDialogCellEditor { - - private UiResourceAttributeNode mUiResourceAttribute; - - public ResourceValueCellEditor(Composite parent) { - super(parent); - } - - @Override - protected Object openDialogBox(Control cellEditorWindow) { - if (mUiResourceAttribute != null) { - String currentValue = (String)getValue(); - return mUiResourceAttribute.showDialog(cellEditorWindow.getShell(), currentValue); - } - - return null; - } - - @Override - protected void doSetValue(Object value) { - if (value instanceof UiResourceAttributeNode) { - mUiResourceAttribute = (UiResourceAttributeNode)value; - super.doSetValue(mUiResourceAttribute.getCurrentValue()); - return; - } - - super.doSetValue(value); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/SectionHelper.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/SectionHelper.java deleted file mode 100644 index 7942024..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/SectionHelper.java +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; - -import org.eclipse.jface.text.DefaultInformationControl; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseTrackListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.forms.SectionPart; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; -import org.eclipse.ui.forms.widgets.TableWrapData; -import org.eclipse.ui.forms.widgets.TableWrapLayout; - -import java.lang.reflect.Method; - -/** - * Helper for the AndroidManifest form editor. - * - * Helps create a new SectionPart with sensible default parameters, - * create default layout or add typical widgets. - * - * IMPORTANT: This is NOT a generic class. It makes a lot of assumptions on the - * UI as used by the form editor for the AndroidManifest. - * - * TODO: Consider moving to a common package. - */ -public final class SectionHelper { - - /** - * Utility class that derives from SectionPart, constructs the Section with - * sensible defaults (with a title and a description) and provide some shorthand - * methods for creating typically UI (label and text, form text.) - */ - static public class ManifestSectionPart extends SectionPart { - - /** - * Construct a SectionPart that uses a title bar and a description. - * It's up to the caller to call setText() and setDescription(). - *

              - * The section style includes a description and a title bar by default. - * - * @param body The parent (e.g. FormPage body) - * @param toolkit Form Toolkit - */ - public ManifestSectionPart(Composite body, FormToolkit toolkit) { - this(body, toolkit, 0, false); - } - - /** - * Construct a SectionPart that uses a title bar and a description. - * It's up to the caller to call setText() and setDescription(). - *

              - * The section style includes a description and a title bar by default. - * You can add extra styles, like Section.TWISTIE. - * - * @param body The parent (e.g. FormPage body). - * @param toolkit Form Toolkit. - * @param extra_style Extra styles (on top of description and title bar). - * @param use_description True if the Section.DESCRIPTION style should be added. - */ - public ManifestSectionPart(Composite body, FormToolkit toolkit, - int extra_style, boolean use_description) { - super(body, toolkit, extra_style | - Section.TITLE_BAR | - (use_description ? Section.DESCRIPTION : 0)); - } - - // Create non-static methods of helpers just for convenience - - /** - * Creates a new composite with a TableWrapLayout set with a given number of columns. - * - * If the parent composite is a Section, the new composite is set as a client. - * - * @param toolkit Form Toolkit - * @param numColumns Desired number of columns. - * @return The new composite. - */ - public Composite createTableLayout(FormToolkit toolkit, int numColumns) { - return SectionHelper.createTableLayout(getSection(), toolkit, numColumns); - } - - /** - * Creates a label widget. - * If the parent layout if a TableWrapLayout, maximize it to span over all columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param label The string for the label. - * @param tooltip An optional tooltip for the label and text. Can be null. - * @return The new created label - */ - public Label createLabel(Composite parent, FormToolkit toolkit, String label, - String tooltip) { - return SectionHelper.createLabel(parent, toolkit, label, tooltip); - } - - /** - * Creates two widgets: a label and a text field. - * - * This expects the parent composite to have a TableWrapLayout with 2 columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param label The string for the label. - * @param value The initial value of the text field. Can be null. - * @param tooltip An optional tooltip for the label and text. Can be null. - * @return The new created Text field (the label is not returned) - */ - public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label, - String value, String tooltip) { - return SectionHelper.createLabelAndText(parent, toolkit, label, value, tooltip); - } - - /** - * Creates a FormText widget. - * - * This expects the parent composite to have a TableWrapLayout with 2 columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param isHtml True if the form text will contain HTML that must be interpreted as - * rich text (i.e. parse tags & expand URLs). - * @param label The string for the label. - * @param setupLayoutData indicates whether the created form text receives a TableWrapData - * through the setLayoutData method. In some case, creating it will make the table parent - * huge, which we don't want. - * @return The new created FormText. - */ - public FormText createFormText(Composite parent, FormToolkit toolkit, boolean isHtml, - String label, boolean setupLayoutData) { - return SectionHelper.createFormText(parent, toolkit, isHtml, label, setupLayoutData); - } - - /** - * Forces the section to recompute its layout and redraw. - * This is needed after the content of the section has been changed at runtime. - */ - public void layoutChanged() { - Section section = getSection(); - - // Calls getSection().reflow(), which is the same that Section calls - // when the expandable state is changed and the height changes. - // Since this is protected, some reflection is needed to invoke it. - try { - Method reflow; - reflow = section.getClass().getDeclaredMethod("reflow", (Class[])null); - reflow.setAccessible(true); - reflow.invoke(section); - } catch (Exception e) { - EditorsPlugin.log(e, "Error when invoking Section.reflow"); - } - - section.layout(true /* changed */, true /* all */); - } - } - - /** - * Creates a new composite with a TableWrapLayout set with a given number of columns. - * - * If the parent composite is a Section, the new composite is set as a client. - * - * @param composite The parent (e.g. a Section or SectionPart) - * @param toolkit Form Toolkit - * @param numColumns Desired number of columns. - * @return The new composite. - */ - static public Composite createTableLayout(Composite composite, FormToolkit toolkit, - int numColumns) { - Composite table = toolkit.createComposite(composite); - TableWrapLayout layout = new TableWrapLayout(); - layout.numColumns = numColumns; - table.setLayout(layout); - toolkit.paintBordersFor(table); - if (composite instanceof Section) { - ((Section) composite).setClient(table); - } - return table; - } - - /** - * Creates a new composite with a GridLayout set with a given number of columns. - * - * If the parent composite is a Section, the new composite is set as a client. - * - * @param composite The parent (e.g. a Section or SectionPart) - * @param toolkit Form Toolkit - * @param numColumns Desired number of columns. - * @return The new composite. - */ - static public Composite createGridLayout(Composite composite, FormToolkit toolkit, - int numColumns) { - Composite grid = toolkit.createComposite(composite); - GridLayout layout = new GridLayout(); - layout.numColumns = numColumns; - grid.setLayout(layout); - toolkit.paintBordersFor(grid); - if (composite instanceof Section) { - ((Section) composite).setClient(grid); - } - return grid; - } - - /** - * Creates two widgets: a label and a text field. - * - * This expects the parent composite to have a TableWrapLayout with 2 columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param label_text The string for the label. - * @param value The initial value of the text field. Can be null. - * @param tooltip An optional tooltip for the label and text. Can be null. - * @return The new created Text field (the label is not returned) - */ - static public Text createLabelAndText(Composite parent, FormToolkit toolkit, String label_text, - String value, String tooltip) { - Label label = toolkit.createLabel(parent, label_text); - label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - Text text = toolkit.createText(parent, value); - text.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); - - addControlTooltip(label, tooltip); - return text; - } - - /** - * Creates a label widget. - * If the parent layout if a TableWrapLayout, maximize it to span over all columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param label_text The string for the label. - * @param tooltip An optional tooltip for the label and text. Can be null. - * @return The new created label - */ - static public Label createLabel(Composite parent, FormToolkit toolkit, String label_text, - String tooltip) { - Label label = toolkit.createLabel(parent, label_text); - - TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); - if (parent.getLayout() instanceof TableWrapLayout) { - twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; - } - label.setLayoutData(twd); - - addControlTooltip(label, tooltip); - return label; - } - - /** - * Associates a tooltip with a control. - * - * This mirrors the behavior from org.eclipse.pde.internal.ui.editor.text.PDETextHover - * - * @param control The control to which associate the tooltip. - * @param tooltip The tooltip string. Can use \n for multi-lines. Will not display if null. - */ - static public void addControlTooltip(final Control control, String tooltip) { - if (control == null || tooltip == null || tooltip.length() == 0) { - return; - } - - // Some kinds of controls already properly implement tooltip display. - if (control instanceof Button) { - control.setToolTipText(tooltip); - return; - } - - control.setToolTipText(null); - - final DefaultInformationControl ic = new DefaultInformationControl(control.getShell()); - ic.setInformation(tooltip); - Point sz = ic.computeSizeHint(); - ic.setSize(sz.x, sz.y); - ic.setVisible(false); // initially hidden - - control.addMouseTrackListener(new MouseTrackListener() { - public void mouseEnter(MouseEvent e) { - } - - public void mouseExit(MouseEvent e) { - ic.setVisible(false); - } - - public void mouseHover(MouseEvent e) { - ic.setLocation(control.toDisplay(10, 25)); // same offset as in PDETextHover - ic.setVisible(true); - } - }); - } - - /** - * Creates a FormText widget. - * - * This expects the parent composite to have a TableWrapLayout with 2 columns. - * - * @param parent The parent (e.g. composite from CreateTableLayout()) - * @param toolkit Form Toolkit - * @param isHtml True if the form text will contain HTML that must be interpreted as - * rich text (i.e. parse tags & expand URLs). - * @param label The string for the label. - * @param setupLayoutData indicates whether the created form text receives a TableWrapData - * through the setLayoutData method. In some case, creating it will make the table parent - * huge, which we don't want. - * @return The new created FormText. - */ - static public FormText createFormText(Composite parent, FormToolkit toolkit, - boolean isHtml, String label, boolean setupLayoutData) { - FormText text = toolkit.createFormText(parent, true /* track focus */); - if (setupLayoutData) { - TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); - twd.maxWidth = AndroidEditor.TEXT_WIDTH_HINT; - if (parent.getLayout() instanceof TableWrapLayout) { - twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; - } - text.setLayoutData(twd); - } - text.setWhitespaceNormalized(true); - text.setText(label, isHtml /* parseTags */, isHtml /* expandURLs */); - return text; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java deleted file mode 100644 index 2fe5783..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/TextValueCellEditor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.editors.ui; - -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; - -import org.eclipse.jface.viewers.TextCellEditor; -import org.eclipse.swt.widgets.Composite; - -/** - * TextCellEditor able to receive a {@link UiAttributeNode} in the {@link #setValue(Object)} - * method. - */ -public class TextValueCellEditor extends TextCellEditor { - - public TextValueCellEditor(Composite parent) { - super(parent); - } - - @Override - protected void doSetValue(Object value) { - if (value instanceof UiAttributeNode) { - super.doSetValue(((UiAttributeNode)value).getCurrentValue()); - return; - } - - super.doSetValue(value); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/UiElementPart.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/UiElementPart.java deleted file mode 100644 index 69adebd..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/UiElementPart.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.manifest.ManifestEditor; -import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; - -/** - * Generic page's section part that displays all attributes of a given {@link UiElementNode}. - *

              - * This part is designed to be displayed in a page that has a table column layout. - * It is linked to a specific {@link UiElementNode} and automatically displays all of its - * attributes, manages its dirty state and commits the attributes when necessary. - *

              - * No derivation is needed unless the UI or workflow needs to be extended. - */ -public class UiElementPart extends ManifestSectionPart { - - /** A reference to the container editor */ - private ManifestEditor mEditor; - /** The {@link UiElementNode} manipulated by this SectionPart. It can be null. */ - private UiElementNode mUiElementNode; - /** Table that contains all the attributes */ - private Composite mTable; - - public UiElementPart(Composite body, FormToolkit toolkit, ManifestEditor editor, - UiElementNode uiElementNode, String sectionTitle, String sectionDescription, - int extra_style) { - super(body, toolkit, extra_style, sectionDescription != null); - mEditor = editor; - mUiElementNode = uiElementNode; - setupSection(sectionTitle, sectionDescription); - - if (uiElementNode == null) { - // This is serious and should never happen. Instead of crashing, simply abort. - // There will be no UI, which will prevent further damage. - EditorsPlugin.log(IStatus.ERROR, "Missing node to edit!"); //$NON-NLS-1$ - return; - } - } - - /** - * Returns the Editor associated with this part. - */ - public ManifestEditor getEditor() { - return mEditor; - } - - /** - * Returns the {@link UiElementNode} associated with this part. - */ - public UiElementNode getUiElementNode() { - return mUiElementNode; - } - - /** - * Changes the element node handled by this part. - * - * @param uiElementNode The new element node for the part. - */ - public void setUiElementNode(UiElementNode uiElementNode) { - mUiElementNode = uiElementNode; - } - - /** - * Initializes the form part. - *

              - * This is called by the owning managed form as soon as the part is added to the form, - * which happens right after the part is actually created. - */ - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - createFormControls(form); - } - - /** - * Setup the section that contains this part. - *

              - * This is called by the constructor to set the section's title and description - * with parameters given in the constructor. - *
              - * Derived class override this if needed, however in most cases the default - * implementation should be enough. - * - * @param sectionTitle The section part's title - * @param sectionDescription The section part's description - */ - protected void setupSection(String sectionTitle, String sectionDescription) { - Section section = getSection(); - section.setText(sectionTitle); - section.setDescription(sectionDescription); - } - - /** - * Create the controls to edit the attributes for the given ElementDescriptor. - *

              - * This MUST not be called by the constructor. Instead it must be called from - * initialize (i.e. right after the form part is added to the managed form.) - *

              - * Derived classes can override this if necessary. - * - * @param managedForm The owner managed form - */ - protected void createFormControls(IManagedForm managedForm) { - setTable(createTableLayout(managedForm.getToolkit(), 2 /* numColumns */)); - - createUiAttributes(managedForm); - } - - /** - * Sets the table where the attribute UI needs to be created. - */ - protected void setTable(Composite table) { - mTable = table; - } - - /** - * Returns the table where the attribute UI needs to be created. - */ - protected Composite getTable() { - return mTable; - } - - /** - * Add all the attribute UI widgets into the underlying table layout. - * - * @param managedForm The owner managed form - */ - protected void createUiAttributes(IManagedForm managedForm) { - Composite table = getTable(); - if (table == null || managedForm == null) { - return; - } - - // Remove any old UI controls - for (Control c : table.getChildren()) { - c.dispose(); - } - - fillTable(table, managedForm); - - // Tell the section that the layout has changed. - layoutChanged(); - } - - /** - * Actually fills the table. - * This is called by {@link #createUiAttributes(IManagedForm)} to populate the new - * table. The default implementation is to use - * {@link #insertUiAttributes(UiElementNode, Composite, IManagedForm)} to actually - * place the attributes of the default {@link UiElementNode} in the table. - *

              - * Derived classes can override this to add controls in the table before or after. - * - * @param table The table to fill. It must have 2 columns. - * @param managedForm The managed form for new controls. - */ - protected void fillTable(Composite table, IManagedForm managedForm) { - int inserted = insertUiAttributes(mUiElementNode, table, managedForm); - - if (inserted == 0) { - createLabel(table, managedForm.getToolkit(), - "No attributes to display, waiting for SDK to finish loading...", - null /* tooltip */ ); - } - } - - /** - * Insert the UI attributes of the given {@link UiElementNode} in the given table. - * - * @param uiNode The {@link UiElementNode} that contains the attributes to display. - * Must not be null. - * @param table The table to fill. It must have 2 columns. - * @param managedForm The managed form for new controls. - * @return The number of UI attributes inserted. It is >= 0. - */ - protected int insertUiAttributes(UiElementNode uiNode, Composite table, IManagedForm managedForm) { - if (uiNode == null || table == null || managedForm == null) { - return 0; - } - - // To iterate over all attributes, we use the {@link ElementDescriptor} instead - // of the {@link UiElementNode} because the attributes' order is guaranteed in the - // descriptor but not in the node itself. - AttributeDescriptor[] attr_desc_list = uiNode.getAttributeDescriptors(); - for (AttributeDescriptor attr_desc : attr_desc_list) { - if (attr_desc instanceof XmlnsAttributeDescriptor) { - // Do not show hidden attributes - continue; - } - - UiAttributeNode ui_attr = uiNode.findUiAttribute(attr_desc); - if (ui_attr != null) { - ui_attr.createUiControl(table, managedForm); - } else { - // The XML has an extra attribute which wasn't declared in - // AndroidManifestDescriptors. This is not a problem, we just ignore it. - EditorsPlugin.log(IStatus.WARNING, - "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ - attr_desc.getXmlLocalName(), - uiNode.getDescriptor().getXmlName()); - } - } - return attr_desc_list.length; - } - - /** - * Tests whether the part is dirty i.e. its widgets have state that is - * newer than the data in the model. - *

              - * This is done by iterating over all attributes and updating the super's - * internal dirty flag. Stop once at least one attribute is dirty. - * - * @return true if the part is dirty, false - * otherwise. - */ - @Override - public boolean isDirty() { - if (mUiElementNode != null && !super.isDirty()) { - for (UiAttributeNode ui_attr : mUiElementNode.getUiAttributes()) { - if (ui_attr.isDirty()) { - markDirty(); - break; - } - } - } - return super.isDirty(); - } - - /** - * If part is displaying information loaded from a model, this method - * instructs it to commit the new (modified) data back into the model. - * - * @param onSave - * indicates if commit is called during 'save' operation or for - * some other reason (for example, if form is contained in a - * wizard or a multi-page editor and the user is about to leave - * the page). - */ - @Override - public void commit(boolean onSave) { - if (mUiElementNode != null) { - mEditor.editXmlModel(new Runnable() { - public void run() { - for (UiAttributeNode ui_attr : mUiElementNode.getUiAttributes()) { - ui_attr.commit(); - } - } - }); - } - - // We need to call super's commit after we synchronized the nodes to make sure we - // reset the dirty flag after all the side effects from committing have occurred. - super.commit(onSave); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java deleted file mode 100644 index 7e38032..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/CopyCutAction.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.apache.xml.serialize.Method; -import org.apache.xml.serialize.OutputFormat; -import org.apache.xml.serialize.XMLSerializer; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.swt.dnd.Clipboard; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; -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.document.NodeContainer; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import java.io.StringWriter; - - -/** - * Provides Cut and Copy actions for the tree nodes. - */ -public class CopyCutAction extends Action { - private UiElementNode mUiNode; - private boolean mPerformCut; - private final AndroidEditor mEditor; - private final Clipboard mClipboard; - private final ICommitXml mXmlCommit; - - /** - * Creates a new Copy or Cut action. - * - * @param ui_node The UI node to cut or copy. It *must* have a non-null XML node. - * @param perform_cut True if the operation is cut, false if it is copy. - */ - public CopyCutAction(AndroidEditor editor, Clipboard clipboard, ICommitXml xmlCommit, - UiElementNode ui_node, boolean perform_cut) { - super(perform_cut ? "Cut" : "Copy"); - mEditor = editor; - mClipboard = clipboard; - mXmlCommit = xmlCommit; - - ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); - if (perform_cut) { - setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT)); - setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT)); - setDisabledImageDescriptor( - images.getImageDescriptor(ISharedImages.IMG_TOOL_CUT_DISABLED)); - } else { - setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY)); - setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY)); - setDisabledImageDescriptor( - images.getImageDescriptor(ISharedImages.IMG_TOOL_COPY_DISABLED)); - } - - mUiNode = ui_node; - mPerformCut = perform_cut; - } - - /** - * Performs the cut or copy action. - * First an XML serializer is used to turn the existing XML node into a valid - * XML fragment, which is added as text to the clipboard. - */ - @Override - public void run() { - super.run(); - try { - String data = null; - - // Get the data directly from the editor. - - // Commit the current pages first, to make sure the XML is in sync. - if (mXmlCommit != null) { - mXmlCommit.commitPendingXmlChanges(); - } - - // Committing may change the XML structure. - Node xml_node = mUiNode.getXmlNode(); - if (xml_node == null) { - return; - } - - IStructuredModel model = mEditor.getModelForRead(); - try { - IStructuredDocument sse_doc = mEditor.getStructuredDocument(); - if (xml_node instanceof NodeContainer) { - // The easy way to get the source of an SSE XML node. - data = ((NodeContainer) xml_node).getSource(); - } else if (xml_node instanceof IndexedRegion && sse_doc != null) { - // Try harder. - IndexedRegion region = (IndexedRegion) xml_node; - int start = region.getStartOffset(); - int end = region.getEndOffset(); - - if (end > start) { - data = sse_doc.get(start, end - start); - } - } - } catch (BadLocationException e) { - // the region offset was invalid. ignore. - } finally { - model.releaseFromRead(); - } - - // In the unlikely event that IStructuredDocument failed to extract the text - // directly from the editor, try to fall back on a direct XML serialization - // of the XML node. This uses the generic Node interface with no SSE tricks. - if (data == null) { - StringWriter sw = new StringWriter(); - XMLSerializer serializer = new XMLSerializer(sw, - new OutputFormat(Method.XML, - OutputFormat.Defaults.Encoding /* utf-8 */, - true /* indent */)); - // Serialize will throw an IOException if it fails. - serializer.serialize((Element) xml_node); - data = sw.toString(); - } - - if (data != null && data.length() > 0) { - mClipboard.setContents( - new Object[] { data }, - new Transfer[] { TextTransfer.getInstance() }); - if (mPerformCut) { - mUiNode.deleteXmlNode(); - } - } - } catch (Exception e) { - EditorsPlugin.log(e, "CopyCutAction failed for UI node %1$s", //$NON-NLS-1$ - mUiNode.getBreadcrumbTrailDescription(true)); - } - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java deleted file mode 100644 index 8b6aa0e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/ICommitXml.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.editors.ui.tree; - -/** - * Interface for an object that can commit its changes to the underlying XML model - */ -public interface ICommitXml { - - /** Commits pending data to the underlying XML model. */ - public void commitPendingXmlChanges(); - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java deleted file mode 100644 index 00e44ab..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/NewItemSelectionDialog.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui.tree; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; -import org.eclipse.ui.dialogs.ISelectionStatusValidator; - -import java.util.Arrays; - -/** - * A selection dialog to select the type of the new element node to - * created, either in the application node or the selected sub node. - */ -public class NewItemSelectionDialog extends AbstractElementListSelectionDialog { - - /** The UI node selected in the tree view before creating the new item selection dialog. - * Can be null -- which means new items must be created in the root_node. */ - private UiElementNode mSelectedUiNode; - /** The root node chosen by the user, either root_node or the one passed - * to the constructor if not null */ - private UiElementNode mChosenRootNode; - private UiElementNode mLocalRootNode; - /** The descriptor of the elements to be displayed as root in this tree view. All elements - * of the same type in the root will be displayed. */ - private ElementDescriptor[] mDescriptorFilters; - - /** - * Creates the new item selection dialog. - * - * @param shell The parent shell for the list. - * @param labelProvider ILabelProvider for the list. - * @param descriptorFilters The element allows at the root of the tree - * @param ui_node The selected node, or null if none is selected. - * @param root_node The root of the Ui Tree, either the UiDocumentNode or a sub-node. - */ - public NewItemSelectionDialog(Shell shell, ILabelProvider labelProvider, - ElementDescriptor[] descriptorFilters, - UiElementNode ui_node, - UiElementNode root_node) { - super(shell, labelProvider); - mDescriptorFilters = descriptorFilters; - mLocalRootNode = root_node; - - // Only accept the UI node if it is not the UI root node and it can have children. - // If the node cannot have children, select its parent as a potential target. - if (ui_node != null && ui_node != mLocalRootNode) { - if (ui_node.getDescriptor().hasChildren()) { - mSelectedUiNode = ui_node; - } else { - UiElementNode parent = ui_node.getUiParent(); - if (parent != null && parent != mLocalRootNode) { - mSelectedUiNode = parent; - } - } - } - - setHelpAvailable(false); - setMultipleSelection(false); - - setValidator(new ISelectionStatusValidator() { - public IStatus validate(Object[] selection) { - if (selection.length == 1 && selection[0] instanceof ViewElementDescriptor) { - return new Status(IStatus.OK, // severity - AndroidConstants.EDITORS_PLUGIN_ID, //plugin id - IStatus.OK, // code - ((ViewElementDescriptor) selection[0]).getCanonicalClassName(), //msg - null); // exception - } else if (selection.length == 1 && selection[0] instanceof ElementDescriptor) { - return new Status(IStatus.OK, // severity - AndroidConstants.EDITORS_PLUGIN_ID, //plugin id - IStatus.OK, // code - "", //$NON-NLS-1$ // msg - null); // exception - } else { - return new Status(IStatus.ERROR, // severity - AndroidConstants.EDITORS_PLUGIN_ID, //plugin id - IStatus.ERROR, // code - "Invalid selection", // msg, translatable - null); // exception - } - } - }); - } - - /** - * @return The root node selected by the user, either root node or the - * one passed to the constructor if not null. - */ - public UiElementNode getChosenRootNode() { - return mChosenRootNode; - } - - /** - * Internal helper to compute the result. Returns the selection from - * the list view, if any. - */ - @Override - protected void computeResult() { - setResult(Arrays.asList(getSelectedElements())); - } - - /** - * Creates the dialog area. - * - * First add a radio area, which may be either 2 radio controls or - * just a message area if there's only one choice (the app root node). - * - * Then uses the default from the AbstractElementListSelectionDialog - * which is to add both a filter text and a filtered list. Adding both - * is necessary (since the base class accesses both internal directly - * fields without checking for null pointers.) - * - * Finally sets the initial selection list. - */ - @Override - protected Control createDialogArea(Composite parent) { - Composite contents = (Composite) super.createDialogArea(parent); - - createRadioControl(contents); - createFilterText(contents); - createFilteredList(contents); - - // Initialize the list state. - // This must be done after the filtered list as been created. - chooseNode(mChosenRootNode); - setSelection(getInitialElementSelections().toArray()); - return contents; - } - - /** - * Creates the message text widget and sets layout data. - * @param content the parent composite of the message area. - */ - private Composite createRadioControl(Composite content) { - - if (mSelectedUiNode != null) { - Button radio1 = new Button(content, SWT.RADIO); - radio1.setText(String.format("Create a new element at the top level, in %1$s.", - mLocalRootNode.getShortDescription())); - - Button radio2 = new Button(content, SWT.RADIO); - radio2.setText(String.format("Create a new element in the selected element, %1$s.", - mSelectedUiNode.getBreadcrumbTrailDescription(false /* include_root */))); - - // Set the initial selection before adding the listeners - // (they can't be run till the filtered list has been created) - radio1.setSelection(false); - radio2.setSelection(true); - mChosenRootNode = mSelectedUiNode; - - radio1.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - chooseNode(mLocalRootNode); - } - }); - - radio2.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - chooseNode(mSelectedUiNode); - } - }); - } else { - setMessage(String.format("Create a new element at the top level, in %1$s.", - mLocalRootNode.getShortDescription())); - createMessageArea(content); - - mChosenRootNode = mLocalRootNode; - } - - return content; - } - - /** - * Internal helper to remember the root node choosen by the user. - * It also sets the list view to the adequate list of children that can - * be added to the chosen root node. - * - * If the chosen root node is mLocalRootNode and a descriptor filter was specified - * when creating the master-detail part, we use this as the set of nodes that - * can be created on the root node. - * - * @param ui_node The chosen root node, either mLocalRootNode or - * mSelectedUiNode. - */ - private void chooseNode(UiElementNode ui_node) { - mChosenRootNode = ui_node; - - if (ui_node == mLocalRootNode && - mDescriptorFilters != null && - mDescriptorFilters.length != 0) { - setListElements(mDescriptorFilters); - } else { - setListElements(ui_node.getDescriptor().getChildren()); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java deleted file mode 100644 index 68580b0..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/PasteAction.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.swt.dnd.Clipboard; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; -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.sse.core.internal.provisional.text.IStructuredDocumentRegion; -import org.eclipse.wst.xml.core.internal.document.NodeContainer; -import org.w3c.dom.Node; - - -/** - * Provides Paste operation for the tree nodes - */ -public class PasteAction extends Action { - private UiElementNode mUiNode; - private final AndroidEditor mEditor; - private final Clipboard mClipboard; - - public PasteAction(AndroidEditor editor, Clipboard clipboard, UiElementNode ui_node) { - super("Paste"); - mEditor = editor; - mClipboard = clipboard; - - ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); - setImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE)); - setHoverImageDescriptor(images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE)); - setDisabledImageDescriptor( - images.getImageDescriptor(ISharedImages.IMG_TOOL_PASTE_DISABLED)); - - mUiNode = ui_node; - } - - /** - * Performs the paste operation. - */ - @Override - public void run() { - super.run(); - - final String data = (String) mClipboard.getContents(TextTransfer.getInstance()); - if (data != null) { - IStructuredModel model = mEditor.getModelForEdit(); - try { - IStructuredDocument sse_doc = mEditor.getStructuredDocument(); - if (sse_doc != null) { - if (mUiNode.getDescriptor().hasChildren()) { - // This UI Node can have children. The new XML is - // inserted as the first child. - - if (mUiNode.getUiChildren().size() > 0) { - // There's already at least one child, so insert right before it. - Node xml_node = mUiNode.getUiChildren().get(0).getXmlNode(); - if (xml_node instanceof IndexedRegion) { // implies xml_node != null - IndexedRegion region = (IndexedRegion) xml_node; - sse_doc.replace(region.getStartOffset(), 0, data); - return; // we're done, no need to try the other cases - } - } - - // If there's no first XML node child. Create one by - // inserting at the end of the *start* tag. - Node xml_node = mUiNode.getXmlNode(); - if (xml_node instanceof NodeContainer) { - NodeContainer container = (NodeContainer) xml_node; - IStructuredDocumentRegion start_tag = - container.getStartStructuredDocumentRegion(); - if (start_tag != null) { - sse_doc.replace(start_tag.getEndOffset(), 0, data); - return; // we're done, no need to try the other case - } - } - } - - // This UI Node doesn't accept children. The new XML is inserted as the - // next sibling. This also serves as a fallback if all the previous - // attempts failed. However, this is not possible if the current node - // has for parent a document -- an XML document can only have one root, - // with no siblings. - if (!(mUiNode.getUiParent() instanceof UiDocumentNode)) { - Node xml_node = mUiNode.getXmlNode(); - if (xml_node instanceof IndexedRegion) { - IndexedRegion region = (IndexedRegion) xml_node; - sse_doc.replace(region.getEndOffset(), 0, data); - } - } - } - - } catch (BadLocationException e) { - EditorsPlugin.log(e, "ParseAction failed for UI Node %2$s, content '%1$s'", //$NON-NLS-1$ - mUiNode.getBreadcrumbTrailDescription(true), data); - } finally { - model.releaseFromEdit(); - } - } - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java deleted file mode 100644 index b3d0755..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiActions.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.swt.widgets.Shell; -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -/** - * Performs basic actions on an XML tree: add node, remove node, move up/down. - */ -public abstract class UiActions implements ICommitXml { - - public UiActions() { - } - - //--------------------- - // Actual implementations must override these to provide specific hooks - - /** Returns the UiDocumentNode for the current model. */ - abstract protected UiElementNode getRootNode(); - - /** Commits pending data before the XML model is modified. */ - abstract public void commitPendingXmlChanges(); - - /** - * Utility method to select an outline item based on its model node - * - * @param ui_node The node to select. Can be null (in which case nothing should happen) - */ - abstract protected void selectUiNode(UiElementNode ui_node); - - //--------------------- - - /** - * Called when the "Add..." button next to the tree view is selected. - *

              - * This simplified version of doAdd does not support descriptor filters and creates - * a new {@link UiModelTreeLabelProvider} for each call. - */ - public void doAdd(UiElementNode ui_node, Shell shell) { - doAdd(ui_node, null /* descriptorFilters */, shell, new UiModelTreeLabelProvider()); - } - - /** - * Called when the "Add..." button next to the tree view is selected. - * - * Displays a selection dialog that lets the user select which kind of node - * to create, depending on the current selection. - */ - public void doAdd(UiElementNode ui_node, - ElementDescriptor[] descriptorFilters, - Shell shell, ILabelProvider labelProvider) { - // If the root node is a document with already a root, use it as the root node - UiElementNode root_node = getRootNode(); - if (root_node instanceof UiDocumentNode && - root_node.getUiChildren().size() > 0) { - root_node = root_node.getUiChildren().get(0); - } - - NewItemSelectionDialog dlg = new NewItemSelectionDialog( - shell, - labelProvider, - descriptorFilters, - ui_node, root_node); - dlg.open(); - Object[] results = dlg.getResult(); - if (results != null && results.length > 0) { - UiElementNode ui_new = addNewTreeElement(dlg.getChosenRootNode(), - (ElementDescriptor) results[0]); - - selectUiNode(ui_new); - } - } - - /** - * Called when the "Remove" button is selected. - * - * If the tree has a selection, remove it. - * This simply deletes the XML node attached to the UI node: when the XML model fires the - * update event, the tree will get refreshed. - */ - public void doRemove(final UiElementNode ui_node, Shell shell) { - if (MessageDialog.openQuestion(shell, - "Remove element from Android XML", // title - String.format("Do you really want to remove %1$s?", - ui_node.getBreadcrumbTrailDescription(false /* include_root */)))) { - commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { - public void run() { - UiElementNode previous = ui_node.getUiPreviousSibling(); - UiElementNode parent = ui_node.getUiParent(); - - // delete node - ui_node.deleteXmlNode(); - - // try to select the previous sibling or the parent - if (previous != null) { - selectUiNode(previous); - } else if (parent != null) { - selectUiNode(parent); - } - } - }); - } - } - - /** - * Called when the "Up" button is selected. - *

              - * If the tree has a selection, move it up, either in the child list or as the last child - * of the previous parent. - */ - public void doUp(final UiElementNode ui_node) { - final Node[] select_xml_node = { null }; - // the node will move either up to its parent or grand-parent - UiElementNode search_root = ui_node.getUiParent(); - if (search_root != null && search_root.getUiParent() != null) { - search_root = search_root.getUiParent(); - } - - commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { - public void run() { - Node xml_node = ui_node.getXmlNode(); - if (xml_node != null) { - Node xml_parent = xml_node.getParentNode(); - if (xml_parent != null) { - UiElementNode ui_prev = ui_node.getUiPreviousSibling(); - if (ui_prev != null && ui_prev.getXmlNode() != null) { - // This node is not the first one of the parent, so it can be - // removed and then inserted before its previous sibling. - // If the previous sibling can have children, though, then it - // is inserted at the end of the children list. - Node xml_prev = ui_prev.getXmlNode(); - if (ui_prev.getDescriptor().hasChildren()) { - xml_prev.appendChild(xml_parent.removeChild(xml_node)); - select_xml_node[0] = xml_node; - } else { - xml_parent.insertBefore( - xml_parent.removeChild(xml_node), - xml_prev); - select_xml_node[0] = xml_node; - } - } else if (!(xml_parent instanceof Document) && - xml_parent.getParentNode() != null && - !(xml_parent.getParentNode() instanceof Document)) { - // If the node is the first one of the child list of its - // parent, move it up in the hierarchy as previous sibling - // to the parent. This is only possible if the parent of the - // parent is not a document. - Node grand_parent = xml_parent.getParentNode(); - grand_parent.insertBefore(xml_parent.removeChild(xml_node), - xml_parent); - select_xml_node[0] = xml_node; - } - } - } - } - }); - - if (select_xml_node[0] == null) { - // The XML node has not been moved, we can just select the same UI node - selectUiNode(ui_node); - } else { - // The XML node has moved. At this point the UI model has been reloaded - // and the XML node has been affected to a new UI node. Find that new UI - // node and select it. - if (search_root == null) { - search_root = ui_node.getUiRoot(); - } - if (search_root != null) { - selectUiNode(search_root.findXmlNode(select_xml_node[0])); - } - } - } - - /** - * Called when the "Down" button is selected. - * - * If the tree has a selection, move it down, either in the same child list or as the - * first child of the next parent. - */ - public void doDown(final UiElementNode ui_node) { - final Node[] select_xml_node = { null }; - // the node will move either down to its parent or grand-parent - UiElementNode search_root = ui_node.getUiParent(); - if (search_root != null && search_root.getUiParent() != null) { - search_root = search_root.getUiParent(); - } - - commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { - public void run() { - Node xml_node = ui_node.getXmlNode(); - if (xml_node != null) { - Node xml_parent = xml_node.getParentNode(); - if (xml_parent != null) { - UiElementNode ui_next = ui_node.getUiNextSibling(); - if (ui_next != null && ui_next.getXmlNode() != null) { - // This node is not the last one of the parent, so it can be - // removed and then inserted after its next sibling. - // If the next sibling is a node that can have children, though, - // then the node is inserted as the first child. - Node xml_next = ui_next.getXmlNode(); - if (ui_next.getDescriptor().hasChildren()) { - // Note: insertBefore works as append if the ref node is - // null, i.e. when the node doesn't have children yet. - xml_next.insertBefore(xml_parent.removeChild(xml_node), - xml_next.getFirstChild()); - select_xml_node[0] = xml_node; - } else { - // Insert "before after next" ;-) - xml_parent.insertBefore(xml_parent.removeChild(xml_node), - xml_next.getNextSibling()); - select_xml_node[0] = xml_node; - } - } else if (!(xml_parent instanceof Document) && - xml_parent.getParentNode() != null && - !(xml_parent.getParentNode() instanceof Document)) { - // This node is the last node of its parent. - // If neither the parent nor the grandparent is a document, - // then the node can be insert right after the parent. - Node grand_parent = xml_parent.getParentNode(); - grand_parent.insertBefore(xml_parent.removeChild(xml_node), - xml_parent.getNextSibling()); - select_xml_node[0] = xml_node; - } - } - } - } - }); - - if (select_xml_node[0] == null) { - // The XML node has not been moved, we can just select the same UI node - selectUiNode(ui_node); - } else { - // The XML node has moved. At this point the UI model has been reloaded - // and the XML node has been affected to a new UI node. Find that new UI - // node and select it. - if (search_root == null) { - search_root = ui_node.getUiRoot(); - } - if (search_root != null) { - selectUiNode(search_root.findXmlNode(select_xml_node[0])); - } - } - } - - //--------------------- - - /** - * Adds a new element of the given descriptor's type to the given UI parent node. - * - * This actually creates the corresponding XML node in the XML model, which in turn - * will refresh the current tree view. - * - * @param ui_parent An existing UI node or null to add to the tree root - * @param elementDescriptor The descriptor of the element to add - * @return The {@link UiElementNode} that has been added to the UI tree. - */ - private UiElementNode addNewTreeElement(UiElementNode ui_parent, - ElementDescriptor elementDescriptor) { - commitPendingXmlChanges(); - final UiElementNode ui_new = ui_parent.appendNewUiChild(elementDescriptor); - UiElementNode root_node = getRootNode(); - - root_node.getEditor().editXmlModel(new Runnable() { - public void run() { - DescriptorsUtils.setDefaultLayoutAttributes(ui_new); - ui_new.createXmlNode(); - } - }); - return ui_new; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java deleted file mode 100644 index 010e30e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiElementDetail.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui.tree; - -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; -import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; -import com.android.ide.eclipse.editors.uimodel.UiAttributeNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.ui.forms.IDetailsPage; -import org.eclipse.ui.forms.IFormPart; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.events.ExpansionEvent; -import org.eclipse.ui.forms.events.IExpansionListener; -import org.eclipse.ui.forms.widgets.FormText; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; -import org.eclipse.ui.forms.widgets.SharedScrolledComposite; -import org.eclipse.ui.forms.widgets.TableWrapData; -import org.eclipse.ui.forms.widgets.TableWrapLayout; -import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; - -import java.util.Collection; -import java.util.HashSet; - -/** - * Details page for the {@link UiElementNode} nodes in the tree view. - *

              - * See IDetailsBase for more details. - */ -class UiElementDetail implements IDetailsPage { - - /** The master-detail part, composed of a main tree and an auxiliary detail part */ - private ManifestSectionPart mMasterPart; - - private Section mMasterSection; - private UiElementNode mCurrentUiElementNode; - private Composite mCurrentTable; - private boolean mIsDirty; - - private IManagedForm mManagedForm; - - private final UiTreeBlock mTree; - - public UiElementDetail(UiTreeBlock tree) { - mTree = tree; - mMasterPart = mTree.getMasterPart(); - mManagedForm = mMasterPart.getManagedForm(); - } - - /* (non-java doc) - * Initializes the part. - */ - public void initialize(IManagedForm form) { - mManagedForm = form; - } - - /* (non-java doc) - * Creates the contents of the page in the provided parent. - */ - public void createContents(Composite parent) { - mMasterSection = createMasterSection(parent); - } - - /* (non-java doc) - * Called when the provided part has changed selection state. - *

              - * Only reply when our master part originates the selection. - */ - public void selectionChanged(IFormPart part, ISelection selection) { - if (part == mMasterPart && - !selection.isEmpty() && - selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - - Object first = tree_selection.getFirstElement(); - if (first instanceof UiElementNode) { - UiElementNode ui_node = (UiElementNode) first; - createUiAttributeControls(mManagedForm, ui_node); - } - } - } - - /* (non-java doc) - * Instructs it to commit the new (modified) data back into the model. - */ - public void commit(boolean onSave) { - - IStructuredModel model = mTree.getEditor().getModelForEdit(); - try { - // Notify the model we're about to change data... - model.aboutToChangeModel(); - - if (mCurrentUiElementNode != null) { - mCurrentUiElementNode.commit(); - } - - // Finally reset the dirty flag if everything was saved properly - mIsDirty = false; - } catch (Exception e) { - EditorsPlugin.log(e, "Detail node failed to commit XML attribute!"); //$NON-NLS-1$ - } finally { - // Notify the model we're done modifying it. This must *always* be executed. - model.changedModel(); - model.releaseFromEdit(); - } - } - - public void dispose() { - // pass - } - - - /* (non-java doc) - * Returns true if the part has been modified with respect to the data - * loaded from the model. - */ - public boolean isDirty() { - if (mCurrentUiElementNode != null && mCurrentUiElementNode.isDirty()) { - markDirty(); - } - return mIsDirty; - } - - public boolean isStale() { - // pass - return false; - } - - /** - * Called by the master part when the tree is refreshed after the framework resources - * have been reloaded. - */ - public void refresh() { - if (mCurrentTable != null) { - mCurrentTable.dispose(); - mCurrentTable = null; - } - mCurrentUiElementNode = null; - mMasterSection.getParent().pack(true /* changed */); - } - - public void setFocus() { - // pass - } - - public boolean setFormInput(Object input) { - // pass - return false; - } - - /** - * Creates a TableWrapLayout in the DetailsPage, which in turns contains a Section. - * - * All the UI should be created in a layout which parent is the mSection itself. - * The hierarchy is: - *

              -     * DetailPage
              -     * + TableWrapLayout
              -     *   + Section (with title/description && fill_grab horizontal)
              -     *     + TableWrapLayout [*]
              -     *       + Labels/Forms/etc... [*]
              -     * 
              - * Both items marked with [*] are created by the derived classes to fit their needs. - * - * @param parent Parent of the mSection (from createContents) - * @return The new Section - */ - private Section createMasterSection(Composite parent) { - TableWrapLayout layout = new TableWrapLayout(); - layout.topMargin = 0; - parent.setLayout(layout); - - FormToolkit toolkit = mManagedForm.getToolkit(); - Section section = toolkit.createSection(parent, Section.TITLE_BAR); - section.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.TOP)); - return section; - } - - /** - * Create the ui attribute controls to edit the attributes for the given - * ElementDescriptor. - *

              - * This is called by the constructor. - * Derived classes can override this if necessary. - * - * @param managedForm The managed form - */ - private void createUiAttributeControls( - final IManagedForm managedForm, - final UiElementNode ui_node) { - - final ElementDescriptor elem_desc = ui_node.getDescriptor(); - mMasterSection.setText(String.format("Attributes for %1$s", ui_node.getShortDescription())); - - if (mCurrentUiElementNode != ui_node) { - // Before changing the table, commit all dirty state. - if (mIsDirty) { - commit(false); - } - if (mCurrentTable != null) { - mCurrentTable.dispose(); - mCurrentTable = null; - } - - // To iterate over all attributes, we use the {@link ElementDescriptor} instead - // of the {@link UiElementNode} because the attributes order is guaranteed in the - // descriptor but not in the node itself. - AttributeDescriptor[] attr_desc_list = ui_node.getAttributeDescriptors(); - - // If the attribute list contains at least one SeparatorAttributeDescriptor, - // sub-sections will be used. This needs to be known early as it influences the - // creation of the master table. - boolean useSubsections = false; - for (AttributeDescriptor attr_desc : attr_desc_list) { - if (attr_desc instanceof SeparatorAttributeDescriptor) { - // Sub-sections will be used. The default sections should no longer be - useSubsections = true; - break; - } - } - - FormToolkit toolkit = managedForm.getToolkit(); - Composite masterTable = SectionHelper.createTableLayout(mMasterSection, - toolkit, useSubsections ? 1 : 2 /* numColumns */); - mCurrentTable = masterTable; - - mCurrentUiElementNode = ui_node; - - if (elem_desc.getTooltip() != null) { - String tooltip = DescriptorsUtils.formatFormText(elem_desc.getTooltip(), - elem_desc, - FrameworkResourceManager.getInstance().getDocumentationBaseUrl()); - - try { - FormText text = SectionHelper.createFormText(masterTable, toolkit, - true /* isHtml */, tooltip, true /* setupLayoutData */); - text.addHyperlinkListener(mTree.getEditor().createHyperlinkListener()); - Image icon = elem_desc.getIcon(); - if (icon != null) { - text.setImage(DescriptorsUtils.IMAGE_KEY, icon); - } - } catch(Exception e) { - // The FormText parser is really really basic and will fail as soon as the - // HTML javadoc is ever so slightly malformatted. - EditorsPlugin.log(e, - "Malformed javadoc, rejected by FormText for node %1$s: '%2$s'", //$NON-NLS-1$ - ui_node.getDescriptor().getXmlName(), - tooltip); - - // Fallback to a pure text tooltip, no fancy HTML - tooltip = DescriptorsUtils.formatTooltip(elem_desc.getTooltip()); - Label label = SectionHelper.createLabel(masterTable, toolkit, - tooltip, tooltip); - } - } - - Composite table = useSubsections ? null : masterTable; - - for (AttributeDescriptor attr_desc : attr_desc_list) { - if (attr_desc instanceof XmlnsAttributeDescriptor) { - // Do not show hidden attributes - continue; - } else if (table == null || attr_desc instanceof SeparatorAttributeDescriptor) { - String title = null; - if (attr_desc instanceof SeparatorAttributeDescriptor) { - // xmlName is actually the label of the separator - title = attr_desc.getXmlLocalName(); - } else { - title = String.format("Attributes from %1$s", elem_desc.getUiName()); - } - - table = createSubSectionTable(toolkit, masterTable, title); - if (attr_desc instanceof SeparatorAttributeDescriptor) { - continue; - } - } - - UiAttributeNode ui_attr = ui_node.findUiAttribute(attr_desc); - - if (ui_attr != null) { - ui_attr.createUiControl(table, managedForm); - - if (ui_attr.getCurrentValue() != null && - ui_attr.getCurrentValue().length() > 0) { - ((Section) table.getParent()).setExpanded(true); - } - } else { - // The XML has an extra unknown attribute. - // This is not expected to happen so it is ignored. - EditorsPlugin.log(IStatus.INFO, - "Attribute %1$s not declared in node %2$s, ignored.", //$NON-NLS-1$ - attr_desc.getXmlLocalName(), - ui_node.getDescriptor().getXmlName()); - } - } - - // Create a sub-section for the unknown attributes. - // It is initially hidden till there are some attributes to show here. - final Composite unknownTable = createSubSectionTable(toolkit, masterTable, - "Unknown XML Attributes"); - unknownTable.getParent().setVisible(false); // set section to not visible - final HashSet reference = new HashSet(); - - final IUiUpdateListener updateListener = new IUiUpdateListener() { - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - if (state == UiUpdateState.ATTR_UPDATED) { - updateUnknownAttributesSection(ui_node, unknownTable, managedForm, - reference); - } - } - }; - ui_node.addUpdateListener(updateListener); - - // remove the listener when the UI is disposed - unknownTable.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - ui_node.removeUpdateListener(updateListener); - } - }); - - updateUnknownAttributesSection(ui_node, unknownTable, managedForm, reference); - - mMasterSection.getParent().pack(true /* changed */); - } - } - - /** - * Create a sub Section and its embedding wrapper table with 2 columns. - * @return The table, child of a new section. - */ - private Composite createSubSectionTable(FormToolkit toolkit, - Composite masterTable, String title) { - - // The Section composite seems to ignore colspan when assigned a TableWrapData so - // if the parent is a table with more than one column an extra table with one column - // is inserted to respect colspan. - int parentNumCol = ((TableWrapLayout) masterTable.getLayout()).numColumns; - if (parentNumCol > 1) { - masterTable = SectionHelper.createTableLayout(masterTable, toolkit, 1); - TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); - twd.maxWidth = AndroidEditor.TEXT_WIDTH_HINT; - twd.colspan = parentNumCol; - masterTable.setLayoutData(twd); - } - - Composite table; - Section section = toolkit.createSection(masterTable, - Section.TITLE_BAR | Section.TWISTIE); - - // Add an expansion listener that will trigger a reflow on the parent - // ScrolledPageBook (which is actually a SharedScrolledComposite). This will - // recompute the correct size and adjust the scrollbar as needed. - section.addExpansionListener(new IExpansionListener() { - public void expansionStateChanged(ExpansionEvent e) { - reflowMasterSection(); - } - - public void expansionStateChanging(ExpansionEvent e) { - // pass - } - }); - - section.setText(title); - section.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, - TableWrapData.TOP)); - table = SectionHelper.createTableLayout(section, toolkit, 2 /* numColumns */); - return table; - } - - /** - * Reflow the parent ScrolledPageBook (which is actually a SharedScrolledComposite). - * This will recompute the correct size and adjust the scrollbar as needed. - */ - private void reflowMasterSection() { - for(Composite c = mMasterSection; c != null; c = c.getParent()) { - if (c instanceof SharedScrolledComposite) { - ((SharedScrolledComposite) c).reflow(true /* flushCache */); - break; - } - } - } - - /** - * Updates the unknown attributes section for the UI Node. - */ - private void updateUnknownAttributesSection(UiElementNode ui_node, - final Composite unknownTable, final IManagedForm managedForm, - HashSet reference) { - Collection ui_attrs = ui_node.getUnknownUiAttributes(); - Section section = ((Section) unknownTable.getParent()); - boolean needs_reflow = false; - - // The table was created hidden, show it if there are unknown attributes now - if (ui_attrs.size() > 0 && !section.isVisible()) { - section.setVisible(true); - needs_reflow = true; - } - - // Compare the new attribute set with the old "reference" one - boolean has_differences = ui_attrs.size() != reference.size(); - if (!has_differences) { - for (UiAttributeNode ui_attr : ui_attrs) { - if (!reference.contains(ui_attr)) { - has_differences = true; - break; - } - } - } - - if (has_differences) { - needs_reflow = true; - reference.clear(); - - // Remove all children of the table - for (Control c : unknownTable.getChildren()) { - c.dispose(); - } - - // Recreate all attributes UI - for (UiAttributeNode ui_attr : ui_attrs) { - reference.add(ui_attr); - ui_attr.createUiControl(unknownTable, managedForm); - - if (ui_attr.getCurrentValue() != null && ui_attr.getCurrentValue().length() > 0) { - section.setExpanded(true); - } - } - } - - if (needs_reflow) { - reflowMasterSection(); - } - } - - /** - * Marks the part dirty. Called as a result of user interaction with the widgets in the - * section. - */ - private void markDirty() { - if (!mIsDirty) { - mIsDirty = true; - mManagedForm.dirtyStateChanged(); - } - } -} - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java deleted file mode 100644 index 9f34d9e..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeContentProvider.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -import java.util.ArrayList; - -/** - * UiModelTreeContentProvider is a trivial implementation of {@link ITreeContentProvider} - * where elements are expected to be instances of {@link UiElementNode}. - */ -class UiModelTreeContentProvider implements ITreeContentProvider { - - /** The root {@link UiElementNode} which contains all the elements that are to be - * manipulated by this tree view. In general this is the manifest UI node. */ - private UiElementNode mUiRootNode; - /** The descriptor of the elements to be displayed as root in this tree view. All elements - * of the same type in the root will be displayed. */ - private ElementDescriptor[] mDescriptorFilters; - - public UiModelTreeContentProvider(UiElementNode uiRootNode, - ElementDescriptor[] descriptorFilters) { - mUiRootNode = uiRootNode; - mDescriptorFilters = descriptorFilters; - } - - /* (non-java doc) - * Returns all the UI node children of the given element or null if not the right kind - * of object. */ - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof UiElementNode) { - UiElementNode node = (UiElementNode) parentElement; - return node.getUiChildren().toArray(); - } - return null; - } - - /* (non-java doc) - * Returns the parent of a given UI node or null if it's a root node or it's not the - * right kind of node. */ - public Object getParent(Object element) { - if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; - return node.getUiParent(); - } - return null; - } - - /* (non-java doc) - * Returns true if the UI node has any UI children nodes. */ - public boolean hasChildren(Object element) { - if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; - return node.getUiChildren().size() > 0; - } - return false; - } - - /* (non-java doc) - * Get root elements for the tree. These are all the UI nodes that - * match the filter descriptor in the current root node. - *

              - * Although not documented, it seems this method should not return null. - * At worse, it should return new Object[0]. - *

              - * inputElement is not currently used. The root node and the filter are given - * by the enclosing class. - */ - public Object[] getElements(Object inputElement) { - ArrayList roots = new ArrayList(); - if (mUiRootNode != null) { - for (UiElementNode ui_node : mUiRootNode.getUiChildren()) { - if (mDescriptorFilters == null || mDescriptorFilters.length == 0) { - roots.add(ui_node); - } else { - for (ElementDescriptor filter : mDescriptorFilters) { - if (ui_node.getDescriptor() == filter) { - roots.add(ui_node); - } - } - } - } - } - - return roots.toArray(); - } - - public void dispose() { - // pass - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // pass - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java deleted file mode 100644 index ff5f24c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiModelTreeLabelProvider.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.ui.ErrorImageComposite; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.swt.graphics.Image; - -/** - * UiModelTreeLabelProvider is a trivial implementation of {@link ILabelProvider} - * where elements are expected to derive from {@link UiElementNode} or - * from {@link ElementDescriptor}. - * - * It is used by both the master tree viewer and by the list in the Add... selection dialog. - */ -public class UiModelTreeLabelProvider implements ILabelProvider { - - public UiModelTreeLabelProvider() { - } - - /** - * Returns the element's logo with a fallback on the android logo. - */ - public Image getImage(Object element) { - ElementDescriptor desc = null; - if (element instanceof ElementDescriptor) { - Image img = ((ElementDescriptor) element).getIcon(); - if (img != null) { - return img; - } - } else if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; - desc = node.getDescriptor(); - if (desc != null) { - Image img = desc.getIcon(); - if (img != null) { - if (node.hasError()) { - //TODO: cache image - return new ErrorImageComposite(img).createImage(); - } else { - return img; - } - } - } - } - return EditorsPlugin.getAndroidLogo(); - } - - /** - * Uses UiElementNode.shortDescription for the label for this tree item. - */ - public String getText(Object element) { - if (element instanceof ElementDescriptor) { - ElementDescriptor desc = (ElementDescriptor) element; - return desc.getUiName(); - } else if (element instanceof UiElementNode) { - UiElementNode node = (UiElementNode) element; - return node.getShortDescription(); - } - return element.toString(); - } - - public void addListener(ILabelProviderListener listener) { - // pass - } - - public void dispose() { - // pass - } - - public boolean isLabelProperty(Object element, String property) { - // pass - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // pass - } -} - - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java deleted file mode 100644 index 7e7bd68..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/ui/tree/UiTreeBlock.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.ui.tree; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; -import com.android.ide.eclipse.editors.ui.SectionHelper.ManifestSectionPart; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.IMenuListener; -import org.eclipse.jface.action.IMenuManager; -import org.eclipse.jface.action.MenuManager; -import org.eclipse.jface.action.Separator; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.ITreeSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.Clipboard; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.ToolBar; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.ui.forms.DetailsPart; -import org.eclipse.ui.forms.IDetailsPage; -import org.eclipse.ui.forms.IDetailsPageProvider; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.MasterDetailsBlock; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.Section; - -import java.util.LinkedList; - -/** - * {@link UiTreeBlock} is a {@link MasterDetailsBlock} which displays a tree view for - * a specific set of {@link UiElementNode}. - *

              - * For a given UI element node, the tree view displays all first-level children that - * match a given type (given by an {@link ElementDescriptor}. All children from these - * nodes are also displayed. - *

              - * In the middle next to the tree are some controls to add or delete tree nodes. - * On the left is a details part that displays all the visible UI attributes for a given - * selected UI element node. - */ -public final class UiTreeBlock extends MasterDetailsBlock implements ICommitXml { - - /** Height hint for the tree view. Helps the grid layout resize properly on smaller screens. */ - private static final int TREE_HEIGHT_HINT = 50; - - /** Container editor */ - AndroidEditor mEditor; - /** The root {@link UiElementNode} which contains all the elements that are to be - * manipulated by this tree view. In general this is the manifest UI node. */ - private UiElementNode mUiRootNode; - /** The descriptor of the elements to be displayed as root in this tree view. All elements - * of the same type in the root will be displayed. */ - private ElementDescriptor[] mDescriptorFilters; - /** The title for the master-detail part (displayed on the top "tab" on top of the tree) */ - private String mTitle; - /** The description for the master-detail part (displayed on top of the tree view) */ - private String mDescription; - /** The master-detail part, composed of a main tree and an auxiliary detail part */ - private ManifestSectionPart mMasterPart; - /** The tree viewer in the master-detail part */ - private TreeViewer mTreeViewer; - /** The "add" button for the tree view */ - private Button mAddButton; - /** The "remove" button for the tree view */ - private Button mRemoveButton; - /** The "up" button for the tree view */ - private Button mUpButton; - /** The "down" button for the tree view */ - private Button mDownButton; - /** The Managed Form used to create the master part */ - private IManagedForm mManagedForm; - /** Reference to the details part of the tree master block. */ - private DetailsPart mDetailsPart; - /** Reference to the clipboard for copy-paste */ - private Clipboard mClipboard; - /** Listener to refresh the tree viewer when the parent's node has been updated */ - private IUiUpdateListener mUiRefreshListener; - /** Listener to enable/disable the UI based on the application node's presence */ - private IUiUpdateListener mUiEnableListener; - - private UiTreeActions mUiTreeActions; - - private final boolean mAutoCreateRoot; - - - /** - * Creates a new {@link MasterDetailsBlock} that will display all UI nodes matching the - * given filter in the given root node. - * - * @param editor The parent manifest editor. - * @param uiRootNode The root {@link UiElementNode} which contains all the elements that are - * to be manipulated by this tree view. In general this is the manifest UI node or the - * application UI node. This cannot be null. - * @param autoCreateRoot True if the root node can be created on-demand (i.e. as needed as - * soon as children exist). False if an external entity controls the existence of the - * root node. In practise, this is false for the manifest application page (the actual - * "application" node is managed by the ApplicationToggle part) whereas it is true - * for all other tree pages. - * @param descriptorFilters A list of descriptors of the elements to be displayed as root in - * this tree view. Use null or an empty list to accept any kind of node. - * @param title Title for the section - * @param description Description for the section - */ - public UiTreeBlock(AndroidEditor editor, - UiElementNode uiRootNode, - boolean autoCreateRoot, - ElementDescriptor[] descriptorFilters, - String title, - String description) { - mEditor = editor; - mUiRootNode = uiRootNode; - mAutoCreateRoot = autoCreateRoot; - mDescriptorFilters = descriptorFilters; - mTitle = title; - mDescription = description; - } - - /** @returns The container editor */ - AndroidEditor getEditor() { - return mEditor; - } - - /** @returns The reference to the clipboard for copy-paste */ - Clipboard getClipboard() { - return mClipboard; - } - - /** @returns The master-detail part, composed of a main tree and an auxiliary detail part */ - ManifestSectionPart getMasterPart() { - return mMasterPart; - } - - @Override - protected void createMasterPart(final IManagedForm managedForm, Composite parent) { - FormToolkit toolkit = managedForm.getToolkit(); - - mManagedForm = managedForm; - mMasterPart = new ManifestSectionPart(parent, toolkit); - Section section = mMasterPart.getSection(); - section.setText(mTitle); - section.setDescription(mDescription); - section.setLayout(new GridLayout()); - section.setLayoutData(new GridData(GridData.FILL_BOTH)); - - Composite grid = SectionHelper.createGridLayout(section, toolkit, 2); - - Tree tree = createTreeViewer(toolkit, grid, managedForm); - createButtons(toolkit, grid); - createTreeContextMenu(tree); - createSectionActions(section, toolkit); - } - - private void createSectionActions(Section section, FormToolkit toolkit) { - ToolBarManager manager = new ToolBarManager(SWT.FLAT); - ToolBar toolbar = manager.createControl(section); - section.setTextClient(toolbar); - - manager.add(new TreeSortAction()); - manager.update(true /*force*/); - } - - /** - * Creates the tree and its viewer - * @return The tree control - */ - private Tree createTreeViewer(FormToolkit toolkit, Composite grid, - final IManagedForm managedForm) { - // Note: we *could* use a FilteredTree instead of the Tree+TreeViewer here. - // However the class must be adapted to create an adapted toolkit tree. - final Tree tree = toolkit.createTree(grid, SWT.SINGLE); - GridData gd = new GridData(GridData.FILL_BOTH); - gd.widthHint = AndroidEditor.TEXT_WIDTH_HINT; - gd.heightHint = TREE_HEIGHT_HINT; - tree.setLayoutData(gd); - - mTreeViewer = new TreeViewer(tree); - mTreeViewer.setContentProvider(new UiModelTreeContentProvider( - mUiRootNode, mDescriptorFilters)); - mTreeViewer.setLabelProvider(new UiModelTreeLabelProvider()); - mTreeViewer.setInput("unused"); //$NON-NLS-1$ - - // Create a listener that reacts to selections on the tree viewer. - // When a selection is made, ask the managed form to propagate an event to - // all parts in the managed form. - // This is picked up by UiElementDetail.selectionChanged(). - mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - managedForm.fireSelectionChanged(mMasterPart, event.getSelection()); - adjustTreeButtons(event.getSelection()); - } - }); - - // Create three listeners: - // - One to refresh the tree viewer when the parent's node has been updated - // - One to refresh the tree viewer when the framework resources have changed - // - One to enable/disable the UI based on the application node's presence. - mUiRefreshListener = new IUiUpdateListener() { - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - mTreeViewer.refresh(); - } - }; - - mUiEnableListener = new IUiUpdateListener() { - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { - // The UiElementNode for the application XML node always exists, even - // if there is no corresponding XML node in the XML file. - // - // Normally, we enable the UI here if the XML node is not null. - // - // However if mAutoCreateRoot is true, the root node will be created on-demand - // so the tree/block is always enabled. - boolean exists = mAutoCreateRoot || (ui_node.getXmlNode() != null); - if (mMasterPart != null) { - Section section = mMasterPart.getSection(); - if (section.getEnabled() != exists) { - section.setEnabled(exists); - for (Control c : section.getChildren()) { - c.setEnabled(exists); - } - } - } - } - }; - - final Runnable resourceRefreshListener = new Runnable() { - public void run() { - // If a details part has been created, we need to "refresh" it too. - if (mDetailsPart != null) { - // The details part does not directly expose access to its internal - // page book. Instead it is possible to resize the page book to 0 and then - // back to its original value, which as the side effect of removing all - // existing cached pages. - int limit = mDetailsPart.getPageLimit(); - mDetailsPart.setPageLimit(0); - mDetailsPart.setPageLimit(limit); - } - // Refresh the tree, preserving the selection if possible. - mTreeViewer.refresh(); - } - }; - - // Setup the listeners - changeRootAndDescriptors(mUiRootNode, mDescriptorFilters, false /* refresh */); - - // Listen on resource framework changes to refresh the tree - EditorsPlugin.getDefault().addResourceChangedListener(resourceRefreshListener); - - // Remove listeners when the tree widget gets disposed. - tree.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - UiElementNode node = mUiRootNode.getUiParent() != null ? - mUiRootNode.getUiParent() : - mUiRootNode; - - node.removeUpdateListener(mUiRefreshListener); - mUiRootNode.removeUpdateListener(mUiEnableListener); - - EditorsPlugin.getDefault().removeResourceChangedListener(resourceRefreshListener); - if (mClipboard != null) { - mClipboard.dispose(); - mClipboard = null; - } - } - }); - - // Get a new clipboard reference. It is disposed when the tree is disposed. - mClipboard = new Clipboard(tree.getDisplay()); - - return tree; - } - - /** - * Changes the UI root node and the descriptor filters of the tree. - *

              - * This removes the listeners attached to the old root node and reattaches them to the - * new one. - * - * @param uiRootNode The root {@link UiElementNode} which contains all the elements that are - * to be manipulated by this tree view. In general this is the manifest UI node or the - * application UI node. This cannot be null. - * @param descriptorFilters A list of descriptors of the elements to be displayed as root in - * this tree view. Use null or an empty list to accept any kind of node. - * @param forceRefresh If tree, forces the tree to refresh - */ - public void changeRootAndDescriptors(UiElementNode uiRootNode, - ElementDescriptor[] descriptorFilters, boolean forceRefresh) { - UiElementNode node; - - // Remove previous listeners if any - if (mUiRootNode != null) { - node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode; - node.removeUpdateListener(mUiRefreshListener); - mUiRootNode.removeUpdateListener(mUiEnableListener); - } - - mUiRootNode = uiRootNode; - mDescriptorFilters = descriptorFilters; - - // Listen on structural changes on the root node of the tree - // If the node has a parent, listen on the parent instead. - node = mUiRootNode.getUiParent() != null ? mUiRootNode.getUiParent() : mUiRootNode; - node.addUpdateListener(mUiRefreshListener); - - // Use the root node to listen to its presence. - mUiRootNode.addUpdateListener(mUiEnableListener); - - // Initialize the enabled/disabled state - mUiEnableListener.uiElementNodeUpdated(mUiRootNode, null /* state, not used */); - - if (forceRefresh) { - mTreeViewer.refresh(); - } - } - - /** - * Creates the buttons next to the tree. - */ - private void createButtons(FormToolkit toolkit, Composite grid) { - - mUiTreeActions = new UiTreeActions(); - - Composite button_grid = SectionHelper.createGridLayout(grid, toolkit, 1); - button_grid.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); - mAddButton = toolkit.createButton(button_grid, "Add...", SWT.PUSH); - SectionHelper.addControlTooltip(mAddButton, "Adds a new element."); - mAddButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | - GridData.VERTICAL_ALIGN_BEGINNING)); - - mAddButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - doTreeAdd(); - } - }); - - mRemoveButton = toolkit.createButton(button_grid, "Remove...", SWT.PUSH); - SectionHelper.addControlTooltip(mRemoveButton, "Removes an existing selected element."); - mRemoveButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - mRemoveButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - doTreeRemove(); - } - }); - - mUpButton = toolkit.createButton(button_grid, "Up", SWT.PUSH); - SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element up."); - mUpButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - mUpButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - doTreeUp(); - } - }); - - mDownButton = toolkit.createButton(button_grid, "Down", SWT.PUSH); - SectionHelper.addControlTooltip(mRemoveButton, "Moves the selected element down."); - mDownButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - - mDownButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - doTreeDown(); - } - }); - - adjustTreeButtons(TreeSelection.EMPTY); - } - - private void createTreeContextMenu(Tree tree) { - MenuManager menuManager = new MenuManager(); - menuManager.setRemoveAllWhenShown(true); - menuManager.addMenuListener(new IMenuListener() { - /** - * The menu is about to be shown. The menu manager has already been - * requested to remove any existing menu item. This method gets the - * tree selection and if it is of the appropriate type it re-creates - * the necessary actions. - */ - public void menuAboutToShow(IMenuManager manager) { - ISelection selection = mTreeViewer.getSelection(); - if (!selection.isEmpty() && selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - Object first = tree_selection.getFirstElement(); - if (first != null && first instanceof UiElementNode) { - UiElementNode ui_node = (UiElementNode) first; - doCreateMenuAction(manager, ui_node); - return; - } - } - doCreateMenuAction(manager, null /* ui_node */); - } - }); - Menu contextMenu = menuManager.createContextMenu(tree); - tree.setMenu(contextMenu); - } - - /** - * Adds the menu actions to the context menu when the given UI node is selected in - * the tree view. - * - * @param manager The context menu manager - * @param ui_node The UI node selected in the tree. Can be null, in which case the root - * is to be modified. - */ - private void doCreateMenuAction(IMenuManager manager, UiElementNode ui_node) { - Action action; - - if (ui_node != null && ui_node.getXmlNode() != null) { - manager.add(new CopyCutAction(getEditor(), getClipboard(), - this, ui_node, true /* cut */)); - manager.add(new CopyCutAction(getEditor(), getClipboard(), - this, ui_node, false /* cut */)); - // Paste is not valid if it would add a second element on a terminal element - // which parent is a document -- an XML document can only have one child. This - // means paste is valid if the current UI node can have children or if the parent - // is not a document. - if (mUiRootNode.getDescriptor().hasChildren() || - !(mUiRootNode.getUiParent() instanceof UiDocumentNode)) { - manager.add(new PasteAction(getEditor(), getClipboard(), ui_node)); - } - manager.add(new Separator()); - } - - // Append "add" and "remove" actions. They do the same thing as the add/remove - // buttons on the side. - - manager.add(new Action("Add...", EditorsPlugin.getAndroidLogoDesc()) { - @Override - public void run() { - super.run(); - doTreeAdd(); - } - }); - - if (ui_node != null) { - manager.add(new Action("Remove", EditorsPlugin.getAndroidLogoDesc()) { - @Override - public void run() { - super.run(); - doTreeRemove(); - } - }); - } - - manager.add(new Separator()); - - if (ui_node != null) { - manager.add(new Action("Up", EditorsPlugin.getAndroidLogoDesc()) { - @Override - public void run() { - super.run(); - doTreeUp(); - } - }); - } - - if (ui_node != null) { - manager.add(new Action("Down", EditorsPlugin.getAndroidLogoDesc()) { - @Override - public void run() { - super.run(); - doTreeDown(); - } - }); - } - } - - - /** - * This is called by the tree when a selection is made. - * It enables/disables the buttons associated with the tree depending on the current - * selection. - * - * @param selection The current tree selection (same as mTreeViewer.getSelection()) - */ - private void adjustTreeButtons(ISelection selection) { - mRemoveButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); - mUpButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); - mDownButton.setEnabled(!selection.isEmpty() && selection instanceof ITreeSelection); - } - - /** - * An adapter/wrapper to use the add/remove/up/down tree edit actions. - */ - private class UiTreeActions extends UiActions { - @Override - protected UiElementNode getRootNode() { - return mUiRootNode; - } - - @Override - protected void selectUiNode(UiElementNode uiNodeToSelect) { - // Select the new item - if (uiNodeToSelect != null) { - LinkedList segments = new LinkedList(); - for (UiElementNode ui_node = uiNodeToSelect; ui_node != mUiRootNode; - ui_node = ui_node.getUiParent()) { - segments.add(0, ui_node); - } - mTreeViewer.setSelection(new TreeSelection(new TreePath(segments.toArray()))); - } - } - - @Override - public void commitPendingXmlChanges() { - commitManagedForm(); - } - } - - /** - * Called when the "Add..." button next to the tree view is selected. - * - * Displays a selection dialog that lets the user select which kind of node - * to create, depending on the current selection. - */ - private void doTreeAdd() { - UiElementNode ui_node = mUiRootNode; - ISelection selection = mTreeViewer.getSelection(); - if (!selection.isEmpty() && selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - Object first = tree_selection.getFirstElement(); - if (first != null && first instanceof UiElementNode) { - ui_node = (UiElementNode) first; - } - } - - mUiTreeActions.doAdd( - ui_node, - mDescriptorFilters, - mTreeViewer.getControl().getShell(), - (ILabelProvider) mTreeViewer.getLabelProvider()); - } - - /** - * Called when the "Remove" button is selected. - * - * If the tree has a selection, remove it. - * This simply deletes the XML node attached to the UI node: when the XML model fires the - * update event, the tree will get refreshed. - */ - protected void doTreeRemove() { - ISelection selection = mTreeViewer.getSelection(); - if (!selection.isEmpty() && selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - Object first = tree_selection.getFirstElement(); - if (first instanceof UiElementNode) { - final UiElementNode ui_node = (UiElementNode) first; - - mUiTreeActions.doRemove(ui_node, mTreeViewer.getControl().getShell()); - } - } - } - - /** - * Called when the "Up" button is selected. - *

              - * If the tree has a selection, move it up, either in the child list or as the last child - * of the previous parent. - */ - protected void doTreeUp() { - ISelection selection = mTreeViewer.getSelection(); - if (!selection.isEmpty() && selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - Object first = tree_selection.getFirstElement(); - if (first instanceof UiElementNode) { - final UiElementNode ui_node = (UiElementNode) first; - - mUiTreeActions.doUp(ui_node); - } - } - } - - /** - * Called when the "Down" button is selected. - * - * If the tree has a selection, move it down, either in the same child list or as the - * first child of the next parent. - */ - protected void doTreeDown() { - ISelection selection = mTreeViewer.getSelection(); - if (!selection.isEmpty() && selection instanceof ITreeSelection) { - ITreeSelection tree_selection = (ITreeSelection) selection; - Object first = tree_selection.getFirstElement(); - if (first instanceof UiElementNode) { - final UiElementNode ui_node = (UiElementNode) first; - - mUiTreeActions.doDown(ui_node); - } - } - } - - /** - * Commits the current managed form (the one associated with our master part). - * As a side effect, this will commit the current UiElementDetails page. - */ - void commitManagedForm() { - if (mManagedForm != null) { - mManagedForm.commit(false /* onSave */); - } - } - - /* Implements ICommitXml for CopyCutAction */ - public void commitPendingXmlChanges() { - commitManagedForm(); - } - - @Override - protected void createToolBarActions(IManagedForm managedForm) { - // pass - } - - @Override - protected void registerPages(DetailsPart detailsPart) { - // Keep a reference on the details part (the super class doesn't provide a getter - // for it.) - mDetailsPart = detailsPart; - - // The page selection mechanism does not use pages registered by association with - // a node class. Instead it uses a custom details page provider that provides a - // new UiElementDetail instance for each node instance. A limit of 5 pages is - // then set (the value is arbitrary but should be reasonable) for the internal - // page book. - detailsPart.setPageLimit(5); - - final UiTreeBlock tree = this; - - detailsPart.setPageProvider(new IDetailsPageProvider() { - public IDetailsPage getPage(Object key) { - if (key instanceof UiElementNode) { - return new UiElementDetail(tree); - } - return null; - } - - public Object getPageKey(Object object) { - return object; // use node object as key - } - }); - } - - /** - * Adds a sort action to the tree viewer. - */ - private class TreeSortAction extends Action { - - private ViewerComparator mComparator; - - public TreeSortAction() { - setToolTipText("Sorts elements alphabetically."); - setImageDescriptor(IconFactory.getInstance().getImageDescriptor("az_sort")); //$NON-NLS-1$ - - if (mTreeViewer != null) { - boolean is_sorted = mTreeViewer.getComparator() != null; - setChecked(is_sorted); - } - } - - /** - * Called when the button is selected. Toggle the tree viewer comparator. - */ - @Override - public void run() { - if (mTreeViewer == null) { - notifyResult(false /*success*/); - return; - } - - ViewerComparator comp = mTreeViewer.getComparator(); - if (comp != null) { - // Tree is currently sorted. - // Save currently comparator and remove it - mComparator = comp; - mTreeViewer.setComparator(null); - } else { - // Tree is not currently sorted. - // Reuse or add a new comparator. - if (mComparator == null) { - mComparator = new ViewerComparator(); - } - mTreeViewer.setComparator(mComparator); - } - - notifyResult(true /*success*/); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java deleted file mode 100644 index 4c368d9..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiSettableAttributeNode.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 - * - * 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.editors.uimodel; - -/** - * This interface decoration indicates that a given UiAttributeNode can both - * set and get its current value. - */ -public interface IUiSettableAttributeNode { - - /** Returns the current value of the node. */ - public String getCurrentValue(); - - /** Sets the current value of the node. */ - public void setCurrentValue(String value); - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java deleted file mode 100644 index 12cb31b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/IUiUpdateListener.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - - -/** - * Listen to update notifications in UI nodes. - */ -public interface IUiUpdateListener { - - /** Update state of the UI node */ - public enum UiUpdateState { - /** The node's attributes have been updated. They may or may not actually have changed. */ - ATTR_UPDATED, - /** The node sub-structure (i.e. child nodes) has changed */ - CHILDREN_CHANGED, - /** The XML counterpart for the UI node has just been created. */ - CREATED, - /** The XML counterpart for the UI node has just been deleted. - * Note that mandatory UI nodes are never actually deleted. */ - DELETED - } - - /** - * Indicates that an UiElementNode has been updated. - *

              - * This happens when an {@link UiElementNode} is refreshed to match the - * XML model. The actual UI element node may or may not have changed. - * - * @param ui_node The {@link UiElementNode} being updated. - */ - public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java deleted file mode 100644 index 17b077a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAbstractTextAttributeNode.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; - -import org.w3c.dom.Node; - -/** - * Represents an XML attribute in that can be modified using a simple text field - * in the XML editor's user interface. - *

              - * The XML attribute has no default value. When unset, the text field is blank. - * When updating the XML, if the field is empty, the attribute will be removed - * from the XML element. - *

              - * See {@link UiAttributeNode} for more information. - */ -public abstract class UiAbstractTextAttributeNode extends UiAttributeNode - implements IUiSettableAttributeNode { - - protected static final String DEFAULT_VALUE = ""; //$NON-NLS-1$ - - /** Prevent internal listener from firing when internally modifying the text */ - private boolean mInternalTextModification; - /** Last value read from the XML model */ - private String mCurrentValue = DEFAULT_VALUE; - - public UiAbstractTextAttributeNode(AttributeDescriptor attributeDescriptor, - UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /** Returns the current value of the node. */ - @Override - public final String getCurrentValue() { - return mCurrentValue; - } - - /** Sets the current value of the node. */ - public final void setCurrentValue(String value) { - mCurrentValue = value; - } - - /** Returns if the attribute node is valid, and its UI has been created. */ - public abstract boolean isValid(); - - /** Returns the text value present in the UI. */ - public abstract String getTextWidgetValue(); - - /** Sets the text value to be displayed in the UI. */ - public abstract void setTextWidgetValue(String value); - - - /** - * Updates the current text field's value when the XML has changed. - *

              - * The caller doesn't really know if attributes have changed, - * so it will call this to refresh the attribute anyway. The value - * is only set if it has changed. - *

              - * This also resets the "dirty" flag. - */ - @Override - public void updateValue(Node xml_attribute_node) { - mCurrentValue = DEFAULT_VALUE; - if (xml_attribute_node != null) { - mCurrentValue = xml_attribute_node.getNodeValue().trim(); - } - - if (isValid() && !getTextWidgetValue().equals(mCurrentValue)) { - try { - mInternalTextModification = true; - setTextWidgetValue(mCurrentValue); - setDirty(false); - } finally { - mInternalTextModification = false; - } - } - } - - /* (non-java doc) - * Called by the user interface when the editor is saved or its state changed - * and the modified attributes must be committed (i.e. written) to the XML model. - */ - @Override - public void commit() { - UiElementNode parent = getUiParent(); - if (parent != null && isValid() && isDirty()) { - String value = getTextWidgetValue().trim(); - if (parent.commitAttributeToXml(this, value)) { - mCurrentValue = value; - setDirty(false); - } - } - } - - protected final boolean isInInternalTextModification() { - return mInternalTextModification; - } - - protected final void setInInternalTextModification(boolean internalTextModification) { - mInternalTextModification = internalTextModification; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java deleted file mode 100644 index 5972f22..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.ui.forms.IManagedForm; -import org.w3c.dom.Node; - -/** - * Represents an XML attribute that can be modified by the XML editor's user interface. - *

              - * The characteristics of an {@link UiAttributeNode} are declared by a - * corresponding {@link AttributeDescriptor}. - *

              - * This is an abstract class. Derived classes must implement the creation of the UI - * and manage its synchronization with the XML. - */ -public abstract class UiAttributeNode { - - private AttributeDescriptor mDescriptor; - private UiElementNode mUiParent; - private boolean mIsDirty; - private boolean mHasError; - - /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} - * and the corresponding runtine {@link UiElementNode} parent. */ - public UiAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { - mDescriptor = attributeDescriptor; - mUiParent = uiParent; - } - - /** Returns the {@link AttributeDescriptor} specific to this UI attribute node */ - public final AttributeDescriptor getDescriptor() { - return mDescriptor; - } - - /** Returns the {@link UiElementNode} that owns this {@link UiAttributeNode} */ - public final UiElementNode getUiParent() { - return mUiParent; - } - - /** Returns the current value of the node. */ - public abstract String getCurrentValue(); - - /** - * @return True if the attribute has been changed since it was last loaded - * from the XML model. - */ - public final boolean isDirty() { - return mIsDirty; - } - - /** - * Sets whether the attribute is dirty and also notifies the editor some part's dirty - * flag as changed. - *

              - * Subclasses should set the to true as a result of user interaction with the widgets in - * the section and then should set to false when the commit() method completed. - */ - public void setDirty(boolean isDirty) { - boolean old_value = mIsDirty; - mIsDirty = isDirty; - // TODO: for unknown attributes, getParent() != null && getParent().getEditor() != null - if (old_value != isDirty) { - getUiParent().getEditor().editorDirtyStateChanged(); - } - } - - /** - * Sets the error flag value. - * @param errorFlag the error flag - */ - public final void setHasError(boolean errorFlag) { - mHasError = errorFlag; - } - - /** - * Returns whether this node has errors. - */ - public final boolean hasError() { - return mHasError; - } - - /** - * Called once by the parent user interface to creates the necessary - * user interface to edit this attribute. - *

              - * This method can be called more than once in the life cycle of an UI node, - * typically when the UI is part of a master-detail tree, as pages are swapped. - * - * @param parent The composite where to create the user interface. - * @param managedForm The managed form owning this part. - */ - public abstract void createUiControl(Composite parent, IManagedForm managedForm); - - /** - * Used to get a list of all possible values for this UI attribute. - *

              - * This is used, among other things, by the XML Content Assists to complete values - * for an attribute. - *

              - * Implementations that do not have any known values should return null. - * - * @return A list of possible completion values or null. - */ - public abstract String[] getPossibleValues(); - - /** - * Called when the XML is being loaded or has changed to - * update the value held by this user interface attribute node. - *

              - * The XML Node may be null, which denotes that the attribute is not - * specified in the XML model. In general, this means the "default" value of the - * attribute should be used. - *

              - * The caller doesn't really know if attributes have changed, - * so it will call this to refresh the attribute anyway. It's up to the - * UI implementation to minimize refreshes. - * - * @param xml_attribute_node - */ - public abstract void updateValue(Node xml_attribute_node); - - /** - * Called by the user interface when the editor is saved or its state changed - * and the modified attributes must be committed (i.e. written) to the XML model. - *

              - * Important behaviors: - *

                - *
              • The caller *must* have called IStructuredModel.aboutToChangeModel before. - * The implemented methods must assume it is safe to modify the XML model. - *
              • On success, the implementation *must* call setDirty(false). - *
              • On failure, the implementation can fail with an exception, which - * is trapped and logged by the caller, or do nothing, whichever is more - * appropriate. - *
              - */ - public abstract void commit(); -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java deleted file mode 100644 index 113738f..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiDocumentNode.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.editors.uimodel; - -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; - -import org.w3c.dom.Document; -import org.w3c.dom.Node; - -/** - * Represents an XML document node that can be modified by the user interface in the XML editor. - *

              - * The structure of a given {@link UiDocumentNode} is declared by a corresponding - * {@link DocumentDescriptor}. - */ -public class UiDocumentNode extends UiElementNode { - - /** - * Creates a new {@link UiDocumentNode} described by a given {@link DocumentDescriptor}. - * - * @param documentDescriptor The {@link DocumentDescriptor} for the XML node. Cannot be null. - */ - public UiDocumentNode(DocumentDescriptor documentDescriptor) { - super(documentDescriptor); - } - - /** - * Computes a short string describing the UI node suitable for tree views. - * Uses the element's attribute "android:name" if present, or the "android:label" one - * followed by the element's name. - * - * @return A short string describing the UI node suitable for tree views. - */ - @Override - public String getShortDescription() { - return "Document"; //$NON-NLS-1$ - } - - /** - * Computes a "breadcrumb trail" description for this node. - * - * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect - * when called on the root node itself. - * @return The "breadcrumb trail" description for this node. - */ - @Override - public String getBreadcrumbTrailDescription(boolean include_root) { - return "Document"; //$NON-NLS-1$ - } - - /** - * This method throws an exception when attempted to assign a parent, since XML documents - * cannot have a parent. It is OK to assign null. - */ - @Override - protected void setUiParent(UiElementNode parent) { - if (parent != null) { - // DEBUG. Change to log warning. - throw new UnsupportedOperationException("Documents can't have UI parents"); //$NON-NLS-1$ - } - super.setUiParent(null); - } - - /** - * Populate this element node with all values from the given XML node. - * - * This fails if the given XML node has a different element name -- it won't change the - * type of this ui node. - * - * This method can be both used for populating values the first time and updating values - * after the XML model changed. - * - * @param xml_node The XML node to mirror - * @return Returns true if the XML structure has changed (nodes added, removed or replaced) - */ - @Override - public boolean loadFromXmlNode(Node xml_node) { - boolean structure_changed = (getXmlDocument() != xml_node); - setXmlDocument((Document) xml_node); - structure_changed |= super.loadFromXmlNode(xml_node); - if (structure_changed) { - invokeUiUpdateListeners(UiUpdateState.CHILDREN_CHANGED); - } - return structure_changed; - } - - /** - * This method throws an exception if there is no underlying XML document. - *

              - * XML documents cannot be created per se -- they are a by-product of the StructuredEditor - * XML parser. - * - * @return The current value of getXmlDocument(). - */ - @Override - public Node createXmlNode() { - if (getXmlDocument() == null) { - // By design, a document node cannot be created, it is owned by the XML parser. - // By "design" this should never happen since the XML parser always creates an XML - // document container, even for an empty file. - throw new UnsupportedOperationException("Documents cannot be created"); //$NON-NLS-1$ - } - return getXmlDocument(); - } - - /** - * This method throws an exception and does not even try to delete the XML document. - *

              - * XML documents cannot be deleted per se -- they are a by-product of the StructuredEditor - * XML parser. - * - * @return The removed node or null if it didn't exist in the firtst place. - */ - @Override - public Node deleteXmlNode() { - // DEBUG. Change to log warning. - throw new UnsupportedOperationException("Documents cannot be deleted"); //$NON-NLS-1$ - } -} - diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java deleted file mode 100644 index 22b68b3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiElementNode.java +++ /dev/null @@ -1,1434 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.CustomViewDescriptorService; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.editors.manifest.descriptors.AndroidManifestDescriptors; -import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.editors.uimodel.IUiUpdateListener.UiUpdateState; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.views.properties.IPropertyDescriptor; -import org.eclipse.ui.views.properties.IPropertySource; -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.Text; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Map.Entry; - -/** - * Represents an XML node that can be modified by the user interface in the XML editor. - *

              - * Each tree viewer used in the application page's parts needs to keep a model representing - * each underlying node in the tree. This interface represents the base type for such a node. - *

              - * Each node acts as an intermediary model between the actual XML model (the real data support) - * and the tree viewers or the corresponding page parts. - *

              - * Element nodes don't contain data per se. Their data is contained in their attributes - * as well as their children's attributes, see {@link UiAttributeNode}. - *

              - * The structure of a given {@link UiElementNode} is declared by a corresponding - * {@link ElementDescriptor}. - *

              - * The class implements {@link IPropertySource}, in order to fill the Eclipse property tab when - * an element is selected. The {@link AttributeDescriptor} are used property descriptors. - */ -public class UiElementNode implements IPropertySource { - - /** List of prefixes removed from android:id strings when creating short descriptions. */ - private static String[] ID_PREFIXES = { - "@android:id/", //$NON-NLS-1$ - "@+id/", "@id/", "@+", "@" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ - - /** The element descriptor for the node. Always present, never null. */ - private ElementDescriptor mDescriptor; - /** The parent element node in the UI model. It is null for a root element or until - * the node is attached to its parent. */ - private UiElementNode mUiParent; - /** The {@link AndroidEditor} handling the UI hierarchy. This is defined only for the - * root node. All children have the value set to null and query their parent. */ - private AndroidEditor mEditor; - /** The XML {@link Document} model that is being mirror by the UI model. This is defined - * only for the root node. All children have the value set to null and query their parent. */ - private Document mXmlDocument; - /** The XML {@link Node} mirror by this UI node. This can be null for mandatory UI node which - * have no corresponding XML node or for new UI nodes before their XML node is set. */ - private Node mXmlNode; - /** The list of all UI children nodes. Can be empty but never null. There's one UI children - * node per existing XML children node. */ - private ArrayList mUiChildren; - /** The list of all UI attributes, as declared in the {@link ElementDescriptor}. - * The list is always defined and never null. Unlike the UiElementNode children list, this - * is always defined, even for attributes that do not exist in the XML model -- that's because - * "missing" attributes in the XML model simply mean a default value is used. Also note that - * the underlying collection is a map, so order is not respected. To get the desired attribute - * order, iterate through the {@link ElementDescriptor}'s attribute list. */ - private HashMap mUiAttributes; - private HashSet mUnknownUiAttributes; - /** A read-only view of the UI children node collection. */ - private List mReadOnlyUiChildren; - /** A read-only view of the UI attributes collection. */ - private Collection mReadOnlyUiAttributes; - /** A map of hidden attribute descriptors. Key is the XML name. */ - private Map mCachedHiddenAttributes; - /** An optional list of {@link IUiUpdateListener}. Most element nodes will not have any - * listeners attached, so the list is only created on demand and can be null. */ - private ArrayList mUiUpdateListeners; - /** Error Flag */ - private boolean mHasError; - /** Temporary data used by the editors. This data is not sync'ed with the XML */ - private Object mEditData; - - /** - * Creates a new {@link UiElementNode} described by a given {@link ElementDescriptor}. - * - * @param elementDescriptor The {@link ElementDescriptor} for the XML node. Cannot be null. - */ - public UiElementNode(ElementDescriptor elementDescriptor) { - mDescriptor = elementDescriptor; - clearContent(); - } - - /** - * Clears the {@link UiElementNode} by resetting the children list and - * the {@link UiAttributeNode}s list. - * Also resets the attached XML node, document, editor if any. - *

              - * The parent {@link UiElementNode} node is not reset so that it's position - * in the hierarchy be left intact, if any. - */ - /* package */ void clearContent() { - mXmlNode = null; - mXmlDocument = null; - mEditor = null; - clearAttributes(); - mReadOnlyUiChildren = null; - if (mUiChildren == null) { - mUiChildren = new ArrayList(); - } else { - // We can't remove mandatory nodes, we just clear them. - for (int i = mUiChildren.size() - 1; i >= 0; --i) { - removeUiChildAtIndex(i); - } - } - } - - /** - * Clears the internal list of attributes, the read-only cached version of it - * and the read-only cached hidden attribute list. - */ - private void clearAttributes() { - mUiAttributes = null; - mReadOnlyUiAttributes = null; - mCachedHiddenAttributes = null; - mUnknownUiAttributes = new HashSet(); - } - - /** - * Gets or creates the internal UiAttributes list. - *

              - * When the descriptor derives from ViewElementDescriptor, this list depends on the - * current UiParent node. - * - * @return A new set of {@link UiAttributeNode} that matches the expected - * attributes for this node. - */ - private HashMap getInternalUiAttributes() { - if (mUiAttributes == null) { - AttributeDescriptor[] attr_list = getAttributeDescriptors(); - mUiAttributes = new HashMap(attr_list.length); - for (AttributeDescriptor desc : attr_list) { - UiAttributeNode ui_node = desc.createUiNode(this); - if (ui_node != null) { // Some AttributeDescriptors do not have UI associated - mUiAttributes.put(desc, ui_node); - } - } - } - return mUiAttributes; - } - - /** - * Computes a short string describing the UI node suitable for tree views. - * Uses the element's attribute "android:name" if present, or the "android:label" one - * followed by the element's name. - * - * @return A short string describing the UI node suitable for tree views. - */ - public String getShortDescription() { - if (mXmlNode != null && mXmlNode instanceof Element && mXmlNode.hasAttributes()) { - - // Application and Manifest nodes have a special treatment: they are unique nodes - // so we don't bother trying to differentiate their strings and we fall back to - // just using the UI name below. - Element elem = (Element) mXmlNode; - - String attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, - AndroidManifestDescriptors.ANDROID_NAME_ATTR); - if (attr == null || attr.length() == 0) { - attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, - AndroidManifestDescriptors.ANDROID_LABEL_ATTR); - } - if (attr == null || attr.length() == 0) { - attr = elem.getAttribute(ResourcesDescriptors.NAME_ATTR); - } - if (attr == null || attr.length() == 0) { - attr = elem.getAttributeNS(AndroidConstants.NS_RESOURCES, - LayoutDescriptors.ID_ATTR); - - if (attr != null && attr.length() > 0) { - for (String prefix : ID_PREFIXES) { - if (attr.startsWith(prefix)) { - attr = attr.substring(prefix.length()); - break; - } - } - } - } - if (attr != null && attr.length() > 0) { - return String.format("%1$s (%2$s)", attr, mDescriptor.getUiName()); - } - } - - return String.format("%1$s", mDescriptor.getUiName()); - } - - /** - * Computes a "breadcrumb trail" description for this node. - * It will look something like "Manifest > Application > .myactivity (Activity) > Intent-Filter" - * - * @param include_root Whether to include the root (e.g. "Manifest") or not. Has no effect - * when called on the root node itself. - * @return The "breadcrumb trail" description for this node. - */ - public String getBreadcrumbTrailDescription(boolean include_root) { - StringBuilder sb = new StringBuilder(getShortDescription()); - - for (UiElementNode ui_node = getUiParent(); - ui_node != null; - ui_node = ui_node.getUiParent()) { - if (!include_root && ui_node.getUiParent() == null) { - break; - } - sb.insert(0, String.format("%1$s > ", ui_node.getShortDescription())); //$NON-NLS-1$ - } - - return sb.toString(); - } - - /** - * Sets the XML {@link Document}. - *

              - * The XML {@link Document} is initially null. The XML {@link Document} must be set only on the - * UI root element node (this method takes care of that.) - */ - public void setXmlDocument(Document xml_doc) { - if (mUiParent == null) { - mXmlDocument = xml_doc; - } else { - mUiParent.setXmlDocument(xml_doc); - } - } - - /** - * Returns the XML {@link Document}. - *

              - * The value is initially null until the UI node is attached to its UI parent -- the value - * of the document is then propagated. - * - * @return the XML {@link Document} or the parent's XML {@link Document} or null. - */ - public Document getXmlDocument() { - if (mXmlDocument != null) { - return mXmlDocument; - } else if (mUiParent != null) { - return mUiParent.getXmlDocument(); - } - return null; - } - - /** - * Returns the XML node associated with this UI node. - *

              - * Some {@link ElementDescriptor} are declared as being "mandatory". This means the - * corresponding UI node will exist even if there is no corresponding XML node. Such structure - * is created and enforced by the parent of the tree, not the element themselves. However - * such nodes will likely not have an XML node associated, so getXmlNode() can return null. - * - * @return The associated XML node. Can be null for mandatory nodes. - */ - public Node getXmlNode() { - return mXmlNode; - } - - /** - * Returns the {@link ElementDescriptor} for this node. This is never null. - *

              - * Do not use this to call getDescriptor().getAttributes(), instead call - * getAttributeDescriptors() which can be overriden by derived classes. - */ - public ElementDescriptor getDescriptor() { - return mDescriptor; - } - - /** - * Returns the {@link AttributeDescriptor} array for the descriptor of this node. - *

              - * Use this instead of getDescriptor().getAttributes() -- derived classes can override - * this to manipulate the attribute descriptor list depending on the current UI node. - */ - public AttributeDescriptor[] getAttributeDescriptors() { - return mDescriptor.getAttributes(); - } - - /** - * Returns the hidden {@link AttributeDescriptor} array for the descriptor of this node. - * This is a subset of the getAttributeDescriptors() list. - *

              - * Use this instead of getDescriptor().getHiddenAttributes() -- potentially derived classes - * could override this to manipulate the attribute descriptor list depending on the current - * UI node. There's no need for it right now so keep it private. - */ - private Map getHiddenAttributeDescriptors() { - if (mCachedHiddenAttributes == null) { - mCachedHiddenAttributes = new HashMap(); - for (AttributeDescriptor attr_desc : getAttributeDescriptors()) { - if (attr_desc instanceof XmlnsAttributeDescriptor) { - mCachedHiddenAttributes.put( - ((XmlnsAttributeDescriptor) attr_desc).getXmlNsName(), - attr_desc); - } - } - } - return mCachedHiddenAttributes; - } - - /** - * Sets the parent of this UiElementNode. - *

              - * The root node has no parent. - */ - protected void setUiParent(UiElementNode parent) { - mUiParent = parent; - // Invalidate the internal UiAttributes list, as it may depend on the actual UiParent. - clearAttributes(); - } - - /** - * @return The parent {@link UiElementNode} or null if this is the root node. - */ - public UiElementNode getUiParent() { - return mUiParent; - } - - /** - * Returns The root {@link UiElementNode}. - */ - public UiElementNode getUiRoot() { - UiElementNode root = this; - while (root.mUiParent != null) { - root = root.mUiParent; - } - - return root; - } - - /** - * Returns the previous UI sibling of this UI node. - * If the node does not have a previous sibling, returns null. - */ - public UiElementNode getUiPreviousSibling() { - if (mUiParent != null) { - List childlist = mUiParent.getUiChildren(); - if (childlist != null && childlist.size() > 1 && childlist.get(0) != this) { - int index = childlist.indexOf(this); - return index > 0 ? childlist.get(index - 1) : null; - } - } - return null; - } - - /** - * Returns the next UI sibling of this UI node. - * If the node does not have a next sibling, returns null. - */ - public UiElementNode getUiNextSibling() { - if (mUiParent != null) { - List childlist = mUiParent.getUiChildren(); - if (childlist != null) { - int size = childlist.size(); - if (size > 1 && childlist.get(size - 1) != this) { - int index = childlist.indexOf(this); - return index >= 0 && index < size - 1 ? childlist.get(index + 1) : null; - } - } - } - return null; - } - - /** - * Sets the {@link AndroidEditor} handling this {@link UiElementNode} hierarchy. - *

              - * The editor must always be set on the root node. This method takes care of that. - */ - public void setEditor(AndroidEditor editor) { - if (mUiParent == null) { - mEditor = editor; - } else { - mUiParent.setEditor(editor); - } - } - - /** - * Returns the {@link AndroidEditor} that embeds this {@link UiElementNode}. - *

              - * The value is initially null until the node is attached to its parent -- the value - * of the root node is then propagated. - * - * @return The embedding {@link AndroidEditor} or null. - */ - public AndroidEditor getEditor() { - return mUiParent == null ? mEditor : mUiParent.getEditor(); - } - - /** - * @return A read-only version of the children collection. - */ - public List getUiChildren() { - if (mReadOnlyUiChildren == null) { - mReadOnlyUiChildren = Collections.unmodifiableList(mUiChildren); - } - return mReadOnlyUiChildren; - } - - /** - * @return A read-only version of the attributes collection. - */ - public Collection getUiAttributes() { - if (mReadOnlyUiAttributes == null) { - mReadOnlyUiAttributes = Collections.unmodifiableCollection( - getInternalUiAttributes().values()); - } - return mReadOnlyUiAttributes; - } - - /** - * @return A read-only version of the unknown attributes collection. - */ - public Collection getUnknownUiAttributes() { - return Collections.unmodifiableCollection(mUnknownUiAttributes); - } - - /** - * Sets the error flag value. - * @param errorFlag the error flag - */ - public final void setHasError(boolean errorFlag) { - mHasError = errorFlag; - } - - /** - * Returns whether this node, its attributes, or one of the children nodes (and attributes) - * has errors. - */ - public final boolean hasError() { - if (mHasError) { - return true; - } - - // get the error value from the attributes. - Collection attributes = getInternalUiAttributes().values(); - for (UiAttributeNode attribute : attributes) { - if (attribute.hasError()) { - return true; - } - } - - // and now from the children. - for (UiElementNode child : mUiChildren) { - if (child.hasError()) { - return true; - } - } - - return false; - } - - /** - * Adds a new {@link IUiUpdateListener} to the internal update listener list. - */ - public void addUpdateListener(IUiUpdateListener listener) { - if (mUiUpdateListeners == null) { - mUiUpdateListeners = new ArrayList(); - } - if (!mUiUpdateListeners.contains(listener)) { - mUiUpdateListeners.add(listener); - } - } - - /** - * Removes an existing {@link IUiUpdateListener} from the internal update listener list. - * Does nothing if the list is empty or the listener is not registered. - */ - public void removeUpdateListener(IUiUpdateListener listener) { - if (mUiUpdateListeners != null) { - mUiUpdateListeners.remove(listener); - } - } - - /** - * Finds a child node relative to this node using a path-like expression. - * F.ex. "node1/node2" would find a child "node1" that contains a child "node2" and - * returns the latter. If there are multiple nodes with the same name at the same - * level, always uses the first one found. - * - * @param path The path like expression to select a child node. - * @return The ui node found or null. - */ - public UiElementNode findUiChildNode(String path) { - String[] items = path.split("/"); //$NON-NLS-1$ - UiElementNode ui_node = this; - for (String item : items) { - boolean next_segment = false; - for (UiElementNode c : ui_node.mUiChildren) { - if (c.getDescriptor().getXmlName().equals(item)) { - ui_node = c; - next_segment = true; - break; - } - } - if (!next_segment) { - return null; - } - } - return ui_node; - } - - /** - * Finds an {@link UiElementNode} which contains the give XML {@link Node}. - * Looks recursively in all children UI nodes. - * - * @param xmlNode The XML node to look for. - * @return The {@link UiElementNode} that contains xmlNode or null if not found, - */ - public UiElementNode findXmlNode(Node xmlNode) { - if (xmlNode == null) { - return null; - } - if (getXmlNode() == xmlNode) { - return this; - } - - for (UiElementNode uiChild : mUiChildren) { - UiElementNode found = uiChild.findXmlNode(xmlNode); - if (found != null) { - return found; - } - } - - return null; - } - - /** - * Returns the {@link UiAttributeNode} matching this attribute descriptor or - * null if not found. - * - * @param attr_desc The {@link AttributeDescriptor} to match. - * @return the {@link UiAttributeNode} matching this attribute descriptor or null - * if not found. - */ - public UiAttributeNode findUiAttribute(AttributeDescriptor attr_desc) { - return getInternalUiAttributes().get(attr_desc); - } - - /** - * Populate this element node with all values from the given XML node. - * - * This fails if the given XML node has a different element name -- it won't change the - * type of this ui node. - * - * This method can be both used for populating values the first time and updating values - * after the XML model changed. - * - * @param xml_node The XML node to mirror - * @return Returns true if the XML structure has changed (nodes added, removed or replaced) - */ - public boolean loadFromXmlNode(Node xml_node) { - boolean structure_changed = (mXmlNode != xml_node); - mXmlNode = xml_node; - if (xml_node != null) { - updateAttributeList(xml_node); - structure_changed |= updateElementList(xml_node); - invokeUiUpdateListeners(structure_changed ? UiUpdateState.CHILDREN_CHANGED - : UiUpdateState.ATTR_UPDATED); - } - return structure_changed; - } - - /** - * Clears the UI node and reload it from the given XML node. - *

              - * This works by clearing all references to any previous XML or UI nodes and - * then reloads the XML document from scratch. The editor reference is kept. - *

              - * This is used in the special case where the ElementDescriptor structure has changed. - * Rather than try to diff inflated UI nodes (as loadFromXmlNode does), we don't bother - * and reload everything. This is not subtle and should be used very rarely. - * - * @param xml_node The XML node or document to reload. Can be null. - */ - public void reloadFromXmlNode(Node xml_node) { - // The editor needs to be preserved, it is not affected by an XML change. - AndroidEditor editor = getEditor(); - clearContent(); - setEditor(editor); - if (xml_node != null) { - setXmlDocument(xml_node.getOwnerDocument()); - } - // This will reload all the XML and recreate the UI structure from scratch. - loadFromXmlNode(xml_node); - } - - /** - * Called by attributes when they want to commit their value - * to an XML node. - *

              - * For mandatory nodes, this makes sure the underlying XML element node - * exists in the model. If not, it is created and assigned as the underlying - * XML node. - *
              - * For non-mandatory nodes, simply return the underlying XML node, which - * must always exists. - * - * @return The XML node matching this {@link UiElementNode} or null. - */ - public Node prepareCommit() { - if (getDescriptor().isMandatory()) { - createXmlNode(); - // The new XML node has been created. - // We don't need to refresh using loadFromXmlNode() since there are - // no attributes or elements that need to be loading into this node. - } - return getXmlNode(); - } - - /** - * Commits the attributes (all internal, inherited from UI parent & unknown attributes). - * This is called by the UI when the embedding part needs to be committed. - */ - public void commit() { - for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) { - ui_attr.commit(); - } - - for (UiAttributeNode ui_attr : mUnknownUiAttributes) { - ui_attr.commit(); - } - } - - /** - * Returns true if the part has been modified with respect to the data - * loaded from the model. - */ - public boolean isDirty() { - for (UiAttributeNode ui_attr : getInternalUiAttributes().values()) { - if (ui_attr.isDirty()) { - return true; - } - } - - for (UiAttributeNode ui_attr : mUnknownUiAttributes) { - if (ui_attr.isDirty()) { - return true; - } - } - - return false; - } - - /** - * Creates the underlying XML element node for this UI node if it doesn't already - * exists. - * - * @return The new value of getXmlNode() (can be null if creation failed) - */ - public Node createXmlNode() { - if (mXmlNode != null) { - return null; - } - Node parentXmlNode = null; - if (mUiParent != null) { - parentXmlNode = mUiParent.prepareCommit(); - if (parentXmlNode == null) { - // The parent failed to create its own backing XML node. Abort. - // No need to throw an exception, the parent will most likely - // have done so itself. - return null; - } - } - - String element_name = getDescriptor().getXmlName(); - Document doc = getXmlDocument(); - - // We *must* have a root node. If not, we need to abort. - if (doc == null) { - throw new RuntimeException( - String.format("Missing XML document for %1$s XML node.", element_name)); - } - - // If we get here and parent_xml_node is null, the node is to be created - // as the root node of the document (which can't be null, cf check above). - if (parentXmlNode == null) { - parentXmlNode = doc; - } - - mXmlNode = doc.createElement(element_name); - parentXmlNode.appendChild(mXmlNode); - // Insert a separator after the tag, to make it easier to read - Text sep = doc.createTextNode("\n"); - parentXmlNode.appendChild(sep); - - // Set all initial attributes in the XML node if they are not empty. - // Iterate on the descriptor list to get the desired order and then use the - // internal values, if any. - for (AttributeDescriptor attr_desc : getAttributeDescriptors()) { - if (attr_desc instanceof XmlnsAttributeDescriptor) { - XmlnsAttributeDescriptor desc = (XmlnsAttributeDescriptor) attr_desc; - Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, - desc.getXmlNsName()); - attr.setValue(desc.getValue()); - attr.setPrefix(desc.getXmlNsPrefix()); - mXmlNode.getAttributes().setNamedItemNS(attr); - } else { - UiAttributeNode ui_attr = mUiAttributes.get(attr_desc); - commitAttributeToXml(ui_attr, ui_attr.getCurrentValue()); - } - } - - invokeUiUpdateListeners(UiUpdateState.CREATED); - return mXmlNode; - } - - /** - * Removes the XML node corresponding to this UI node if it exists - * and also removes all mirrored information in this UI node (i.e. children, attributes) - * - * @return The removed node or null if it didn't exist in the firtst place. - */ - public Node deleteXmlNode() { - if (mXmlNode == null) { - return null; - } - - // First clear the internals of the node and *then* actually deletes the XML - // node (because doing so will generate an update even and this node may be - // revisited via loadFromXmlNode). - Node old_xml_node = mXmlNode; - clearContent(); - - Node xml_parent = old_xml_node.getParentNode(); - if (xml_parent == null) { - xml_parent = getXmlDocument(); - } - old_xml_node = xml_parent.removeChild(old_xml_node); - - invokeUiUpdateListeners(UiUpdateState.DELETED); - return old_xml_node; - } - - /** - * Updates the element list for this UiElementNode. - * At the end, the list of children UiElementNode here will match the one from the - * provided XML {@link Node}: - *

                - *
              • Walk both the current ui children list and the xml children list at the same time. - *
              • If we have a new xml child but already reached the end of the ui child list, add the - * new xml node. - *
              • Otherwise, check if the xml node is referenced later in the ui child list and if so, - * move it here. It means the XML child list has been reordered. - *
              • Otherwise, this is a new XML node that we add in the middle of the ui child list. - *
              • At the end, we may have finished walking the xml child list but still have remaining - * ui children, simply delete them as they matching trailing xml nodes that have been - * removed unless they are mandatory ui nodes. - *
              - * Note that only the first case is used when populating the ui list the first time. - * - * @param xml_node The XML node to mirror - * @return True when the XML structure has changed. - */ - protected boolean updateElementList(Node xml_node) { - boolean structure_changed = false; - int ui_index = 0; - Node xml_child = xml_node.getFirstChild(); - while (xml_child != null) { - if (xml_child.getNodeType() == Node.ELEMENT_NODE) { - String element_name = xml_child.getNodeName(); - UiElementNode ui_node = null; - if (mUiChildren.size() <= ui_index) { - // A new node is being added at the end of the list - ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name, - false /* recursive */); - if (desc == null) { - // Unknown node. Create a temporary descriptor for it. - // most important we want to auto-add unknown attributes to it. - AndroidEditor editor = getEditor(); - IEditorInput editorInput = editor.getEditorInput(); - if (editorInput instanceof IFileEditorInput) { - IFileEditorInput fileInput = (IFileEditorInput)editorInput; - desc = CustomViewDescriptorService.getInstance().getDescriptor( - fileInput.getFile().getProject(), element_name); - if (desc == null) { - desc = new ElementDescriptor(element_name); - } - } else { - desc = new ElementDescriptor(element_name); - // TODO associate a new "?" icon to this descriptor. - } - } - structure_changed = true; - ui_node = appendNewUiChild(desc); - ui_index++; - } else { - // A new node is being inserted or moved. - // Note: mandatory nodes can be created without an XML node in which case - // getXmlNode() is null. - UiElementNode ui_child; - int n = mUiChildren.size(); - for (int j = ui_index; j < n; j++) { - ui_child = mUiChildren.get(j); - if (ui_child.getXmlNode() != null && ui_child.getXmlNode() == xml_child) { - if (j > ui_index) { - // Found the same XML node at some later index, now move it here. - mUiChildren.remove(j); - mUiChildren.add(ui_index, ui_child); - structure_changed = true; - } - ui_node = ui_child; - ui_index++; - break; - } - } - - if (ui_node == null) { - // Look for an unused mandatory node with no XML node attached - // referencing the same XML element name - for (int j = ui_index; j < n; j++) { - ui_child = mUiChildren.get(j); - if (ui_child.getXmlNode() == null && - ui_child.getDescriptor().isMandatory() && - ui_child.getDescriptor().getXmlName().equals(element_name)) { - if (j > ui_index) { - // Found it, now move it here - mUiChildren.remove(j); - mUiChildren.add(ui_index, ui_child); - } - // assign the XML node to this empty mandatory element. - ui_child.mXmlNode = xml_child; - structure_changed = true; - ui_node = ui_child; - ui_index++; - } - } - } - - if (ui_node == null) { - // Inserting new node - ElementDescriptor desc = mDescriptor.findChildrenDescriptor(element_name, - false /* recursive */); - if (desc == null) { - // Unknown element. Simply ignore it. - EditorsPlugin.log(IStatus.WARNING, - "AndroidManifest: Ignoring unknown '%s' XML element", //$NON-NLS-1$ - element_name); - } else { - structure_changed = true; - ui_node = insertNewUiChild(ui_index, desc); - ui_index++; - } - } - } - if (ui_node != null) { - // If we touched an UI Node, even an existing one, refresh its content. - // For new nodes, this will populate them recursively. - structure_changed |= ui_node.loadFromXmlNode(xml_child); - } - } - xml_child = xml_child.getNextSibling(); - } - - // There might be extra UI nodes at the end if the XML node list got shorter. - for (int index = mUiChildren.size() - 1; index >= ui_index; --index) { - structure_changed |= removeUiChildAtIndex(index); - } - - return structure_changed; - } - - /** - * Internal helper to remove an UI child node given by its index in the - * internal child list. - * - * Also invokes the update listener on the node to be deleted. - * - * @param ui_index The index of the UI child to remove, range 0 .. mUiChildren.size()-1 - * @return True if the structure has changed - * @throws IndexOutOfBoundsException if index is out of mUiChildren's bounds. Of course you - * know that could never happen unless the computer is on fire or something. - */ - private boolean removeUiChildAtIndex(int ui_index) { - UiElementNode ui_node = mUiChildren.get(ui_index); - invokeUiUpdateListeners(UiUpdateState.DELETED); - if (ui_node.getDescriptor().isMandatory()) { - // We can't remove a mandatory node, we just clear its content. - - // A mandatory node with no XML means it doesn't really exist, so it can't be - // deleted. - boolean xml_exists = (ui_node.getXmlNode() != null); - - ui_node.clearContent(); - return xml_exists; - } else { - mUiChildren.remove(ui_index); - return true; - } - } - - /** - * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} - * and appends it to the end of the element children list. - * - * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node. - * @return The new UI node that has been appended - */ - public UiElementNode appendNewUiChild(ElementDescriptor descriptor) { - UiElementNode ui_node; - ui_node = descriptor.createUiNode(); - mUiChildren.add(ui_node); - ui_node.setUiParent(this); - ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED); - return ui_node; - } - - /** - * Creates a new {@link UiElementNode} from the given {@link ElementDescriptor} - * and inserts it in the element children list at the specified position. - * - * @param index The position where to insert in the element children list. - * @param descriptor The {@link ElementDescriptor} that knows how to create the UI node. - * @return The new UI node. - */ - private UiElementNode insertNewUiChild(int index, ElementDescriptor descriptor) { - UiElementNode ui_node; - ui_node = descriptor.createUiNode(); - mUiChildren.add(index, ui_node); - ui_node.setUiParent(this); - ui_node.invokeUiUpdateListeners(UiUpdateState.CREATED); - return ui_node; - } - - /** - * Updates the {@link UiAttributeNode} list for this {@link UiElementNode}. - *

              - * For a given {@link UiElementNode}, the attribute list always exists in - * full and is totally independent of whether the XML model actually - * has the corresponding attributes. - *

              - * For each attribute declared in this {@link UiElementNode}, get - * the corresponding XML attribute. It may not exist, in which case the - * value will be null. We don't really know if a value has changed, so - * the updateValue() is called on the UI sattribute in all cases. - * - * @param xmlNode The XML node to mirror - */ - protected void updateAttributeList(Node xmlNode) { - NamedNodeMap xmlAttrMap = xmlNode.getAttributes(); - HashSet visited = new HashSet(); - - // For all known (i.e. expected) UI attributes, find an existing XML attribute of - // same (uri, local name) and update the internal Ui attribute value. - for (UiAttributeNode uiAttr : getInternalUiAttributes().values()) { - AttributeDescriptor desc = uiAttr.getDescriptor(); - if (!(desc instanceof SeparatorAttributeDescriptor)) { - Node xmlAttr = xmlAttrMap == null ? null : - xmlAttrMap.getNamedItemNS(desc.getNamespaceUri(), desc.getXmlLocalName()); - uiAttr.updateValue(xmlAttr); - visited.add(xmlAttr); - } - } - - // Clone the current list of unknown attributes. We'll then remove from this list when - // we still attributes which are still unknown. What will be left are the old unknown - // attributes that have been deleted in the current XML attribute list. - @SuppressWarnings("unchecked") //$NON-NLS-1$ - HashSet deleted = (HashSet) mUnknownUiAttributes.clone(); - - // We need to ignore hidden attributes. - Map hiddenAttrDesc = getHiddenAttributeDescriptors(); - - // Traverse the actual XML attribute list to find unknown attributes - if (xmlAttrMap != null) { - for (int i = 0; i < xmlAttrMap.getLength(); i++) { - Node xmlAttr = xmlAttrMap.item(i); - // Ignore attributes which have actual descriptors - if (visited.contains(xmlAttr)) { - continue; - } - - String xmlFullName = xmlAttr.getNodeName(); - - // Ignore attributes which are hidden (based on the prefix:localName key) - if (hiddenAttrDesc.containsKey(xmlFullName)) { - continue; - } - - String xmlAttrLocalName = xmlAttr.getLocalName(); - String xmlNsUri = xmlAttr.getNamespaceURI(); - - UiAttributeNode uiAttr = null; - for (UiAttributeNode a : mUnknownUiAttributes) { - String aLocalName = a.getDescriptor().getXmlLocalName(); - String aNsUri = a.getDescriptor().getNamespaceUri(); - if (aLocalName.equals(xmlAttrLocalName) && - (aNsUri == xmlNsUri || (aNsUri != null && aNsUri.equals(xmlNsUri)))) { - // This attribute is still present in the unknown list - uiAttr = a; - // It has not been deleted - deleted.remove(a); - break; - } - } - if (uiAttr == null) { - // Create a new unknown attribute - TextAttributeDescriptor desc = new TextAttributeDescriptor( - xmlAttrLocalName, // xml name - xmlFullName, // ui name - xmlNsUri, // NS uri - "Unknown XML attribute"); // tooltip, translatable - uiAttr = desc.createUiNode(this); - mUnknownUiAttributes.add(uiAttr); - } - - uiAttr.updateValue(xmlAttr); - } - - // Remove from the internal list unknown attributes that have been deleted from the xml - for (UiAttributeNode a : deleted) { - mUnknownUiAttributes.remove(a); - } - } - } - - /** - * Invoke all registered {@link IUiUpdateListener} listening on this UI updates for this node. - */ - protected void invokeUiUpdateListeners(UiUpdateState state) { - if (mUiUpdateListeners != null) { - for (IUiUpdateListener listener : mUiUpdateListeners) { - try { - listener.uiElementNodeUpdated(this, state); - } catch (Exception e) { - // prevent a crashing listener from crashing the whole invocation chain - EditorsPlugin.log(e, "UIElement Listener failed: %s, state=%s", //$NON-NLS-1$ - getBreadcrumbTrailDescription(true), - state.toString()); - } - } - } - } - - // --- for derived implementations only --- - - // TODO doc - protected void setXmlNode(Node xml_node) { - mXmlNode = xml_node; - } - - /** - * Sets the temporary data used by the editors. - * @param data the data. - */ - public void setEditData(Object data) { - mEditData = data; - } - - /** - * Returns the temporary data used by the editors for this object. - * @return the data, or null if none has been set. - */ - public Object getEditData() { - return mEditData; - } - - public void refreshUi() { - invokeUiUpdateListeners(UiUpdateState.ATTR_UPDATED); - } - - - // ------------- Helpers - - /** - * Helper method to commit a single attribute value to XML. - *

              - * This method updates the XML regardless of the current XML value. - * Callers should check first if an update is needed. - * If the new value is empty, the XML attribute will be actually removed. - *

              - * Note that the caller MUST ensure that modifying the underlying XML model is - * safe and must take care of marking the model as dirty if necessary. - * - * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode. - * @param newValue The new value to set. - * @return True if the XML attribute was modified or removed, false if nothing changed. - */ - public boolean commitAttributeToXml(UiAttributeNode uiAttr, String newValue) { - // Get (or create) the underlying XML element node that contains the attributes. - Node element = prepareCommit(); - if (element != null) { - String attrLocalName = uiAttr.getDescriptor().getXmlLocalName(); - String attrNsUri = uiAttr.getDescriptor().getNamespaceUri(); - - NamedNodeMap attrMap = element.getAttributes(); - if (newValue == null || newValue.length() == 0) { - // Remove attribute if it's empty - if (attrMap.getNamedItemNS(attrNsUri, attrLocalName) != null) { - attrMap.removeNamedItemNS(attrNsUri, attrLocalName); - return true; - } - } else { - // Add or replace an attribute - Document doc = element.getOwnerDocument(); - if (doc != null) { - Attr attr = doc.createAttributeNS(attrNsUri, attrLocalName); - attr.setValue(newValue); - attr.setPrefix(lookupNamespacePrefix(element, attrNsUri)); - attrMap.setNamedItemNS(attr); - return true; - } - } - } - return false; - } - - /** - * Returns the namespace prefix matching the requested namespace URI. - * If no such declaration is found, returns the default "android" prefix. - * - * @param node The current node. Must not be null. - * @param nsUri The namespace URI of which the prefix is to be found, - * e.g. AndroidConstants.NS_RESOURCES - * @return The first prefix declared or the default "android" prefix. - */ - private String lookupNamespacePrefix(Node node, String nsUri) { - // Note: Node.lookupPrefix is not implemented in wst/xml/core NodeImpl.java - // The following code emulates this simple call: - // String prefix = node.lookupPrefix(AndroidConstants.NS_RESOURCES); - - // if the requested URI is null, it denotes an attribute with no namespace. - if (nsUri == null) { - return null; - } - - // per XML specification, the "xmlns" URI is reserved - if (XmlnsAttributeDescriptor.XMLNS_URI.equals(nsUri)) { - return "xmlns"; //$NON-NLS-1$ - } - - HashSet visited = new HashSet(); - Document doc = node == null ? null : node.getOwnerDocument(); - - for (; node != null && node.getNodeType() == Node.ELEMENT_NODE; - node = node.getParentNode()) { - NamedNodeMap attrs = node.getAttributes(); - for (int n = attrs.getLength() - 1; n >= 0; --n) { - Node attr = attrs.item(n); - if ("xmlns".equals(attr.getPrefix())) { //$NON-NLS-1$ - String uri = attr.getNodeValue(); - String nsPrefix = attr.getLocalName(); - if (AndroidConstants.NS_RESOURCES.equals(uri)) { - return nsPrefix; - } - visited.add(nsPrefix); - } - } - } - - // Use a sensible default prefix if we can't find one. - // We need to make sure the prefix is not one that was declared in the scope - // visited above. - String prefix = AndroidConstants.NS_RESOURCES.equals(nsUri) ? "android" : "ns"; //$NON-NLS-1$ //$NON-NLS-2$ - String base = prefix; - for (int i = 1; visited.contains(prefix); i++) { - prefix = base + Integer.toString(i); - } - - // Also create & define this prefix/URI in the XML document as an attribute in the - // first element of the document. - if (doc != null) { - node = doc.getFirstChild(); - while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { - node = node.getNextSibling(); - } - if (node != null) { - Attr attr = doc.createAttributeNS(XmlnsAttributeDescriptor.XMLNS_URI, prefix); - attr.setValue(nsUri); - attr.setPrefix("xmlns"); //$NON-NLS-1$ - node.getAttributes().setNamedItemNS(attr); - } - } - - return prefix; - } - - /** - * Utility method to internally set the value of a text attribute for the current - * UiElementNode. - *

              - * This method is a helper. It silently ignores the errors such as the requested - * attribute not being present in the element or attribute not being settable. - * It accepts inherited attributes (such as layout). - *

              - * This does not commit to the XML model. It does mark the attribute node as dirty. - * This is up to the caller. - * - * @param attrXmlName The XML name of the attribute to modify - * @param value The new value for the attribute. - * @param override True if the value must be set even if one already exists. - */ - public void setAttributeValue(String attrXmlName, String value, boolean override) { - HashMap attributeMap = getInternalUiAttributes(); - - for (Entry entry : attributeMap.entrySet()) { - AttributeDescriptor ui_desc = entry.getKey(); - if (ui_desc.getXmlLocalName().equals(attrXmlName)) { - UiAttributeNode ui_attr = entry.getValue(); - // Not all attributes are editable, ignore those which are not - if (ui_attr instanceof IUiSettableAttributeNode) { - String current = ui_attr.getCurrentValue(); - if (override || current == null || current.length() == 0) { - ((IUiSettableAttributeNode) ui_attr).setCurrentValue(value); - // mark the attribute as dirty since their internal content - // as been modified, but not the underlying XML model - ui_attr.setDirty(true); - } - } - break; - } - } - } - - /** - * Utility method to retrieve the internal value of an attribute. - *

              - * Note that this retrieves the *field* value if the attribute has some UI, and - * not the actual XML value. They may differ if the attribute is dirty. - * - * @param attrXmlName The XML name of the attribute to modify - * @return The current internal value for the attribute or null in case of error. - */ - public String getAttributeValue(String attrXmlName) { - HashMap attributeMap = getInternalUiAttributes(); - - for (Entry entry : attributeMap.entrySet()) { - AttributeDescriptor ui_desc = entry.getKey(); - if (ui_desc.getXmlLocalName().equals(attrXmlName)) { - UiAttributeNode ui_attr = entry.getValue(); - return ui_attr.getCurrentValue(); - } - } - return null; - } - - // ------ IPropertySource methods - - public Object getEditableValue() { - return null; - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors() - * - * Returns the property descriptor for this node. Since the descriptors are not linked to the - * data, the AttributeDescriptor are used directly. - */ - public IPropertyDescriptor[] getPropertyDescriptors() { - List propDescs = new ArrayList(); - - // get the standard descriptors - HashMap attributeMap = getInternalUiAttributes(); - Set keys = attributeMap.keySet(); - - - // we only want the descriptor that do implement the IPropertyDescriptor interface. - for (AttributeDescriptor key : keys) { - if (key instanceof IPropertyDescriptor) { - propDescs.add((IPropertyDescriptor)key); - } - } - - // now get the descriptor from the unknown attributes - for (UiAttributeNode unknownNode : mUnknownUiAttributes) { - if (unknownNode.getDescriptor() instanceof IPropertyDescriptor) { - propDescs.add((IPropertyDescriptor)unknownNode.getDescriptor()); - } - } - - // TODO cache this maybe, as it's not going to change (except for unknown descriptors) - return propDescs.toArray(new IPropertyDescriptor[propDescs.size()]); - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang.Object) - * - * Returns the value of a given property. The id is the result of IPropertyDescriptor.getId(), - * which return the AttributeDescriptor itself. - */ - public Object getPropertyValue(Object id) { - HashMap attributeMap = getInternalUiAttributes(); - - UiAttributeNode attribute = attributeMap.get(id); - - if (attribute == null) { - // look for the id in the unknown attributes. - for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { - if (id == unknownAttr.getDescriptor()) { - return unknownAttr; - } - } - } - - return attribute; - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.views.properties.IPropertySource#isPropertySet(java.lang.Object) - * - * Returns whether the property is set. In our case this is if the string is non empty. - */ - public boolean isPropertySet(Object id) { - HashMap attributeMap = getInternalUiAttributes(); - - UiAttributeNode attribute = attributeMap.get(id); - - if (attribute != null) { - return attribute.getCurrentValue().length() > 0; - } - - // look for the id in the unknown attributes. - for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { - if (id == unknownAttr.getDescriptor()) { - return unknownAttr.getCurrentValue().length() > 0; - } - } - - return false; - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.views.properties.IPropertySource#resetPropertyValue(java.lang.Object) - * - * Reset the property to its default value. For now we simply empty it. - */ - public void resetPropertyValue(Object id) { - HashMap attributeMap = getInternalUiAttributes(); - - UiAttributeNode attribute = attributeMap.get(id); - if (attribute != null) { - // TODO: reset the value of the attribute - - return; - } - - // look for the id in the unknown attributes. - for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { - if (id == unknownAttr.getDescriptor()) { - // TODO: reset the value of the attribute - - return; - } - } - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.views.properties.IPropertySource#setPropertyValue(java.lang.Object, java.lang.Object) - * - * Set the property value. id is the result of IPropertyDescriptor.getId(), which is the - * AttributeDescriptor itself. Value should be a String. - */ - public void setPropertyValue(Object id, Object value) { - HashMap attributeMap = getInternalUiAttributes(); - - UiAttributeNode attribute = attributeMap.get(id); - - if (attribute == null) { - // look for the id in the unknown attributes. - for (UiAttributeNode unknownAttr : mUnknownUiAttributes) { - if (id == unknownAttr.getDescriptor()) { - attribute = unknownAttr; - break; - } - } - } - - if (attribute != null) { - final UiAttributeNode fAttribute = attribute; - - // get the current value and compare it to the new value - String oldValue = fAttribute.getCurrentValue(); - final String newValue = (String)value; - - if (oldValue.equals(newValue)) { - return; - } - - AndroidEditor editor = getEditor(); - editor.editXmlModel(new Runnable() { - public void run() { - commitAttributeToXml(fAttribute, newValue); - } - }); - } - } - - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java deleted file mode 100644 index 4e0a8c7..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * 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.editors.uimodel; - -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.FlagAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; - -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.resource.FontDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Rectangle; -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.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.dialogs.SelectionStatusDialog; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; - -/** - * Represents an XML attribute that is defined by a set of flag values, - * i.e. enum names separated by pipe (|) characters. - * - * Note: in Android resources, a "flag" is a list of fixed values where one or - * more values can be selected using an "or", e.g. "align='left|top'". - * By contrast, an "enum" is a list of fixed values of which only one can be - * selected at a given time, e.g. "gravity='right'". - *

              - * This class handles the "flag" case. - * The "enum" case is done using {@link UiListAttributeNode}. - */ -public class UiFlagAttributeNode extends UiTextAttributeNode { - - public UiFlagAttributeNode(FlagAttributeDescriptor attributeDescriptor, - UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public void createUiControl(Composite parent, IManagedForm managedForm) { - setManagedForm(managedForm); - FormToolkit toolkit = managedForm.getToolkit(); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - - Label label = toolkit.createLabel(parent, desc.getUiName()); - label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); - - Composite composite = toolkit.createComposite(parent); - composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); - GridLayout gl = new GridLayout(2, false); - gl.marginHeight = gl.marginWidth = 0; - composite.setLayout(gl); - // Fixes missing text borders under GTK... also requires adding a 1-pixel margin - // for the text field below - toolkit.paintBordersFor(composite); - - final Text text = toolkit.createText(composite, getCurrentValue()); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK - text.setLayoutData(gd); - final Button selectButton = toolkit.createButton(composite, "Select...", SWT.PUSH); - - setTextWidget(text); - - selectButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - - String currentText = getTextWidgetValue(); - - String result = showDialog(selectButton.getShell(), currentText); - - if (result != null) { - setTextWidgetValue(result); - } - } - }); - } - - /** - * Get the flag names, either from the initial names set in the attribute - * or by querying the framework resource parser. - */ - @Override - public String[] getPossibleValues() { - String attr_name = getDescriptor().getXmlLocalName(); - String element_name = getUiParent().getDescriptor().getXmlName(); - - String[] values = null; - - if (getDescriptor() instanceof FlagAttributeDescriptor && - ((FlagAttributeDescriptor) getDescriptor()).getNames() != null) { - // Get enum values from the descriptor - values = ((FlagAttributeDescriptor) getDescriptor()).getNames(); - } - - if (values == null) { - // or from the framework resource manager - values = FrameworkResourceManager.getInstance().getValues(element_name, attr_name); - } - - return values; - } - - /** - * Shows a dialog letting the user choose a set of enum, and returns a string - * containing the result. - */ - public String showDialog(Shell shell, String currentValue) { - FlagSelectionDialog dlg = new FlagSelectionDialog( - shell, currentValue.trim().split("\\s*\\|\\s*")); //$NON-NLS-1$ - dlg.open(); - Object[] result = dlg.getResult(); - if (result != null) { - StringBuilder buf = new StringBuilder(); - for (Object name : result) { - if (name instanceof String) { - if (buf.length() > 0) { - buf.append("|"); //$NON-NLS-1$ - } - buf.append(name); - } - } - - return buf.toString(); - } - - return null; - - } - - /** - * Displays a list of flag names with checkboxes. - */ - private class FlagSelectionDialog extends SelectionStatusDialog { - - private Set mCurrentSet; - private Table mTable; - - public FlagSelectionDialog(Shell parentShell, String[] currentNames) { - super(parentShell); - - mCurrentSet = new HashSet(); - for (String name : currentNames) { - if (name.length() > 0) { - mCurrentSet.add(name); - } - } - - int shellStyle = getShellStyle(); - setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); - } - - @Override - protected void computeResult() { - if (mTable != null) { - ArrayList results = new ArrayList(); - - for (TableItem item : mTable.getItems()) { - if (item.getChecked()) { - results.add((String)item.getData()); - } - } - - setResult(results); - } - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite composite= new Composite(parent, SWT.NONE); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - composite.setLayout(new GridLayout(1, true)); - composite.setFont(parent.getFont()); - - Label label = new Label(composite, SWT.NONE); - label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - label.setText(String.format("Select the flag values for attribute %1$s:", - ((FlagAttributeDescriptor) getDescriptor()).getUiName())); - - mTable = new Table(composite, SWT.CHECK | SWT.BORDER); - GridData data = new GridData(); - // The 60,18 hints are the ones used by AbstractElementListSelectionDialog - data.widthHint = convertWidthInCharsToPixels(60); - data.heightHint = convertHeightInCharsToPixels(18); - data.grabExcessVerticalSpace = true; - data.grabExcessHorizontalSpace = true; - data.horizontalAlignment = GridData.FILL; - data.verticalAlignment = GridData.FILL; - mTable.setLayoutData(data); - - mTable.setHeaderVisible(false); - final TableColumn column = new TableColumn(mTable, SWT.NONE); - - // List all the expected flag names and check those which are currently used - String[] names = getPossibleValues(); - if (names != null) { - for (String name : names) { - TableItem item = new TableItem(mTable, SWT.NONE); - item.setText(name); - item.setData(name); - - boolean hasName = mCurrentSet.contains(name); - item.setChecked(hasName); - if (hasName) { - mCurrentSet.remove(name); - } - } - } - - // If there are unknown flag names currently used, display them at the end if the - // table already checked. - if (!mCurrentSet.isEmpty()) { - FontDescriptor fontDesc = JFaceResources.getDialogFontDescriptor(); - fontDesc = fontDesc.withStyle(SWT.ITALIC); - Font font = fontDesc.createFont(JFaceResources.getDialogFont().getDevice()); - - for (String name : mCurrentSet) { - TableItem item = new TableItem(mTable, SWT.NONE); - item.setText(String.format("%1$s (unknown flag)", name)); - item.setData(name); - item.setChecked(true); - item.setFont(font); - } - } - - // Add a listener that will resize the column to the full width of the table - // so that only one column appears in the table even if the dialog is resized. - ControlAdapter listener = new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Rectangle r = mTable.getClientArea(); - column.setWidth(r.width); - } - }; - - mTable.addControlListener(listener); - listener.controlResized(null /* event not used */); - - // Add a selection listener that will check/uncheck items when they are double-clicked - mTable.addSelectionListener(new SelectionAdapter() { - /** Default selection means double-click on "most" platforms */ - @Override - public void widgetDefaultSelected(SelectionEvent e) { - if (e.item instanceof TableItem) { - TableItem i = (TableItem) e.item; - i.setChecked(!i.getChecked()); - } - super.widgetDefaultSelected(e); - } - }); - - Dialog.applyDialogFont(composite); - setHelpAvailable(false); - - return composite; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java deleted file mode 100644 index 261e146..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.ListAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; - -/** - * Represents an XML attribute which has possible built-in values, and can be modified by - * an editable Combo box. - *

              - * See {@link UiTextAttributeNode} for more information. - */ -public class UiListAttributeNode extends UiAbstractTextAttributeNode { - - protected Combo mCombo; - - public UiListAttributeNode(ListAttributeDescriptor attributeDescriptor, - UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public final void createUiControl(final Composite parent, IManagedForm managedForm) { - FormToolkit toolkit = managedForm.getToolkit(); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - - Label label = toolkit.createLabel(parent, desc.getUiName()); - label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); - - int style = SWT.DROP_DOWN; - mCombo = new Combo(parent, style); - TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE); - twd.maxWidth = 100; - mCombo.setLayoutData(twd); - - fillCombo(); - - setTextWidgetValue(getCurrentValue()); - - mCombo.addModifyListener(new ModifyListener() { - /** - * Sent when the text is modified, whether by the user via manual - * input or programmatic input via setText(). - *

              - * Simply mark the attribute as dirty if it really changed. - * The container SectionPart will collect these flag and manage them. - */ - public void modifyText(ModifyEvent e) { - if (!isInInternalTextModification() && - !isDirty() && - mCombo != null && - getCurrentValue() != null && - !mCombo.getText().equals(getCurrentValue())) { - setDirty(true); - } - } - }); - - // Remove self-reference when the widget is disposed - mCombo.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - mCombo = null; - } - }); - } - - protected void fillCombo() { - String[] values = getPossibleValues(); - - if (values == null) { - EditorsPlugin.log(IStatus.ERROR, - "FrameworkResourceManager did not provide values yet for %1$s", - getDescriptor().getXmlLocalName()); - } else { - for (String value : values) { - mCombo.add(value); - } - } - } - - /** - * Get the list values, either from the initial values set in the attribute - * or by querying the framework resource parser. - */ - @Override - public String[] getPossibleValues() { - AttributeDescriptor descriptor = getDescriptor(); - UiElementNode uiParent = getUiParent(); - - String attr_name = descriptor.getXmlLocalName(); - String element_name = uiParent.getDescriptor().getXmlName(); - - // FrameworkResourceManager expects a specific prefix for the attribute. - String prefix = ""; - if (AndroidConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) { - prefix = "android:"; //$NON-NLS-1$ - } else if (XmlnsAttributeDescriptor.XMLNS_URI.equals(descriptor.getNamespaceUri())) { - prefix = "xmlns:"; //$NON-NLS-1$ - } - attr_name = prefix + attr_name; - - String[] values = null; - - if (descriptor instanceof ListAttributeDescriptor && - ((ListAttributeDescriptor) descriptor).getValues() != null) { - // Get enum values from the descriptor - values = ((ListAttributeDescriptor) descriptor).getValues(); - } - - if (values == null) { - // or from the framework resource manager - // get the great-grand-parent descriptor. - - // the parent should always exist. - UiElementNode grandParentNode = uiParent.getUiParent(); - - String greatGrandParentNodeName = null; - if (grandParentNode != null) { - UiElementNode greatGrandParentNode = grandParentNode.getUiParent(); - if (greatGrandParentNode != null) { - greatGrandParentNodeName = greatGrandParentNode.getDescriptor().getXmlName(); - } - } - values = FrameworkResourceManager.getInstance().getValues(element_name, attr_name, - greatGrandParentNodeName); - } - - return values; - } - - @Override - public String getTextWidgetValue() { - if (mCombo != null) { - return mCombo.getText(); - } - - return null; - } - - @Override - public final boolean isValid() { - return mCombo != null; - } - - @Override - public void setTextWidgetValue(String value) { - if (mCombo != null) { - mCombo.setText(value); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java deleted file mode 100644 index 132ccc0..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.common.resources.FrameworkResourceManager; -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; -import com.android.ide.eclipse.editors.ui.SectionHelper; -import com.android.ide.eclipse.editors.wizards.ReferenceChooserDialog; -import com.android.ide.eclipse.editors.wizards.ResourceChooser; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.jface.window.Window; -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.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IFileEditorInput; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; - -/** - * Represents an XML attribute for a resource that can be modified using a simple text field or - * a dialog to choose an existing resource. - *

              - * It can be configured to represent any kind of resource, by providing the desired - * {@link ResourceType} in the constructor. - *

              - * See {@link UiTextAttributeNode} for more information. - */ -public class UiResourceAttributeNode extends UiTextAttributeNode { - - private ResourceType mType; - - public UiResourceAttributeNode(ResourceType type, - AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - - mType = type; - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public void createUiControl(final Composite parent, IManagedForm managedForm) { - setManagedForm(managedForm); - FormToolkit toolkit = managedForm.getToolkit(); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - - Label label = toolkit.createLabel(parent, desc.getUiName()); - label.setLayoutData(new TableWrapData(TableWrapData.LEFT, TableWrapData.MIDDLE)); - SectionHelper.addControlTooltip(label, DescriptorsUtils.formatTooltip(desc.getTooltip())); - - Composite composite = toolkit.createComposite(parent); - composite.setLayoutData(new TableWrapData(TableWrapData.FILL_GRAB, TableWrapData.MIDDLE)); - GridLayout gl = new GridLayout(2, false); - gl.marginHeight = gl.marginWidth = 0; - composite.setLayout(gl); - // Fixes missing text borders under GTK... also requires adding a 1-pixel margin - // for the text field below - toolkit.paintBordersFor(composite); - - final Text text = toolkit.createText(composite, getCurrentValue()); - GridData gd = new GridData(GridData.FILL_HORIZONTAL); - gd.horizontalIndent = 1; // Needed by the fixed composite borders under GTK - text.setLayoutData(gd); - Button browseButton = toolkit.createButton(composite, "Browse...", SWT.PUSH); - - setTextWidget(text); - - // TODO Add a validator using onAddModifyListener - - browseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - String result = showDialog(parent.getShell(), text.getText().trim()); - if (result != null) { - text.setText(result); - } - } - }); - } - - /** - * Shows a dialog letting the user choose a set of enum, and returns a string - * containing the result. - */ - public String showDialog(Shell shell, String currentValue) { - // we need to get the project of the manifest. - UiElementNode uiNode = getUiParent(); - AndroidEditor editor = uiNode.getEditor(); - IEditorInput input = editor.getEditorInput(); - if (input instanceof IFileEditorInput) { - // from the file editor we can get the IFile object, and from it, the IProject. - IFile file = ((IFileEditorInput)input).getFile(); - IProject project = file.getProject(); - - // get the resource repository for this project and the system resources. - IResourceRepository projectRepository = - ResourceManager.getInstance().getProjectResources(project); - - if (mType != null) { - IResourceRepository systemRepository = - FrameworkResourceManager.getInstance().getSystemResources(); - - // open a resource chooser dialog for specified resource type. - ResourceChooser dlg = new ResourceChooser(mType, - projectRepository, systemRepository, shell); - - dlg.setCurrentResource(currentValue); - - if (dlg.open() == Window.OK) { - return dlg.getCurrentResource(); - } - } else { - ReferenceChooserDialog dlg = new ReferenceChooserDialog(projectRepository, - shell); - - dlg.setCurrentResource(currentValue); - - if (dlg.open() == Window.OK) { - return dlg.getCurrentResource(); - } - } - } - - return null; - } - - @Override - public String[] getPossibleValues() { - // TODO: compute a list of existing resources for content assist completion - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java deleted file mode 100644 index 192f752..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.editors.uimodel; - -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; - -import org.eclipse.swt.SWT; -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.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.FormToolkit; -import org.eclipse.ui.forms.widgets.TableWrapData; -import org.eclipse.ui.forms.widgets.TableWrapLayout; -import org.w3c.dom.Node; - -/** - * {@link UiSeparatorAttributeNode} does not represent any real attribute. - *

              - * It is used to separate groups of attributes visually. - */ -public class UiSeparatorAttributeNode extends UiAttributeNode { - - /** Creates a new {@link UiAttributeNode} linked to a specific {@link AttributeDescriptor} */ - public UiSeparatorAttributeNode(SeparatorAttributeDescriptor attrDesc, - UiElementNode uiParent) { - super(attrDesc, uiParent); - } - - /** Returns the current value of the node. */ - @Override - public String getCurrentValue() { - // There is no value here. - return null; - } - - /** - * Sets whether the attribute is dirty and also notifies the editor some part's dirty - * flag as changed. - *

              - * Subclasses should set the to true as a result of user interaction with the widgets in - * the section and then should set to false when the commit() method completed. - */ - @Override - public void setDirty(boolean isDirty) { - // This is never dirty. - } - - /** - * Called once by the parent user interface to creates the necessary - * user interface to edit this attribute. - *

              - * This method can be called more than once in the life cycle of an UI node, - * typically when the UI is part of a master-detail tree, as pages are swapped. - * - * @param parent The composite where to create the user interface. - * @param managedForm The managed form owning this part. - */ - @Override - public void createUiControl(Composite parent, IManagedForm managedForm) { - FormToolkit toolkit = managedForm.getToolkit(); - Composite row = toolkit.createComposite(parent); - - TableWrapData twd = new TableWrapData(TableWrapData.FILL_GRAB); - if (parent.getLayout() instanceof TableWrapLayout) { - twd.colspan = ((TableWrapLayout) parent.getLayout()).numColumns; - } - row.setLayoutData(twd); - row.setLayout(new GridLayout(3, false /* equal width */)); - - Label sep = toolkit.createSeparator(row, SWT.HORIZONTAL); - GridData gd = new GridData(SWT.LEFT, SWT.CENTER, false, false); - gd.widthHint = 16; - sep.setLayoutData(gd); - - Label label = toolkit.createLabel(row, getDescriptor().getXmlLocalName()); - label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); - - sep = toolkit.createSeparator(row, SWT.HORIZONTAL); - sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - } - - /** No completion values for this UI attribute. */ - @Override - public String[] getPossibleValues() { - return null; - } - - /** - * Called when the XML is being loaded or has changed to - * update the value held by this user interface attribute node. - *

              - * The XML Node may be null, which denotes that the attribute is not - * specified in the XML model. In general, this means the "default" value of the - * attribute should be used. - *

              - * The caller doesn't really know if attributes have changed, - * so it will call this to refresh the attribute anyway. It's up to the - * UI implementation to minimize refreshes. - * - * @param xml_attribute_node - */ - @Override - public void updateValue(Node xml_attribute_node) { - // No value to update. - } - - /** - * Called by the user interface when the editor is saved or its state changed - * and the modified attributes must be committed (i.e. written) to the XML model. - *

              - * Important behaviors: - *

                - *
              • The caller *must* have called IStructuredModel.aboutToChangeModel before. - * The implemented methods must assume it is safe to modify the XML model. - *
              • On success, the implementation *must* call setDirty(false). - *
              • On failure, the implementation can fail with an exception, which - * is trapped and logged by the caller, or do nothing, whichever is more - * appropriate. - *
              - */ - @Override - public void commit() { - // No value to commit. - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java deleted file mode 100644 index 4c53f4c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.TextAttributeDescriptor; -import com.android.ide.eclipse.editors.ui.SectionHelper; - -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Text; -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.widgets.TableWrapData; - -/** - * Represents an XML attribute in that can be modified using a simple text field - * in the XML editor's user interface. - *

              - * The XML attribute has no default value. When unset, the text field is blank. - * When updating the XML, if the field is empty, the attribute will be removed - * from the XML element. - *

              - * See {@link UiAttributeNode} for more information. - */ -public class UiTextAttributeNode extends UiAbstractTextAttributeNode { - - /** Text field */ - private Text mText; - /** The managed form, set only once createUiControl has been called. */ - private IManagedForm mManagedForm; - - public UiTextAttributeNode(AttributeDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /* (non-java doc) - * Creates a label widget and an associated text field. - *

              - * As most other parts of the android manifest editor, this assumes the - * parent uses a table layout with 2 columns. - */ - @Override - public void createUiControl(Composite parent, IManagedForm managedForm) { - setManagedForm(managedForm); - TextAttributeDescriptor desc = (TextAttributeDescriptor) getDescriptor(); - Text text = SectionHelper.createLabelAndText(parent, managedForm.getToolkit(), - desc.getUiName(), getCurrentValue(), - DescriptorsUtils.formatTooltip(desc.getTooltip())); - - setTextWidget(text); - } - - /** No completion values for this UI attribute. */ - @Override - public String[] getPossibleValues() { - return null; - } - - /** - * Sets the internal managed form. - * This is usually set by createUiControl. - */ - protected void setManagedForm(IManagedForm managedForm) { - mManagedForm = managedForm; - } - - /** - * @return The managed form, set only once createUiControl has been called. - */ - protected IManagedForm getManagedForm() { - return mManagedForm; - } - - /* (non-java doc) - * Returns if the attribute node is valid, and its UI has been created. - */ - @Override - public boolean isValid() { - return mText != null; - } - - @Override - public String getTextWidgetValue() { - if (mText != null) { - return mText.getText(); - } - - return null; - } - - @Override - public void setTextWidgetValue(String value) { - if (mText != null) { - mText.setText(value); - } - } - - /** - * Sets the Text widget object, and prepares it to handle modification and synchronization - * with the XML node. - * @param textWidget - */ - protected final void setTextWidget(Text textWidget) { - mText = textWidget; - - if (textWidget != null) { - // Sets the with hint for the text field. Derived classes can always override it. - // This helps the grid layout to resize correctly on smaller screen sizes. - Object data = textWidget.getLayoutData(); - if (data == null) { - } else if (data instanceof GridData) { - ((GridData)data).widthHint = AndroidEditor.TEXT_WIDTH_HINT; - } else if (data instanceof TableWrapData) { - ((TableWrapData)data).maxWidth = 100; - } - - mText.addModifyListener(new ModifyListener() { - /** - * Sent when the text is modified, whether by the user via manual - * input or programmatic input via setText(). - *

              - * Simply mark the attribute as dirty if it really changed. - * The container SectionPart will collect these flag and manage them. - */ - public void modifyText(ModifyEvent e) { - if (!isInInternalTextModification() && - !isDirty() && - mText != null && - getCurrentValue() != null && - !mText.getText().equals(getCurrentValue())) { - setDirty(true); - } - } - }); - - // Remove self-reference when the widget is disposed - mText.addDisposeListener(new DisposeListener() { - public void widgetDisposed(DisposeEvent e) { - mText = null; - } - }); - } - - onAddValidators(mText); - } - - /** - * Called after the text widget as been created. - *

              - * Derived classes typically want to: - *

            • Create a new {@link ModifyListener} and attach it to the given {@link Text} widget. - *
            • In the modify listener, call getManagedForm().getMessageManager().addMessage() - * and getManagedForm().getMessageManager().removeMessage() as necessary. - *
            • Call removeMessage in a new text.addDisposeListener. - *
            • Call the validator once to setup the initial messages as needed. - *

              - * The base implementation does nothing. - * - * @param text The {@link Text} widget to validate. - */ - protected void onAddValidators(Text text) { - } - - /** - * Returns the text widget. - */ - protected final Text getTextWidget() { - return mText; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java deleted file mode 100644 index 5c1db05..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/uimodel/UiTextValueNode.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.uimodel; - -import com.android.ide.eclipse.editors.descriptors.TextValueDescriptor; - -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.Text; - -/** - * Represents an XML element value in that can be modified using a simple text field - * in the XML editor's user interface. - */ -public class UiTextValueNode extends UiTextAttributeNode { - - public UiTextValueNode(TextValueDescriptor attributeDescriptor, UiElementNode uiParent) { - super(attributeDescriptor, uiParent); - } - - /** - * Updates the current text field's value when the XML has changed. - *

              - * The caller doesn't really know if value of the element has changed, - * so it will call this to refresh the value anyway. The value - * is only set if it has changed. - *

              - * This also resets the "dirty" flag. - */ - @Override - public void updateValue(Node xml_attribute_node) { - setCurrentValue(DEFAULT_VALUE); - - // The argument xml_attribute_node is not used here. It should always be - // null since this is not an attribute. What we want is the "text value" of - // the parent element, which is actually the first text node of the element. - - UiElementNode parent = getUiParent(); - if (parent != null) { - Node xml_node = parent.getXmlNode(); - if (xml_node != null) { - for (Node xml_child = xml_node.getFirstChild(); - xml_child != null; - xml_child = xml_child.getNextSibling()) { - if (xml_child.getNodeType() == Node.TEXT_NODE) { - setCurrentValue(xml_child.getNodeValue()); - break; - } - } - } - } - - if (isValid() && !getTextWidgetValue().equals(getCurrentValue())) { - try { - setInInternalTextModification(true); - setTextWidgetValue(getCurrentValue()); - setDirty(false); - } finally { - setInInternalTextModification(false); - } - } - } - - /* (non-java doc) - * Called by the user interface when the editor is saved or its state changed - * and the modified "attributes" must be committed (i.e. written) to the XML model. - */ - @Override - public void commit() { - UiElementNode parent = getUiParent(); - if (parent != null && isValid() && isDirty()) { - // Get (or create) the underlying XML element node that contains the value. - Node element = parent.prepareCommit(); - if (element != null) { - String value = getTextWidgetValue(); - - // Try to find an existing text child to update. - boolean updated = false; - - for (Node xml_child = element.getFirstChild(); - xml_child != null; - xml_child = xml_child.getNextSibling()) { - if (xml_child.getNodeType() == Node.TEXT_NODE) { - xml_child.setNodeValue(value); - updated = true; - break; - } - } - - // If we didn't find a text child to update, we need to create one. - if (!updated) { - Document doc = element.getOwnerDocument(); - if (doc != null) { - Text text = doc.createTextNode(value); - element.appendChild(text); - } - } - - setCurrentValue(value); - } - } - setDirty(false); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java deleted file mode 100644 index 39cfc58..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ConfigurationSelector.java +++ /dev/null @@ -1,1272 +0,0 @@ -/* - * 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.editors.wizards; - -import com.android.ide.eclipse.editors.resources.configurations.CountryCodeQualifier; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier; -import com.android.ide.eclipse.editors.resources.configurations.LanguageQualifier; -import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier; -import com.android.ide.eclipse.editors.resources.configurations.NetworkCodeQualifier; -import com.android.ide.eclipse.editors.resources.configurations.PixelDensityQualifier; -import com.android.ide.eclipse.editors.resources.configurations.RegionQualifier; -import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.editors.resources.configurations.ScreenDimensionQualifier; -import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier; -import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier; -import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier; -import com.android.ide.eclipse.editors.resources.configurations.KeyboardStateQualifier.KeyboardState; -import com.android.ide.eclipse.editors.resources.configurations.NavigationMethodQualifier.NavigationMethod; -import com.android.ide.eclipse.editors.resources.configurations.ScreenOrientationQualifier.ScreenOrientation; -import com.android.ide.eclipse.editors.resources.configurations.TextInputMethodQualifier.TextInputMethod; -import com.android.ide.eclipse.editors.resources.configurations.TouchScreenQualifier.TouchScreenType; -import com.android.ide.eclipse.editors.resources.manager.ResourceManager; - -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITableLabelProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.StackLayout; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.events.FocusAdapter; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.VerifyEvent; -import org.eclipse.swt.events.VerifyListener; -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; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -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 java.util.HashMap; - -/** - * Custom UI widget to let user build a Folder configuration. - *

              - * To use this, instantiate somewhere in the UI and then: - *

                - *
              • Use {@link #setConfiguration(String)} or {@link #setConfiguration(FolderConfiguration)}. - *
              • Retrieve the configuration using {@link #getConfiguration(FolderConfiguration)} and - * test it using {@link FolderConfiguration#isValid()}. - *
              - */ -public class ConfigurationSelector extends Composite { - - public static final int WIDTH_HINT = 600; - public static final int HEIGHT_HINT = 250; - - private Runnable mOnChangeListener; - - private TableViewer mFullTableViewer; - private TableViewer mSelectionTableViewer; - private Button mAddButton; - private Button mRemoveButton; - private StackLayout mStackLayout; - - private boolean mOnRefresh = false; - - private final FolderConfiguration mBaseConfiguration = new FolderConfiguration(); - private final FolderConfiguration mSelectedConfiguration = new FolderConfiguration(); - - private final HashMap, QualifierEditBase> mUiMap = - new HashMap, QualifierEditBase>(); - private Composite mQualifierEditParent; - - /** - * Basic of {@link VerifyListener} to only accept digits. - */ - private static class DigitVerifier implements VerifyListener { - public void verifyText(VerifyEvent e) { - // check for digit only. - for (int i = 0 ; i < e.text.length(); i++) { - char letter = e.text.charAt(i); - if (letter < '0' || letter > '9') { - e.doit = false; - return; - } - } - } - } - - /** - * Implementation of {@link VerifyListener} for Country Code qualifiers. - */ - public static class MobileCodeVerifier extends DigitVerifier { - @Override - public void verifyText(VerifyEvent e) { - super.verifyText(e); - - // basic tests passed? - if (e.doit) { - // check the max 3 digits. - if (e.text.length() - e.end + e.start + - ((Text)e.getSource()).getText().length() > 3) { - e.doit = false; - } - } - } - } - - /** - * Implementation of {@link VerifyListener} for the Language and Region qualifiers. - */ - public static class LanguageRegionVerifier implements VerifyListener { - public void verifyText(VerifyEvent e) { - // check for length - if (e.text.length() - e.end + e.start + ((Combo)e.getSource()).getText().length() > 2) { - e.doit = false; - return; - } - - // check for lower case only. - for (int i = 0 ; i < e.text.length(); i++) { - char letter = e.text.charAt(i); - if ((letter < 'a' || letter > 'z') && (letter < 'A' || letter > 'Z')) { - e.doit = false; - return; - } - } - } - } - - /** - * Implementation of {@link VerifyListener} for the Pixel Density qualifier. - */ - public static class DensityVerifier extends DigitVerifier { } - - /** - * Implementation of {@link VerifyListener} for the Screen Dimension qualifier. - */ - public static class DimensionVerifier extends DigitVerifier { } - - /** - * Enum for the state of the configuration being created. - */ - public enum ConfigurationState { - OK, INVALID_CONFIG, REGION_WITHOUT_LANGUAGE; - } - - public ConfigurationSelector(Composite parent) { - super(parent, SWT.NONE); - - mBaseConfiguration.createDefault(); - - GridLayout gl = new GridLayout(4, false); - gl.marginWidth = gl.marginHeight = 0; - setLayout(gl); - - // first column is the first table - final Table fullTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); - fullTable.setLayoutData(new GridData(GridData.FILL_BOTH)); - fullTable.setHeaderVisible(true); - fullTable.setLinesVisible(true); - - // create the column - final TableColumn fullTableColumn = new TableColumn(fullTable, SWT.LEFT); - // set the header - fullTableColumn.setText("Available Qualifiers"); - - fullTable.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Rectangle r = fullTable.getClientArea(); - fullTableColumn.setWidth(r.width); - } - }); - - mFullTableViewer = new TableViewer(fullTable); - mFullTableViewer.setContentProvider(new QualifierContentProvider()); - mFullTableViewer.setLabelProvider(new QualifierLabelProvider( - false /* showQualifierValue */)); - mFullTableViewer.setInput(mBaseConfiguration); - mFullTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - ISelection selection = event.getSelection(); - if (selection instanceof IStructuredSelection) { - IStructuredSelection structSelection = (IStructuredSelection)selection; - Object first = structSelection.getFirstElement(); - - if (first instanceof ResourceQualifier) { - mAddButton.setEnabled(true); - return; - } - } - - mAddButton.setEnabled(false); - } - }); - - // 2nd column is the left/right arrow button - Composite buttonComposite = new Composite(this, SWT.NONE); - gl = new GridLayout(1, false); - gl.marginWidth = gl.marginHeight = 0; - buttonComposite.setLayout(gl); - buttonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL)); - - new Composite(buttonComposite, SWT.NONE); - mAddButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); - mAddButton.setText("->"); - mAddButton.setEnabled(false); - mAddButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - IStructuredSelection selection = - (IStructuredSelection)mFullTableViewer.getSelection(); - - Object first = selection.getFirstElement(); - if (first instanceof ResourceQualifier) { - ResourceQualifier qualifier = (ResourceQualifier)first; - - mBaseConfiguration.removeQualifier(qualifier); - mSelectedConfiguration.addQualifier(qualifier); - - mFullTableViewer.refresh(); - mSelectionTableViewer.refresh(); - mSelectionTableViewer.setSelection(new StructuredSelection(qualifier), true); - - onChange(false /* keepSelection */); - } - } - }); - - mRemoveButton = new Button(buttonComposite, SWT.BORDER | SWT.PUSH); - mRemoveButton.setText("<-"); - mRemoveButton.setEnabled(false); - mRemoveButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - IStructuredSelection selection = - (IStructuredSelection)mSelectionTableViewer.getSelection(); - - Object first = selection.getFirstElement(); - if (first instanceof ResourceQualifier) { - ResourceQualifier qualifier = (ResourceQualifier)first; - - mSelectedConfiguration.removeQualifier(qualifier); - mBaseConfiguration.addQualifier(qualifier); - - mFullTableViewer.refresh(); - mSelectionTableViewer.refresh(); - - onChange(false /* keepSelection */); - } - } - }); - - // 3rd column is the selected config table - final Table selectionTable = new Table(this, SWT.SINGLE | SWT.FULL_SELECTION | SWT.BORDER); - selectionTable.setLayoutData(new GridData(GridData.FILL_BOTH)); - selectionTable.setHeaderVisible(true); - selectionTable.setLinesVisible(true); - - // create the column - final TableColumn selectionTableColumn = new TableColumn(selectionTable, SWT.LEFT); - // set the header - selectionTableColumn.setText("Chosen Qualifiers"); - - selectionTable.addControlListener(new ControlAdapter() { - @Override - public void controlResized(ControlEvent e) { - Rectangle r = selectionTable.getClientArea(); - selectionTableColumn.setWidth(r.width); - } - }); - mSelectionTableViewer = new TableViewer(selectionTable); - mSelectionTableViewer.setContentProvider(new QualifierContentProvider()); - mSelectionTableViewer.setLabelProvider(new QualifierLabelProvider( - true /* showQualifierValue */)); - mSelectionTableViewer.setInput(mSelectedConfiguration); - mSelectionTableViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - // ignore selection changes during resfreshes in some cases. - if (mOnRefresh) { - return; - } - - ISelection selection = event.getSelection(); - if (selection instanceof IStructuredSelection) { - IStructuredSelection structSelection = (IStructuredSelection)selection; - - if (structSelection.isEmpty() == false) { - Object first = structSelection.getFirstElement(); - - if (first instanceof ResourceQualifier) { - mRemoveButton.setEnabled(true); - - QualifierEditBase composite = mUiMap.get(first.getClass()); - - if (composite != null) { - composite.setQualifier((ResourceQualifier)first); - } - - mStackLayout.topControl = composite; - mQualifierEditParent.layout(); - - return; - } - } else { - mStackLayout.topControl = null; - mQualifierEditParent.layout(); - } - } - - mRemoveButton.setEnabled(false); - } - }); - - // 4th column is the detail of the selected qualifier - mQualifierEditParent = new Composite(this, SWT.NONE); - mQualifierEditParent.setLayout(mStackLayout = new StackLayout()); - mQualifierEditParent.setLayoutData(new GridData(GridData.FILL_VERTICAL)); - - // create the UI for all the qualifiers, and associate them to the ResourceQualifer class. - mUiMap.put(CountryCodeQualifier.class, new MCCEdit(mQualifierEditParent)); - mUiMap.put(NetworkCodeQualifier.class, new MNCEdit(mQualifierEditParent)); - mUiMap.put(LanguageQualifier.class, new LanguageEdit(mQualifierEditParent)); - mUiMap.put(RegionQualifier.class, new RegionEdit(mQualifierEditParent)); - mUiMap.put(ScreenOrientationQualifier.class, new OrientationEdit(mQualifierEditParent)); - mUiMap.put(PixelDensityQualifier.class, new PixelDensityEdit(mQualifierEditParent)); - mUiMap.put(TouchScreenQualifier.class, new TouchEdit(mQualifierEditParent)); - mUiMap.put(KeyboardStateQualifier.class, new KeyboardEdit(mQualifierEditParent)); - mUiMap.put(TextInputMethodQualifier.class, new TextInputEdit(mQualifierEditParent)); - mUiMap.put(NavigationMethodQualifier.class, new NavigationEdit(mQualifierEditParent)); - mUiMap.put(ScreenDimensionQualifier.class, new ScreenDimensionEdit(mQualifierEditParent)); - } - - /** - * Sets a listener to be notified when the configuration changes. - * @param listener A {@link Runnable} whose run() method is called when the - * configuration is changed. The method is called from the UI thread. - */ - public void setOnChangeListener(Runnable listener) { - mOnChangeListener = listener; - } - - /** - * Initialize the UI with a given {@link FolderConfiguration}. This must - * be called from the UI thread. - * @param config The configuration. - */ - public void setConfiguration(FolderConfiguration config) { - mSelectedConfiguration.set(config); - mSelectionTableViewer.refresh(); - - // create the base config, which is the default config minus the qualifiers - // in SelectedConfiguration - mBaseConfiguration.substract(mSelectedConfiguration); - mFullTableViewer.refresh(); - } - - /** - * Initialize the UI with the configuration represented by a resource folder name. - * This must be called from the UI thread. - * - * @param folderSegments the segments of the folder name, - * split using {@link FolderConfiguration#QUALIFIER_SEP}. - * @return true if success, or false if the folder name is not a valid name. - */ - public boolean setConfiguration(String[] folderSegments) { - FolderConfiguration config = ResourceManager.getInstance().getConfig(folderSegments); - - if (config == null) { - return false; - } - - setConfiguration(config); - - return true; - } - - /** - * Initialize the UI with the configuration represented by a resource folder name. - * This must be called from the UI thread. - * @param folderName the name of the folder. - * @return true if success, or false if the folder name is not a valid name. - */ - public boolean setConfiguration(String folderName) { - // split the name of the folder in segments. - String[] folderSegments = folderName.split(FolderConfiguration.QUALIFIER_SEP); - - return setConfiguration(folderSegments); - } - - /** - * Gets the configuration as setup by the widget. - * @param config the {@link FolderConfiguration} object to be filled with the information - * from the UI. - */ - public void getConfiguration(FolderConfiguration config) { - config.set(mSelectedConfiguration); - } - - /** - * Returns the state of the configuration being edited/created. - */ - public ConfigurationState getState() { - if (mSelectedConfiguration.getInvalidQualifier() != null) { - return ConfigurationState.INVALID_CONFIG; - } - - if (mSelectedConfiguration.checkRegion() == false) { - return ConfigurationState.REGION_WITHOUT_LANGUAGE; - } - - return ConfigurationState.OK; - } - - /** - * Returns the first invalid qualifier of the configuration being edited/created, - * or null if they are all valid (or if none exists). - *

              If {@link #getState()} return {@link ConfigurationState#INVALID_CONFIG} then this will - * not return null. - */ - public ResourceQualifier getInvalidQualifier() { - return mSelectedConfiguration.getInvalidQualifier(); - } - - /** - * Handle changes in the configuration. - * @param keepSelection if true attemps to avoid triggering selection change in - * {@link #mSelectedConfiguration}. - */ - private void onChange(boolean keepSelection) { - ISelection selection = null; - if (keepSelection) { - mOnRefresh = true; - selection = mSelectionTableViewer.getSelection(); - } - - mSelectionTableViewer.refresh(true); - - if (keepSelection) { - mSelectionTableViewer.setSelection(selection); - mOnRefresh = false; - } - - if (mOnChangeListener != null) { - mOnChangeListener.run(); - } - } - - /** - * Content provider around a {@link FolderConfiguration}. - */ - private static class QualifierContentProvider implements IStructuredContentProvider { - - private FolderConfiguration mInput; - - public QualifierContentProvider() { - } - - public void dispose() { - // pass - } - - public Object[] getElements(Object inputElement) { - return mInput.getQualifiers(); - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - mInput = null; - if (newInput instanceof FolderConfiguration) { - mInput = (FolderConfiguration)newInput; - } - } - } - - /** - * Label provider for {@link ResourceQualifier} objects. - */ - private static class QualifierLabelProvider implements ITableLabelProvider { - - private final boolean mShowQualifierValue; - - public QualifierLabelProvider(boolean showQualifierValue) { - mShowQualifierValue = showQualifierValue; - } - - public String getColumnText(Object element, int columnIndex) { - // only one column, so we can ignore columnIndex - if (element instanceof ResourceQualifier) { - if (mShowQualifierValue) { - String value = ((ResourceQualifier)element).getStringValue(); - if (value.length() == 0) { - return String.format("%1$s (?)", - ((ResourceQualifier)element).getShortName()); - } else { - return value; - } - - } else { - return ((ResourceQualifier)element).getShortName(); - } - } - - return null; - } - - public Image getColumnImage(Object element, int columnIndex) { - // only one column, so we can ignore columnIndex - if (element instanceof ResourceQualifier) { - return ((ResourceQualifier)element).getIcon(); - } - - return null; - } - - public void addListener(ILabelProviderListener listener) { - // pass - } - - public void dispose() { - // pass - } - - public boolean isLabelProperty(Object element, String property) { - // pass - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // pass - } - } - - /** - * Base class for Edit widget for {@link ResourceQualifier}. - */ - private abstract static class QualifierEditBase extends Composite { - - public QualifierEditBase(Composite parent, String title) { - super(parent, SWT.NONE); - setLayout(new GridLayout(1, false)); - - new Label(this, SWT.NONE).setText(title); - } - - public abstract void setQualifier(ResourceQualifier qualifier); - } - - /** - * Edit widget for {@link CountryCodeQualifier}. - */ - private class MCCEdit extends QualifierEditBase { - - private Text mText; - - public MCCEdit(Composite parent) { - super(parent, CountryCodeQualifier.NAME); - - mText = new Text(this, SWT.BORDER); - mText.addVerifyListener(new MobileCodeVerifier()); - mText.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onTextChange(); - } - }); - - mText.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - onTextChange(); - } - }); - } - - private void onTextChange() { - String value = mText.getText(); - - if (value.length() == 0) { - // empty string, means a qualifier with no value. - // 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.setCountryCodeQualifier(new CountryCodeQualifier()); - } else { - try { - CountryCodeQualifier qualifier = CountryCodeQualifier.getQualifier( - CountryCodeQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mSelectedConfiguration.setCountryCodeQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong - // (for instance not exactly 3 digits). - mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); - } - } catch (NumberFormatException nfe) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - mSelectedConfiguration.setCountryCodeQualifier(new CountryCodeQualifier()); - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - CountryCodeQualifier q = (CountryCodeQualifier)qualifier; - - mText.setText(Integer.toString(q.getCode())); - } - } - - /** - * Edit widget for {@link NetworkCodeQualifier}. - */ - private class MNCEdit extends QualifierEditBase { - private Text mText; - - public MNCEdit(Composite parent) { - super(parent, NetworkCodeQualifier.NAME); - - mText = new Text(this, SWT.BORDER); - mText.addVerifyListener(new MobileCodeVerifier()); - mText.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onTextChange(); - } - }); - mText.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - onTextChange(); - } - }); - } - - private void onTextChange() { - String value = mText.getText(); - - if (value.length() == 0) { - // empty string, means a qualifier with no value. - // 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.setNetworkCodeQualifier(new NetworkCodeQualifier()); - } else { - try { - NetworkCodeQualifier qualifier = NetworkCodeQualifier.getQualifier( - NetworkCodeQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mSelectedConfiguration.setNetworkCodeQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong - // (for instance not exactly 3 digits). - mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); - } - } catch (NumberFormatException nfe) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - mSelectedConfiguration.setNetworkCodeQualifier(new NetworkCodeQualifier()); - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - NetworkCodeQualifier q = (NetworkCodeQualifier)qualifier; - - mText.setText(Integer.toString(q.getCode())); - } - } - - /** - * Edit widget for {@link LanguageQualifier}. - */ - private class LanguageEdit extends QualifierEditBase { - private Combo mLanguage; - - public LanguageEdit(Composite parent) { - super(parent, LanguageQualifier.NAME); - - mLanguage = new Combo(this, SWT.DROP_DOWN); - mLanguage.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mLanguage.addVerifyListener(new LanguageRegionVerifier()); - mLanguage.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onLanguageChange(); - } - public void widgetSelected(SelectionEvent e) { - onLanguageChange(); - } - }); - mLanguage.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onLanguageChange(); - } - }); - } - - private void onLanguageChange() { - // update the current config - String value = mLanguage.getText(); - - if (value.length() == 0) { - // empty string, 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.setLanguageQualifier(new LanguageQualifier()); - } else { - LanguageQualifier qualifier = null; - String segment = LanguageQualifier.getFolderSegment(value); - if (segment != null) { - qualifier = LanguageQualifier.getQualifier(segment); - } - - if (qualifier != null) { - mSelectedConfiguration.setLanguageQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - mSelectedConfiguration.setLanguageQualifier(new LanguageQualifier()); - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - LanguageQualifier q = (LanguageQualifier)qualifier; - - String value = q.getValue(); - if (value != null) { - mLanguage.setText(value); - } - } - } - - /** - * Edit widget for {@link RegionQualifier}. - */ - private class RegionEdit extends QualifierEditBase { - private Combo mRegion; - - public RegionEdit(Composite parent) { - super(parent, RegionQualifier.NAME); - - mRegion = new Combo(this, SWT.DROP_DOWN); - mRegion.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mRegion.addVerifyListener(new LanguageRegionVerifier()); - mRegion.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onRegionChange(); - } - public void widgetSelected(SelectionEvent e) { - onRegionChange(); - } - }); - mRegion.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onRegionChange(); - } - }); - } - - private void onRegionChange() { - // update the current config - String value = mRegion.getText(); - - if (value.length() == 0) { - // empty string, 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.setRegionQualifier(new RegionQualifier()); - } else { - RegionQualifier qualifier = null; - String segment = RegionQualifier.getFolderSegment(value); - if (segment != null) { - qualifier = RegionQualifier.getQualifier(segment); - } - - if (qualifier != null) { - mSelectedConfiguration.setRegionQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong (for instance a one letter string). - mSelectedConfiguration.setRegionQualifier(new RegionQualifier()); - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - RegionQualifier q = (RegionQualifier)qualifier; - - String value = q.getValue(); - if (value != null) { - mRegion.setText(q.getValue()); - } - } - } - - /** - * Edit widget for {@link ScreenOrientationQualifier}. - */ - private class OrientationEdit extends QualifierEditBase { - - private Combo mOrientation; - - public OrientationEdit(Composite parent) { - super(parent, ScreenOrientationQualifier.NAME); - - mOrientation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - ScreenOrientation[] soValues = ScreenOrientation.values(); - for (ScreenOrientation value : soValues) { - mOrientation.add(value.getDisplayValue()); - } - - mOrientation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mOrientation.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onOrientationChange(); - } - public void widgetSelected(SelectionEvent e) { - onOrientationChange(); - } - }); - } - - protected void onOrientationChange() { - // update the current config - int index = mOrientation.getSelectionIndex(); - - if (index != -1) { - mSelectedConfiguration.setScreenOrientationQualifier(new ScreenOrientationQualifier( - ScreenOrientation.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.setScreenOrientationQualifier( - new ScreenOrientationQualifier()); - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - ScreenOrientationQualifier q = (ScreenOrientationQualifier)qualifier; - - ScreenOrientation value = q.getValue(); - if (value == null) { - mOrientation.clearSelection(); - } else { - mOrientation.select(ScreenOrientation.getIndex(value)); - } - } - } - - /** - * Edit widget for {@link PixelDensityQualifier}. - */ - private class PixelDensityEdit extends QualifierEditBase { - private Text mText; - - public PixelDensityEdit(Composite parent) { - super(parent, PixelDensityQualifier.NAME); - - mText = new Text(this, SWT.BORDER); - mText.addVerifyListener(new DensityVerifier()); - mText.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onTextChange(); - } - }); - mText.addFocusListener(new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - onTextChange(); - } - }); - } - - private void onTextChange() { - String value = mText.getText(); - - if (value.length() == 0) { - // empty string, means a qualifier with no value. - // 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.setPixelDensityQualifier(new PixelDensityQualifier()); - } else { - try { - PixelDensityQualifier qualifier = PixelDensityQualifier.getQualifier( - PixelDensityQualifier.getFolderSegment(Integer.parseInt(value))); - if (qualifier != null) { - mSelectedConfiguration.setPixelDensityQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong - // (for instance a one letter string). - // We do nothing in this case. - return; - } - } catch (NumberFormatException nfe) { - // Looks like the code is not a number. This should not happen since the text - // field has a VerifyListener that prevents it. - // We do nothing in this case. - return; - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - PixelDensityQualifier q = (PixelDensityQualifier)qualifier; - - mText.setText(Integer.toString(q.getValue())); - } - } - - /** - * Edit widget for {@link TouchScreenQualifier}. - */ - private class TouchEdit extends QualifierEditBase { - - private Combo mTouchScreen; - - public TouchEdit(Composite parent) { - super(parent, TouchScreenQualifier.NAME); - - mTouchScreen = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - TouchScreenType[] tstValues = TouchScreenType.values(); - for (TouchScreenType value : tstValues) { - mTouchScreen.add(value.getDisplayValue()); - } - - mTouchScreen.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mTouchScreen.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onTouchChange(); - } - public void widgetSelected(SelectionEvent e) { - onTouchChange(); - } - }); - } - - protected void onTouchChange() { - // update the current config - int index = mTouchScreen.getSelectionIndex(); - - if (index != -1) { - mSelectedConfiguration.setTouchTypeQualifier(new TouchScreenQualifier( - TouchScreenType.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.setTouchTypeQualifier(new TouchScreenQualifier()); - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - TouchScreenQualifier q = (TouchScreenQualifier)qualifier; - - TouchScreenType value = q.getValue(); - if (value == null) { - mTouchScreen.clearSelection(); - } else { - mTouchScreen.select(TouchScreenType.getIndex(value)); - } - } - } - - /** - * Edit widget for {@link KeyboardStateQualifier}. - */ - private class KeyboardEdit extends QualifierEditBase { - - private Combo mKeyboard; - - public KeyboardEdit(Composite parent) { - super(parent, KeyboardStateQualifier.NAME); - - mKeyboard = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - KeyboardState[] ksValues = KeyboardState.values(); - for (KeyboardState value : ksValues) { - mKeyboard.add(value.getDisplayValue()); - } - - mKeyboard.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mKeyboard.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onKeyboardChange(); - } - public void widgetSelected(SelectionEvent e) { - onKeyboardChange(); - } - }); - } - - protected void onKeyboardChange() { - // update the current config - int index = mKeyboard.getSelectionIndex(); - - if (index != -1) { - mSelectedConfiguration.setKeyboardStateQualifier(new KeyboardStateQualifier( - KeyboardState.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.setKeyboardStateQualifier( - new KeyboardStateQualifier()); - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - KeyboardStateQualifier q = (KeyboardStateQualifier)qualifier; - - KeyboardState value = q.getValue(); - if (value == null) { - mKeyboard.clearSelection(); - } else { - mKeyboard.select(KeyboardState.getIndex(value)); - } - } - } - - /** - * Edit widget for {@link TextInputMethodQualifier}. - */ - private class TextInputEdit extends QualifierEditBase { - - private Combo mTextInput; - - public TextInputEdit(Composite parent) { - super(parent, TextInputMethodQualifier.NAME); - - mTextInput = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - TextInputMethod[] timValues = TextInputMethod.values(); - for (TextInputMethod value : timValues) { - mTextInput.add(value.getDisplayValue()); - } - - mTextInput.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mTextInput.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onTextInputChange(); - } - public void widgetSelected(SelectionEvent e) { - onTextInputChange(); - } - }); - } - - protected void onTextInputChange() { - // update the current config - int index = mTextInput.getSelectionIndex(); - - if (index != -1) { - mSelectedConfiguration.setTextInputMethodQualifier(new TextInputMethodQualifier( - TextInputMethod.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.setTextInputMethodQualifier( - new TextInputMethodQualifier()); - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - TextInputMethodQualifier q = (TextInputMethodQualifier)qualifier; - - TextInputMethod value = q.getValue(); - if (value == null) { - mTextInput.clearSelection(); - } else { - mTextInput.select(TextInputMethod.getIndex(value)); - } - } - } - - /** - * Edit widget for {@link NavigationMethodQualifier}. - */ - private class NavigationEdit extends QualifierEditBase { - - private Combo mNavigation; - - public NavigationEdit(Composite parent) { - super(parent, NavigationMethodQualifier.NAME); - - mNavigation = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY); - NavigationMethod[] nmValues = NavigationMethod.values(); - for (NavigationMethod value : nmValues) { - mNavigation.add(value.getDisplayValue()); - } - - mNavigation.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mNavigation.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - onNavigationChange(); - } - public void widgetSelected(SelectionEvent e) { - onNavigationChange(); - } - }); - } - - protected void onNavigationChange() { - // update the current config - int index = mNavigation.getSelectionIndex(); - - if (index != -1) { - mSelectedConfiguration.setNavigationMethodQualifier(new NavigationMethodQualifier( - NavigationMethod.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.setNavigationMethodQualifier( - new NavigationMethodQualifier()); - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - NavigationMethodQualifier q = (NavigationMethodQualifier)qualifier; - - NavigationMethod value = q.getValue(); - if (value == null) { - mNavigation.clearSelection(); - } else { - mNavigation.select(NavigationMethod.getIndex(value)); - } - } - } - - /** - * Edit widget for {@link ScreenDimensionQualifier}. - */ - private class ScreenDimensionEdit extends QualifierEditBase { - - private Text mSize1; - private Text mSize2; - - public ScreenDimensionEdit(Composite parent) { - super(parent, ScreenDimensionQualifier.NAME); - - ModifyListener modifyListener = new ModifyListener() { - public void modifyText(ModifyEvent e) { - onSizeChange(); - } - }; - - FocusAdapter focusListener = new FocusAdapter() { - @Override - public void focusLost(FocusEvent e) { - onSizeChange(); - } - }; - - mSize1 = new Text(this, SWT.BORDER); - mSize1.addVerifyListener(new DimensionVerifier()); - mSize1.addModifyListener(modifyListener); - mSize1.addFocusListener(focusListener); - - mSize2 = new Text(this, SWT.BORDER); - mSize2.addVerifyListener(new DimensionVerifier()); - mSize2.addModifyListener(modifyListener); - mSize2.addFocusListener(focusListener); - } - - private void onSizeChange() { - // update the current config - String size1 = mSize1.getText(); - String size2 = mSize2.getText(); - - // if only one of the strings is empty, do nothing - if ((size1.length() == 0) ^ (size2.length() == 0)) { - return; - } else if (size1.length() == 0 && size2.length() == 0) { - // empty size, 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.setScreenDimensionQualifier(new ScreenDimensionQualifier()); - } else { - ScreenDimensionQualifier qualifier = ScreenDimensionQualifier.getQualifier(size1, - size2); - - if (qualifier != null) { - mSelectedConfiguration.setScreenDimensionQualifier(qualifier); - } else { - // Failure! Looks like the value is wrong. - // we do nothing in this case. - return; - } - } - - // notify of change - onChange(true /* keepSelection */); - } - - @Override - public void setQualifier(ResourceQualifier qualifier) { - ScreenDimensionQualifier q = (ScreenDimensionQualifier)qualifier; - - mSize1.setText(Integer.toString(q.getValue1())); - mSize2.setText(Integer.toString(q.getValue2())); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java deleted file mode 100644 index 2f3209b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileCreationPage.java +++ /dev/null @@ -1,1009 +0,0 @@ -/* - * 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.editors.wizards; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.ProjectChooserHelper; -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.LayoutDescriptors; -import com.android.ide.eclipse.editors.menu.descriptors.MenuDescriptors; -import com.android.ide.eclipse.editors.resources.configurations.FolderConfiguration; -import com.android.ide.eclipse.editors.resources.configurations.ResourceQualifier; -import com.android.ide.eclipse.editors.resources.descriptors.ResourcesDescriptors; -import com.android.ide.eclipse.editors.resources.manager.ResourceFolderType; -import com.android.ide.eclipse.editors.wizards.ConfigurationSelector.ConfigurationState; -import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; - -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IResource; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Path; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.events.SelectionAdapter; -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.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; - -/** - * This is the single page of the {@link NewXmlFileWizard} which provides the ability to create - * skeleton XML resources files for Android projects. - *

              - * This page is used to select the project, the resource folder, resource type and file name. - */ -class NewXmlFileCreationPage extends WizardPage { - - /** - * Information on one type of resource that can be created (e.g. menu, pref, layout, etc.) - */ - static class TypeInfo { - private final String mUiName; - private final ResourceFolderType mResFolderType; - private final String mTooltip; - private final Object mRootSeed; - private Button mWidget; - private ArrayList mRoots = new ArrayList(); - private final String mXmlns; - private final String mDefaultAttrs; - private final String mDefaultRoot; - - public TypeInfo(String uiName, - String tooltip, - ResourceFolderType resFolderType, - Object rootSeed, - String defaultRoot, - String xmlns, - String defaultAttrs) { - mUiName = uiName; - mResFolderType = resFolderType; - mTooltip = tooltip; - mRootSeed = rootSeed; - mDefaultRoot = defaultRoot; - mXmlns = xmlns; - mDefaultAttrs = defaultAttrs; - } - - /** Returns the UI name for the resource type. Unique. Never null. */ - String getUiName() { - return mUiName; - } - - /** Returns the tooltip for the resource type. Can be null. */ - String getTooltip() { - return mTooltip; - } - - /** - * Returns the name of the {@link ResourceFolderType}. - * Never null but not necessarily unique, - * e.g. two types use {@link ResourceFolderType#XML}. - */ - String getResFolderName() { - return mResFolderType.getName(); - } - - /** - * Returns the matching {@link ResourceFolderType}. - * Never null but not necessarily unique, - * e.g. two types use {@link ResourceFolderType#XML}. - */ - ResourceFolderType getResFolderType() { - return mResFolderType; - } - - /** Sets the radio button associate with the resource type. Can be null. */ - void setWidget(Button widget) { - mWidget = widget; - } - - /** Returns the radio button associate with the resource type. Can be null. */ - Button getWidget() { - return mWidget; - } - - /** - * Returns the seed used to fill the root element values. - * The seed might be either a String, a String array, an {@link ElementDescriptor}, - * a {@link DocumentDescriptor} or null. - */ - Object getRootSeed() { - return mRootSeed; - } - - /** Returns the default root element that should be selected by default. Can be null. */ - String getDefaultRoot() { - return mDefaultRoot; - } - - /** - * Returns the list of all possible root elements for the resource type. - * This can be an empty ArrayList but not null. - *

              - * TODO: the root list SHOULD depend on the currently selected project, to include - * custom classes. - */ - ArrayList getRoots() { - return mRoots; - } - - /** - * If the generated resource XML file requires an "android" XMLNS, this should be set - * to {@link AndroidConstants#NS_RESOURCES}. When it is null, no XMLNS is generated. - */ - String getXmlns() { - return mXmlns; - } - - /** - * When not null, this represent extra attributes that must be specified in the - * root element of the generated XML file. When null, no extra attributes are inserted. - */ - String getDefaultAttrs() { - return mDefaultAttrs; - } - } - - /** - * TypeInfo, information for each "type" of file that can be created. - */ - private static final TypeInfo[] sTypes = { - new TypeInfo( - "Layout", // UI name - "An XML file that describes a screen layout.", // tooltip - ResourceFolderType.LAYOUT, // folder type - LayoutDescriptors.getInstance().getDescriptor(), // root seed - "LinearLayout", // default root - AndroidConstants.NS_RESOURCES, // xmlns - "android:layout_width=\"wrap_content\"\n" + // default attributes - "android:layout_height=\"wrap_content\"" - ), - new TypeInfo("Values", // UI name - "An XML file with simple values: colors, strings, dimensions, etc.", // tooltip - ResourceFolderType.VALUES, // folder type - ResourcesDescriptors.ROOT_ELEMENT, // root seed - null, // default root - null, // xmlns - null // default attributes - ), - new TypeInfo("Menu", // UI name - "An XML file that describes an menu.", // tooltip - ResourceFolderType.MENU, // folder type - MenuDescriptors.MENU_ROOT_ELEMENT, // root seed - null, // default root - AndroidConstants.NS_RESOURCES, // xmlns - null // default attributes - ), - new TypeInfo("Preference", // UI name - "An XML file that describes preferences.", // tooltip - ResourceFolderType.XML, // folder type - XmlDescriptors.getInstance().getPreferencesDescriptor(), // root seed - AndroidConstants.CLASS_PREFERENCE_SCREEN, // default root - AndroidConstants.NS_RESOURCES, // xmlns - null // default attributes - ), - new TypeInfo("Searchable", // UI name - "An XML file that describes a searchable [TODO].", // tooltip - ResourceFolderType.XML, // folder type - XmlDescriptors.getInstance().getSearchableDescriptor(), // root seed - null, // default root - AndroidConstants.NS_RESOURCES, // xmlns - null // default attributes - ), - new TypeInfo("Animation", // UI name - "An XML file that describes an animation.", // tooltip - ResourceFolderType.ANIM, // folder type - // TODO reuse constants if we ever make an editor with descriptors for animations - new String[] { // root seed - "set", //$NON-NLS-1$ - "alpha", //$NON-NLS-1$ - "scale", //$NON-NLS-1$ - "translate", //$NON-NLS-1$ - "rotate" //$NON-NLS-1$ - }, - "set", //$NON-NLS-1$ // default root - null, // xmlns - null // default attributes - ), - }; - - /** Absolute destination folder root, e.g. "/res/" */ - private static String sResFolderAbs = AndroidConstants.WS_RESOURCES + AndroidConstants.WS_SEP; - /** Relative destination folder root, e.g. "res/" */ - private static String sResFolderRel = AndroidConstants.FD_RESOURCES + AndroidConstants.WS_SEP; - - private IProject mProject; - private Text mProjectTextField; - private Button mProjectBrowseButton; - private Text mFileNameTextField; - private Text mWsFolderPathTextField; - private Combo mRootElementCombo; - private IStructuredSelection mInitialSelection; - private ConfigurationSelector mConfigSelector; - private FolderConfiguration mTempConfig = new FolderConfiguration(); - private boolean mInternalWsFolderPathUpdate; - private boolean mInternalTypeUpdate; - private boolean mInternalConfigSelectorUpdate; - private ProjectChooserHelper mProjectChooserHelper; - - - // --- UI creation --- - - /** - * Constructs a new {@link NewXmlFileCreationPage}. - *

              - * Called by {@link NewXmlFileWizard#createMainPage()}. - */ - protected NewXmlFileCreationPage(String pageName) { - super(pageName); - setPageComplete(false); - } - - public void setInitialSelection(IStructuredSelection initialSelection) { - mInitialSelection = initialSelection; - } - - /** - * Called by the parent Wizard to create the UI for this Wizard Page. - * - * {@inheritDoc} - * - * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) - */ - public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NULL); - composite.setFont(parent.getFont()); - - initializeDialogUnits(parent); - - composite.setLayout(new GridLayout(3, false /*makeColumnsEqualWidth*/)); - composite.setLayoutData(new GridData(GridData.FILL_BOTH)); - - createProjectGroup(composite); - createTypeGroup(composite); - createRootGroup(composite); - - // Show description the first time - setErrorMessage(null); - setMessage(null); - setControl(composite); - - // Update state the first time - initializeRootValues(); - initializeFromSelection(mInitialSelection); - validatePage(); - } - - /** - * Returns the target project or null. - */ - public IProject getProject() { - return mProject; - } - - /** - * Returns the destination filename or an empty string. - */ - public String getFileName() { - return mFileNameTextField == null ? "" : mFileNameTextField.getText(); //$NON-NLS-1$ - } - - /** - * Returns the destination folder path relative to the project or an empty string. - */ - public String getWsFolderPath() { - return mWsFolderPathTextField == null ? "" : mWsFolderPathTextField.getText(); //$NON-NLS-1$ - } - - - /** - * Returns an {@link IFile} on the destination file. - *

              - * Uses {@link #getProject()}, {@link #getWsFolderPath()} and {@link #getFileName()}. - *

              - * Returns null if the project, filename or folder are invalid and the destination file - * cannot be determined. - *

              - * The {@link IFile} is a resource. There might or might not be an actual real file. - */ - public IFile getDestinationFile() { - IProject project = getProject(); - String wsFolderPath = getWsFolderPath(); - String fileName = getFileName(); - if (project != null && wsFolderPath.length() > 0 && fileName.length() > 0) { - IPath dest = new Path(wsFolderPath).append(fileName); - IFile file = project.getFile(dest); - return file; - } - return null; - } - - /** - * Returns the {@link TypeInfo} for the currently selected type radio button. - * Returns null if no radio button is selected. - * - * @return A {@link TypeInfo} or null. - */ - public TypeInfo getSelectedType() { - TypeInfo type = null; - for (TypeInfo ti : sTypes) { - if (ti.getWidget().getSelection()) { - type = ti; - break; - } - } - return type; - } - - /** - * Returns the selected root element string, if any. - * - * @return The selected root element string or null. - */ - public String getRootElement() { - int index = mRootElementCombo.getSelectionIndex(); - if (index >= 0) { - return mRootElementCombo.getItem(index); - } - return null; - } - - // --- UI creation --- - - /** - * Helper method to create a new GridData with an horizontal span. - * - * @param horizSpan The number of cells for the horizontal span. - * @return A new GridData with the horizontal span. - */ - private GridData newGridData(int horizSpan) { - GridData gd = new GridData(); - gd.horizontalSpan = horizSpan; - return gd; - } - - /** - * Helper method to create a new GridData with an horizontal span and a style. - * - * @param horizSpan The number of cells for the horizontal span. - * @param style The style, e.g. {@link GridData#FILL_HORIZONTAL} - * @return A new GridData with the horizontal span and the style. - */ - private GridData newGridData(int horizSpan, int style) { - GridData gd = new GridData(style); - gd.horizontalSpan = horizSpan; - return gd; - } - - /** - * Helper method that creates an empty cell in the parent composite. - * - * @param parent The parent composite. - */ - private void emptyCell(Composite parent) { - new Label(parent, SWT.NONE); - } - - /** - * Creates the project & filename fields. - *

              - * The parent must be a GridLayout with 3 colums. - */ - private void createProjectGroup(Composite parent) { - // project name - String tooltip = "The Android Project where the new resource file will be created."; - Label label = new Label(parent, SWT.NONE); - label.setText("Project"); - label.setToolTipText(tooltip); - - mProjectTextField = new Text(parent, SWT.BORDER); - mProjectTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mProjectTextField.setToolTipText(tooltip); - - mProjectBrowseButton = new Button(parent, SWT.NONE); - mProjectBrowseButton.setText("Browse..."); - mProjectBrowseButton.setToolTipText("Allows you to select the Android project to modify."); - mProjectBrowseButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - onProjectBrowse(); - } - }); - mProjectChooserHelper = new ProjectChooserHelper(parent.getShell()); - - // file name - tooltip = "The name of the resource file to create."; - label = new Label(parent, SWT.NONE); - label.setText("File"); - label.setToolTipText(tooltip); - - mFileNameTextField = new Text(parent, SWT.BORDER); - mFileNameTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mFileNameTextField.setToolTipText(tooltip); - mFileNameTextField.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - validatePage(); - } - }); - - emptyCell(parent); - } - - /** - * Creates the type field, {@link ConfigurationSelector} and the folder field. - *

              - * The parent must be a GridLayout with 3 colums. - */ - private void createTypeGroup(Composite parent) { - // separator - Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); - label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL)); - - // label before type radios - label = new Label(parent, SWT.NONE); - label.setText("What type of resource would you like to create?"); - label.setLayoutData(newGridData(3)); - - // display the types on three columns of radio buttons. - emptyCell(parent); - Composite grid = new Composite(parent, SWT.NONE); - emptyCell(parent); - - grid.setLayout(new GridLayout(3, true /*makeColumnsEqualWidth*/)); - - SelectionListener radioListener = new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - // single-click. Only do something if activated. - if (e.getSource() instanceof Button) { - onRadioTypeUpdated((Button) e.getSource()); - } - } - }; - - int n = sTypes.length; - int num_lines = n/3; - for (int line = 0; line < num_lines; line++) { - for (int i = 0; i < 3; i++) { - TypeInfo type = sTypes[line * 3 + i]; - Button radio = new Button(grid, SWT.RADIO); - type.setWidget(radio); - radio.setSelection(false); - radio.setText(type.getUiName()); - radio.setToolTipText(type.getTooltip()); - radio.addSelectionListener(radioListener); - } - } - - // label before configuration selector - label = new Label(parent, SWT.NONE); - label.setText("What type of resource configuration would you like?"); - label.setLayoutData(newGridData(3)); - - // configuration selector - emptyCell(parent); - mConfigSelector = new ConfigurationSelector(parent); - GridData gd = newGridData(2, GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL); - gd.widthHint = ConfigurationSelector.WIDTH_HINT; - gd.heightHint = ConfigurationSelector.HEIGHT_HINT; - mConfigSelector.setLayoutData(gd); - mConfigSelector.setOnChangeListener(new onConfigSelectorUpdated()); - - // folder name - String tooltip = "The folder where the file will be generated, relative to the project."; - label = new Label(parent, SWT.NONE); - label.setText("Folder"); - label.setToolTipText(tooltip); - - mWsFolderPathTextField = new Text(parent, SWT.BORDER); - mWsFolderPathTextField.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mWsFolderPathTextField.setToolTipText(tooltip); - mWsFolderPathTextField.addModifyListener(new ModifyListener() { - public void modifyText(ModifyEvent e) { - onWsFolderPathUpdated(); - } - }); - - emptyCell(parent); - } - - /** - * Creates the root element combo. - *

              - * The parent must be a GridLayout with 3 colums. - */ - private void createRootGroup(Composite parent) { - // separator - Label label = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL); - label.setLayoutData(newGridData(3, GridData.GRAB_HORIZONTAL)); - - // label before the root combo - String tooltip = "The root element to create in the XML file."; - label = new Label(parent, SWT.NONE); - label.setText("Select the root element for the XML file:"); - label.setLayoutData(newGridData(3)); - label.setToolTipText(tooltip); - - // root combo - emptyCell(parent); - - mRootElementCombo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); - mRootElementCombo.setEnabled(false); - mRootElementCombo.select(0); - mRootElementCombo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); - mRootElementCombo.setToolTipText(tooltip); - - emptyCell(parent); - } - - /** - * Called by {@link NewXmlFileWizard} to initialize the page with the selection - * received by the wizard -- typically the current user workbench selection. - *

              - * Things we expect to find out from the selection: - *

                - *
              • The project name, valid if it's an android nature.
              • - *
              • The current folder, valid if it's a folder under /res
              • - *
              • An existing filename, in which case the user will be asked whether to override it.
              • - *
                  - * - * @param selection The selection when the wizard was initiated. - */ - private void initializeFromSelection(IStructuredSelection selection) { - if (selection == null) { - return; - } - - - // Find the best match in the element list. In case there are multiple selected elements - // select the one that provides the most information and assign them a score, - // e.g. project=1 + folder=2 + file=4. - IProject targetProject = null; - String targetWsFolderPath = null; - String targetFileName = null; - int targetScore = 0; - for (Object element : selection.toList()) { - if (element instanceof IAdaptable) { - IResource res = (IResource) ((IAdaptable) element).getAdapter(IResource.class); - IProject project = res != null ? res.getProject() : null; - - // Is this an Android project? - try { - if (project == null || !project.hasNature(AndroidConstants.NATURE)) { - continue; - } - } catch (CoreException e) { - // checking the nature failed, ignore this resource - continue; - } - - int score = 1; // we have a valid project at least - - IPath wsFolderPath = null; - String fileName = null; - if (res.getType() == IResource.FOLDER) { - wsFolderPath = res.getProjectRelativePath(); - } else if (res.getType() == IResource.FILE) { - fileName = res.getName(); - wsFolderPath = res.getParent().getProjectRelativePath(); - } - - // Disregard this folder selection if it doesn't point to /res/something - if (wsFolderPath != null && - wsFolderPath.segmentCount() > 1 && - AndroidConstants.FD_RESOURCES.equals(wsFolderPath.segment(0))) { - score += 2; - } else { - wsFolderPath = null; - fileName = null; - } - - score += fileName != null ? 4 : 0; - - if (score > targetScore) { - targetScore = score; - targetProject = project; - targetWsFolderPath = wsFolderPath != null ? wsFolderPath.toString() : null; - targetFileName = fileName; - } - } - } - - // Now set the UI accordingly - if (targetScore > 0) { - mProject = targetProject; - mProjectTextField.setText(targetProject != null ? targetProject.getName() : ""); //$NON-NLS-1$ - mFileNameTextField.setText(targetFileName != null ? targetFileName : ""); //$NON-NLS-1$ - mWsFolderPathTextField.setText(targetWsFolderPath != null ? targetWsFolderPath : ""); //$NON-NLS-1$ - } - } - - /** - * Initialize the root values of the type infos based on the current framework values. - */ - private void initializeRootValues() { - for (TypeInfo type : sTypes) { - ArrayList roots = type.getRoots(); - if (roots.size() > 0) { - continue; - } - - // depending of the type of the seed, initialize the root in different ways - Object rootSeed = type.getRootSeed(); - - if (rootSeed instanceof String) { - // The seed is a single string, Add it as-is. - roots.add((String) rootSeed); - } else if (rootSeed instanceof String[]) { - // The seed is an array of strings. Add them as-is. - for (String value : (String[]) rootSeed) { - roots.add(value); - } - } else if (rootSeed instanceof ElementDescriptor) { - // The seed is an element descriptor or a document descriptor. - // In this case add all the children element descriptors defined, recursively, - // and avoid infinite recursion by keeping track of what has already been added. - - HashSet visited = new HashSet(); - initRootElementDescriptor(roots, (ElementDescriptor) rootSeed, visited); - - // Sort alphabetically. - Collections.sort(roots); - } - } - } - - /** - * Helper method to recursively insert all XML names for the given {@link ElementDescriptor} - * into the roots array list. Keeps track of visited nodes to avoid infinite recursion. - * Also avoids inserting the top {@link DocumentDescriptor} which is generally synthetic - * and not a valid root element. - */ - private void initRootElementDescriptor(ArrayList roots, - ElementDescriptor desc, HashSet visited) { - if (!(desc instanceof DocumentDescriptor)) { - String xmlName = desc.getXmlName(); - if (xmlName != null && xmlName.length() > 0) { - roots.add(xmlName); - } - } - - visited.add(desc); - - for (ElementDescriptor child : desc.getChildren()) { - if (!visited.contains(child)) { - initRootElementDescriptor(roots, child, visited); - } - } - } - - /** - * Callback called when the user uses the "Browse Projects" button. - */ - private void onProjectBrowse() { - IJavaProject p = mProjectChooserHelper.chooseJavaProject(mProjectTextField.getText()); - if (p != null) { - mProject = p.getProject(); - mProjectTextField.setText(mProject.getName()); - validatePage(); - } - } - - /** - * Callback called when the Folder text field is changed, either programmatically - * or by the user. - */ - private void onWsFolderPathUpdated() { - if (mInternalWsFolderPathUpdate) { - return; - } - - String wsFolderPath = mWsFolderPathTextField.getText(); - - // This is a custom path, we need to sanitize it. - // First it should start with "/res/". Then we need to make sure there are no - // relative paths, things like "../" or "./" or even "//". - wsFolderPath = wsFolderPath.replaceAll("/+\\.\\./+|/+\\./+|//+|\\\\+|^/+", "/"); //$NON-NLS-1$ //$NON-NLS-2$ - wsFolderPath = wsFolderPath.replaceAll("^\\.\\./+|^\\./+", ""); //$NON-NLS-1$ //$NON-NLS-2$ - wsFolderPath = wsFolderPath.replaceAll("/+\\.\\.$|/+\\.$|/+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ - - ArrayList matches = new ArrayList(); - - // We get "res/foo" from selections relative to the project when we want a "/res/foo" path. - if (wsFolderPath.startsWith(sResFolderRel)) { - wsFolderPath = sResFolderAbs + wsFolderPath.substring(sResFolderRel.length()); - - mInternalWsFolderPathUpdate = true; - mWsFolderPathTextField.setText(wsFolderPath); - mInternalWsFolderPathUpdate = false; - } - - if (wsFolderPath.startsWith(sResFolderAbs)) { - wsFolderPath = wsFolderPath.substring(sResFolderAbs.length()); - - int pos = wsFolderPath.indexOf(AndroidConstants.WS_SEP_CHAR); - if (pos >= 0) { - wsFolderPath = wsFolderPath.substring(0, pos); - } - - String[] folderSegments = wsFolderPath.split(FolderConfiguration.QUALIFIER_SEP); - - if (folderSegments.length > 0) { - String folderName = folderSegments[0]; - - // update config selector - mInternalConfigSelectorUpdate = true; - mConfigSelector.setConfiguration(folderSegments); - mInternalConfigSelectorUpdate = false; - - boolean selected = false; - for (TypeInfo type : sTypes) { - if (type.getResFolderName().equals(folderName)) { - matches.add(type); - selected |= type.getWidget().getSelection(); - } - } - - if (matches.size() == 1) { - // If there's only one match, select it if it's not already selected - if (!selected) { - selectType(matches.get(0)); - } - } else if (matches.size() > 1) { - // There are multiple type candidates for this folder. This can happen - // for /res/xml for example. Check to see if one of them is currently - // selected. If yes, leave the selection unchanged. If not, deselect all type. - if (!selected) { - selectType(null); - } - } else { - // Nothing valid was selected. - selectType(null); - } - } - } - - validatePage(); - } - - /** - * Callback called when one of the type radio button is changed. - * - * @param typeWidget The type radio button that changed. - */ - private void onRadioTypeUpdated(Button typeWidget) { - // Do nothing if this is an internal modification or if the widget has been - // de-selected. - if (mInternalTypeUpdate || !typeWidget.getSelection()) { - return; - } - - // Find type info that has just been enabled. - TypeInfo type = null; - for (TypeInfo ti : sTypes) { - if (ti.getWidget() == typeWidget) { - type = ti; - break; - } - } - - if (type == null) { - return; - } - - // update the combo - - updateRootCombo(type); - - // update the folder path - - String wsFolderPath = mWsFolderPathTextField.getText(); - String newPath = null; - - mConfigSelector.getConfiguration(mTempConfig); - ResourceQualifier qual = mTempConfig.getInvalidQualifier(); - if (qual == null) { - // The configuration is valid. Reformat the folder path using the canonical - // value from the configuration. - - newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); - } else { - // The configuration is invalid. We still update the path but this time - // do it manually on the string. - if (wsFolderPath.startsWith(sResFolderAbs)) { - wsFolderPath.replaceFirst( - "^(" + sResFolderAbs +")[^-]*(.*)", //$NON-NLS-1$ //$NON-NLS-2$ - "\\1" + type.getResFolderName() + "\\2"); //$NON-NLS-1$ //$NON-NLS-2$ - } else { - newPath = sResFolderAbs + mTempConfig.getFolderName(type.getResFolderType()); - } - } - - if (newPath != null && !newPath.equals(wsFolderPath)) { - mInternalWsFolderPathUpdate = true; - mWsFolderPathTextField.setText(newPath); - mInternalWsFolderPathUpdate = false; - } - - validatePage(); - } - - /** - * Helper method that fills the values of the "root element" combo box based - * on the currently selected type radio button. Also disables the combo is there's - * only one choice. Always select the first root element for the given type. - * - * @param type The currently selected {@link TypeInfo}. Cannot be null. - */ - private void updateRootCombo(TypeInfo type) { - // reset all the values in the combo - mRootElementCombo.removeAll(); - - if (type != null) { - // get the list of roots. The list can be empty but not null. - ArrayList roots = type.getRoots(); - - // enable the combo if there's more than one choice - mRootElementCombo.setEnabled(roots != null && roots.size() > 1); - - for (String root : roots) { - mRootElementCombo.add(root); - } - - int index = 0; // default is to select the first one - String defaultRoot = type.getDefaultRoot(); - if (defaultRoot != null) { - index = roots.indexOf(defaultRoot); - } - mRootElementCombo.select(index < 0 ? 0 : index); - } - } - - /** - * Callback called when the configuration has changed in the {@link ConfigurationSelector}. - */ - private class onConfigSelectorUpdated implements Runnable { - public void run() { - if (mInternalConfigSelectorUpdate) { - return; - } - - TypeInfo type = getSelectedType(); - - if (type != null) { - mConfigSelector.getConfiguration(mTempConfig); - StringBuffer sb = new StringBuffer(sResFolderAbs); - sb.append(mTempConfig.getFolderName(type.getResFolderType())); - - mInternalWsFolderPathUpdate = true; - mWsFolderPathTextField.setText(sb.toString()); - mInternalWsFolderPathUpdate = false; - - validatePage(); - } - } - } - - /** - * Helper method to select on of the type radio buttons. - * - * @param type The TypeInfo matching the radio button to selected or null to deselect them all. - */ - private void selectType(TypeInfo type) { - if (type == null || !type.getWidget().getSelection()) { - mInternalTypeUpdate = true; - for (TypeInfo type2 : sTypes) { - type2.getWidget().setSelection(type2 == type); - } - updateRootCombo(type); - mInternalTypeUpdate = false; - } - } - - /** - * Validates the fields, displays errors and warnings. - * Enables the finish button if there are no errors. - */ - private void validatePage() { - String error = null; - String warning = null; - - // -- validate project - if (getProject() == null) { - error = "Please select an Android project."; - } - - // -- validate filename - if (error == null) { - String fileName = getFileName(); - if (fileName == null || fileName.length() == 0) { - error = "A destination file name is required."; - } else if (fileName != null && !fileName.endsWith(AndroidConstants.DOT_XML)) { - error = String.format("The filename must end with %1$s.", AndroidConstants.DOT_XML); - } - } - - // -- validate type - if (error == null) { - TypeInfo type = getSelectedType(); - - if (type == null) { - error = "One of the types must be selected (e.g. layout, values, etc.)"; - } - } - - // -- validate folder configuration - if (error == null) { - ConfigurationState state = mConfigSelector.getState(); - if (state == ConfigurationState.INVALID_CONFIG) { - ResourceQualifier qual = mConfigSelector.getInvalidQualifier(); - if (qual != null) { - error = String.format("The qualifier '%1$s' is invalid in the folder configuration.", - qual.getName()); - } - } else if (state == ConfigurationState.REGION_WITHOUT_LANGUAGE) { - error = "The Region qualifier requires the Language qualifier."; - } - } - - // -- validate generated path - if (error == null) { - String wsFolderPath = getWsFolderPath(); - if (!wsFolderPath.startsWith(sResFolderAbs)) { - error = String.format("Target folder must start with %1$s.", sResFolderAbs); - } - } - - // -- validate destination file doesn't exist - if (error == null) { - IFile file = getDestinationFile(); - if (file != null && file.exists()) { - warning = "The destination file already exists"; - } - } - - // -- update UI & enable finish if there's no error - setPageComplete(error == null); - if (error != null) { - setMessage(error, WizardPage.ERROR); - } else if (warning != null) { - setMessage(warning, WizardPage.WARNING); - } else { - setErrorMessage(null); - setMessage(null); - } - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java deleted file mode 100644 index 8318e4b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/NewXmlFileWizard.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * 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.editors.wizards; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.IconFactory; -import com.android.ide.eclipse.editors.wizards.NewXmlFileCreationPage.TypeInfo; - -import org.eclipse.core.resources.IContainer; -import org.eclipse.core.resources.IFile; -import org.eclipse.core.resources.IFolder; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.ui.INewWizard; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPage; -import org.eclipse.ui.IWorkbenchWindow; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.ide.IDE; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; - -/** - * The "New Android XML File Wizard" provides the ability to create skeleton XML - * resources files for Android projects. - *

                  - * The wizard has one page, {@link NewXmlFileCreationPage}, used to select the project, - * the resource folder, resource type and file name. It then creates the XML file. - */ -public class NewXmlFileWizard extends Wizard implements INewWizard { - - private static final String PROJECT_LOGO_LARGE = "android_large"; //$NON-NLS-1$ - - protected static final String MAIN_PAGE_NAME = "newAndroidXmlFilePage"; //$NON-NLS-1$ - - private NewXmlFileCreationPage mMainPage; - - public void init(IWorkbench workbench, IStructuredSelection selection) { - setHelpAvailable(false); // TODO have help - setWindowTitle("New Android XML File"); - setImageDescriptor(); - - mMainPage = createMainPage(); - mMainPage.setTitle("New Android XML File"); - mMainPage.setDescription("Creates a new Android XML file."); - mMainPage.setInitialSelection(selection); - } - - /** - * Creates the wizard page. - *

                  - * Please do NOT override this method. - *

                  - * This is protected so that it can be overridden by unit tests. - * However the contract of this class is private and NO ATTEMPT will be made - * to maintain compatibility between different versions of the plugin. - */ - protected NewXmlFileCreationPage createMainPage() { - return new NewXmlFileCreationPage(MAIN_PAGE_NAME); - } - - // -- Methods inherited from org.eclipse.jface.wizard.Wizard -- - // - // The Wizard class implements most defaults and boilerplate code needed by - // IWizard - - /** - * Adds pages to this wizard. - */ - @Override - public void addPages() { - addPage(mMainPage); - } - - /** - * Performs any actions appropriate in response to the user having pressed - * the Finish button, or refuse if finishing now is not permitted: here, it - * actually creates the workspace project and then switch to the Java - * perspective. - * - * @return True - */ - @Override - public boolean performFinish() { - IFile file = createXmlFile(); - if (file == null) { - return false; - } else { - // Open the file in an editor - IWorkbenchWindow win = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); - if (win != null) { - IWorkbenchPage page = win.getActivePage(); - if (page != null) { - try { - IDE.openEditor(page, file); - } catch (PartInitException e) { - EditorsPlugin.log(e, "Failed to create %1$s: missing type", //$NON-NLS-1$ - file.getFullPath().toString()); - } - } - } - return true; - } - } - - // -- Custom Methods -- - - private IFile createXmlFile() { - IFile file = mMainPage.getDestinationFile(); - String name = file.getFullPath().toString(); - boolean need_delete = false; - - if (file.exists()) { - if (!EditorsPlugin.displayPrompt("New Android XML File", - String.format("Do you want to overwrite the file %1$s ?", name))) { - // abort if user selects cancel. - return null; - } - need_delete = true; - } else { - createWsParentDirectory(file.getParent()); - } - - TypeInfo type = mMainPage.getSelectedType(); - if (type == null) { - // this is not expected to happen - EditorsPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing type", name); //$NON-NLS-1$ - return null; - } - String xmlns = type.getXmlns(); - String root = mMainPage.getRootElement(); - if (root == null) { - // this is not expected to happen - EditorsPlugin.log(IStatus.ERROR, "Failed to create %1$s: missing root element", //$NON-NLS-1$ - file.toString()); - return null; - } - - StringBuilder sb = new StringBuilder("\n"); //$NON-NLS-1$ - - sb.append('<').append(root); - if (xmlns != null) { - sb.append('\n').append(" xmlns:android=\"").append(xmlns).append("\""); //$NON-NLS-1$ //$NON-NLS-2$ - } - - String attrs = type.getDefaultAttrs(); - if (attrs != null) { - sb.append("\n "); //$NON-NLS-1$ - sb.append(attrs.replace("\n", "\n ")); //$NON-NLS-1$ //$NON-NLS-2$ - } - - sb.append(">\n"); //$NON-NLS-1$ - sb.append("\n"); //$NON-NLS-1$ //$NON-NLS-2$ - - String result = sb.toString(); - String error = null; - try { - byte[] buf = result.getBytes("UTF8"); - InputStream stream = new ByteArrayInputStream(buf); - if (need_delete) { - file.delete(IFile.KEEP_HISTORY | IFile.FORCE, null /*monitor*/); - } - file.create(stream, true /*force*/, null /*progres*/); - return file; - } catch (UnsupportedEncodingException e) { - error = e.getMessage(); - } catch (CoreException e) { - error = e.getMessage(); - } - - error = String.format("Failed to generate %1$s: %2$s", name, error); - EditorsPlugin.displayError("New Android XML File", error); - return null; - } - - private boolean createWsParentDirectory(IContainer wsPath) { - if (wsPath.getType() == IContainer.FOLDER) { - if (wsPath == null || wsPath.exists()) { - return true; - } - - IFolder folder = (IFolder) wsPath; - try { - if (createWsParentDirectory(wsPath.getParent())) { - folder.create(true /* force */, true /* local */, null /* monitor */); - return true; - } - } catch (CoreException e) { - e.printStackTrace(); - } - } - - return false; - } - - /** - * Returns an image descriptor for the wizard logo. - */ - private void setImageDescriptor() { - ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); - setDefaultPageImageDescriptor(desc); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java deleted file mode 100644 index 446cc14..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ReferenceChooserDialog.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 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.editors.wizards; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.jface.dialogs.DialogSettings; -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.jface.dialogs.IDialogSettings; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.TreePath; -import org.eclipse.jface.viewers.TreeSelection; -import org.eclipse.jface.viewers.TreeViewer; -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.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.ui.dialogs.FilteredTree; -import org.eclipse.ui.dialogs.PatternFilter; -import org.eclipse.ui.dialogs.SelectionStatusDialog; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A dialog to let the user choose a reference to a resource. - * - */ -public class ReferenceChooserDialog extends SelectionStatusDialog { - - private static Pattern sResourcePattern = Pattern.compile("@(.*)/(.+)"); //$NON-NLS-1$ - private static Pattern sInlineIdResourcePattern = Pattern.compile("@\\+id/(.+)"); //$NON-NLS-1$ - - private static IDialogSettings sDialogSettings = new DialogSettings(""); - - private IResourceRepository mResources; - private String mCurrentResource; - - private FilteredTree mFilteredTree; - - /** - * @param parent - */ - public ReferenceChooserDialog(IResourceRepository resources, Shell parent) { - super(parent); - - int shellStyle = getShellStyle(); - setShellStyle(shellStyle | SWT.MAX | SWT.RESIZE); - - setTitle("Reference Dialog"); - setMessage(String.format("Choose a resource")); - mResources = resources; - - setDialogBoundsSettings(sDialogSettings, getDialogBoundsStrategy()); - } - - public void setCurrentResource(String resource) { - mCurrentResource = resource; - } - - public String getCurrentResource() { - return mCurrentResource; - } - - - /* (non-Javadoc) - * @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult() - */ - @Override - protected void computeResult() { - // get the selection - TreePath treeSelection = getSelection(); - if (treeSelection != null) { - if (treeSelection.getSegmentCount() == 2) { - // get the resource type and the resource item - ResourceType resourceType = (ResourceType)treeSelection.getFirstSegment(); - ResourceItem resourceItem = (ResourceItem)treeSelection.getLastSegment(); - - mCurrentResource = resourceType.getXmlString(resourceItem, false /* system */); - } - } - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite top = (Composite)super.createDialogArea(parent); - - // create the standard message area - createMessageArea(top); - - // create the filtered tree - createFilteredTree(top); - - // setup the initial selection - setupInitialSelection(); - - return top; - } - - private void createFilteredTree(Composite parent) { - mFilteredTree = new FilteredTree(parent, SWT.BORDER | SWT.SINGLE | SWT.FULL_SELECTION, - new PatternFilter()); - - GridData data = new GridData(); - data.widthHint = convertWidthInCharsToPixels(60); - data.heightHint = convertHeightInCharsToPixels(18); - data.grabExcessVerticalSpace = true; - data.grabExcessHorizontalSpace = true; - data.horizontalAlignment = GridData.FILL; - data.verticalAlignment = GridData.FILL; - mFilteredTree.setLayoutData(data); - mFilteredTree.setFont(parent.getFont()); - - TreeViewer treeViewer = mFilteredTree.getViewer(); - Tree tree = treeViewer.getTree(); - - tree.addSelectionListener(new SelectionListener() { - public void widgetDefaultSelected(SelectionEvent e) { - handleDoubleClick(); - } - - public void widgetSelected(SelectionEvent e) { - handleSelection(); - } - }); - - treeViewer.setLabelProvider(new ResourceLabelProvider()); - treeViewer.setContentProvider(new ResourceContentProvider(false /* fullLevels */)); - treeViewer.setInput(mResources); - } - - protected void handleSelection() { - validateCurrentSelection(); - } - - protected void handleDoubleClick() { - if (validateCurrentSelection()) { - buttonPressed(IDialogConstants.OK_ID); - } - } - - /** - * Returns the selected item in the tree as a {@link TreePath} object. - * @return the TreePath object or null if there was no selection. - */ - private TreePath getSelection() { - ISelection selection = mFilteredTree.getViewer().getSelection(); - if (selection instanceof TreeSelection) { - TreeSelection treeSelection = (TreeSelection)selection; - TreePath[] treePaths = treeSelection.getPaths(); - - // the selection mode is SWT.SINGLE, so we just get the first one. - if (treePaths.length > 0) { - return treePaths[0]; - } - } - - return null; - } - - private boolean validateCurrentSelection() { - TreePath treeSelection = getSelection(); - - IStatus status; - if (treeSelection != null) { - if (treeSelection.getSegmentCount() == 2) { - status = new Status(IStatus.OK, AndroidConstants.EDITORS_PLUGIN_ID, - IStatus.OK, "", //$NON-NLS-1$ - null); - } else { - status = new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID, - IStatus.ERROR, "You must select a Resource Item", - null); - } - } else { - status = new Status(IStatus.ERROR, AndroidConstants.EDITORS_PLUGIN_ID, - IStatus.ERROR, "", //$NON-NLS-1$ - null); - } - - updateStatus(status); - - return status.isOK(); - } - - /** - * Sets up the initial selection. - *

                  - * This parses {@link #mCurrentResource} to find out the resource type and the resource name. - */ - private void setupInitialSelection() { - // checks the inline id pattern first as it's more restrictive than the other one. - Matcher m = sInlineIdResourcePattern.matcher(mCurrentResource); - if (m.matches()) { - // get the matching name - String resourceName = m.group(1); - - // setup initial selection - setupInitialSelection(ResourceType.ID, resourceName); - } else { - // attempts the inline id pattern - m = sResourcePattern.matcher(mCurrentResource); - if (m.matches()) { - // get the resource type. - ResourceType resourceType = ResourceType.getEnum(m.group(1)); - if (resourceType != null) { - // get the matching name - String resourceName = m.group(2); - - // setup initial selection - setupInitialSelection(resourceType, resourceName); - } - } - } - } - - /** - * Sets up the initial selection based on a {@link ResourceType} and a resource name. - * @param resourceType the resource type. - * @param resourceName the resource name. - */ - private void setupInitialSelection(ResourceType resourceType, String resourceName) { - // get all the resources of this type - ResourceItem[] resourceItems = mResources.getResources(resourceType); - - for (ResourceItem resourceItem : resourceItems) { - if (resourceName.equals(resourceItem.getName())) { - // name of the resource match, we select it, - TreePath treePath = new TreePath(new Object[] { resourceType, resourceItem }); - mFilteredTree.getViewer().setSelection(new TreeSelection(treePath)); - - // and we're done. - return; - } - } - - // if we get here, the resource type is valid, but the resource is missing. - // we select and expand the resource type element. - TreePath treePath = new TreePath(new Object[] { resourceType }); - mFilteredTree.getViewer().setSelection(new TreeSelection(treePath)); - mFilteredTree.getViewer().setExpandedState(resourceType, true /* expanded */); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java deleted file mode 100644 index 60a627b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceChooser.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.wizards; - -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; - -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.dialogs.AbstractElementListSelectionDialog; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * A dialog to let the user select a resource based on a resource type. - */ -public class ResourceChooser extends AbstractElementListSelectionDialog { - - private Pattern mProjectResourcePattern; - - private ResourceType mResourceType; - - private IResourceRepository mProjectResources; - - // TODO: enable when we can display the system resources. - // private Pattern mSystemResourcePattern; - // private IResourceRepository mSystemResources; - // private Button mProjectButton; - // private Button mSystemButton; - - private String mCurrentResource; - - /** - * Creates a Resource Chooser dialog. - * @param type The type of the resource to choose - * @param project The repository for the project - * @param system The System resource repository - * @param parent the parent shell - */ - public ResourceChooser(ResourceType type, IResourceRepository project, - IResourceRepository system, Shell parent) { - super(parent, new ResourceLabelProvider()); - - mResourceType = type; - mProjectResources = project; - // TODO: enable when we can display the system resources. - // mSystemResources = system; - - mProjectResourcePattern = Pattern.compile( - "@" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ - // TODO: enable when we can display the system resources. - // mSystemResourcePattern = Pattern.compile( - // "@android:" + mResourceType.getName() + "/(.+)"); //$NON-NLS-1$ //$NON-NLS-2$ - - setTitle("Resource Chooser"); - setMessage(String.format("Choose a %1$s resource", - mResourceType.getDisplayName().toLowerCase())); - } - - public void setCurrentResource(String resource) { - mCurrentResource = resource; - } - - public String getCurrentResource() { - return mCurrentResource; - } - - @Override - protected void computeResult() { - Object[] elements = getSelectedElements(); - if (elements.length == 1 && elements[0] instanceof ResourceItem) { - ResourceItem item = (ResourceItem)elements[0]; - - mCurrentResource = mResourceType.getXmlString(item, - // TODO: enable when we can display the system resources. - false /*mSystemButton.getSelection()*/); - } - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite top = (Composite)super.createDialogArea(parent); - - createMessageArea(top); - - // TODO: enable when we can display the system resources. - // createButtons(top); - - createFilterText(top); - createFilteredList(top); - - setupResourceListAndCurrent(); - - return top; - } - - /** - * Creates the radio button to switch between project and system resources. - * @param top the parent composite - */ - /* TODO: enable when we can display the system resources. - private void createButtons(Composite top) { - mProjectButton = new Button(top, SWT.RADIO); - mProjectButton.setText("Project Resources"); - mProjectButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - setListElements(mProjectResources.getResources(mResourceType)); - } - }); - mSystemButton = new Button(top, SWT.RADIO); - mSystemButton.setText("System Resources"); - mSystemButton.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - setListElements(mSystemResources.getResources(mResourceType)); - } - }); - } - */ - - /** - * Setups the current list based on the current resource. - */ - private void setupResourceListAndCurrent() { - if (setupInitialSelection(mProjectResourcePattern, mProjectResources) == false) { - // if we couldn't understand the current value, we default to the project resources - ResourceItem[] items = mProjectResources.getResources(mResourceType); - setListElements(items); - } - /* - * TODO: enable when we can display the system resources. - if (setupInitialSelection(mProjectResourcePattern, mProjectResources) == false) { - if (setupInitialSelection(mSystemResourcePattern, mSystemResources) == false) { - // if we couldn't understand the current value, we default to the project resources - IResourceItem[] items = mProjectResources.getResources(mResourceType); - setListElements(items); - mProjectButton.setSelection(true); - } else { - mSystemButton.setSelection(true); - } - } else { - mProjectButton.setSelection(true); - }*/ - } - - /** - * Attempts to setup the list of element from a repository if the current resource - * matches the provided pattern. - * @param pattern the pattern to test the current value - * @param repository the repository to use if the pattern matches. - * @return true if success. - */ - private boolean setupInitialSelection(Pattern pattern, IResourceRepository repository) { - Matcher m = pattern.matcher(mCurrentResource); - if (m.matches()) { - // we have a project resource, let's setup the list - ResourceItem[] items = repository.getResources(mResourceType); - setListElements(items); - - // and let's look for the item we found - String name = m.group(1); - - for (ResourceItem item : items) { - if (name.equals(item.getName())) { - setSelection(new Object[] { item }); - break; - } - } - return true; - } - return false; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java deleted file mode 100644 index 7c6a539..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceContentProvider.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.wizards; - -import com.android.ide.eclipse.common.resources.IResourceRepository; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.manager.ConfigurableResourceItem; -import com.android.ide.eclipse.editors.resources.manager.ResourceFile; - -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Content provider for the Resource Explorer TreeView. - * Each level of the tree is represented by a different class. - *

                    - *
                  • {@link ResourceType}. This represents the list of existing Resource Type present - * in the resources. This can be matched to the subclasses inside the class R - *
                  • - *
                      - *
                    • {@link ResourceItem}. This represents one resource (which can existing in various alternate - * versions). This is similar to the resource Ids defined as R.sometype.id. - *
                    • - *
                        - *
                      • {@link ResourceFile}. (optional) This represents a particular version of the - * {@link ResourceItem}. It is displayed as a list of resource qualifier. - *
                      • - *
                      - *
                    - *
                  - * - * @see ResourceLabelProvider - */ -public class ResourceContentProvider implements ITreeContentProvider { - - /** - * The current ProjectResources being displayed. - */ - private IResourceRepository mResources; - - private boolean mFullLevels; - - /** - * Constructs a new content providers for resource display. - * @param fullLevels if true the content provider will suppport all 3 levels. If - * false, only two levels are provided. - */ - public ResourceContentProvider(boolean fullLevels) { - mFullLevels = fullLevels; - } - - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof ResourceType) { - return mResources.getResources((ResourceType)parentElement); - } else if (mFullLevels && parentElement instanceof ConfigurableResourceItem) { - return ((ConfigurableResourceItem)parentElement).getSourceFileArray(); - } - return null; - } - - public Object getParent(Object element) { - // pass - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof ResourceType) { - return mResources.hasResources((ResourceType)element); - } else if (mFullLevels && element instanceof ConfigurableResourceItem) { - return ((ConfigurableResourceItem)element).hasAlternates(); - } - return false; - } - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof IResourceRepository) { - if ((IResourceRepository)inputElement == mResources) { - // get the top level resources. - return mResources.getAvailableResourceTypes(); - } - } - - return new Object[0]; - } - - public void dispose() { - // pass - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput instanceof IResourceRepository) { - mResources = (IResourceRepository)newInput; - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java deleted file mode 100644 index 024d084..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/wizards/ResourceLabelProvider.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2007 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.editors.wizards; - -import com.android.ide.eclipse.common.resources.IIdResourceItem; -import com.android.ide.eclipse.common.resources.ResourceItem; -import com.android.ide.eclipse.common.resources.ResourceType; -import com.android.ide.eclipse.editors.resources.manager.ConfigurableResourceItem; -import com.android.ide.eclipse.editors.resources.manager.IdResourceItem; -import com.android.ide.eclipse.editors.resources.manager.ResourceFile; - -import org.eclipse.jface.viewers.ILabelProvider; -import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.ITableLabelProvider; -import org.eclipse.swt.graphics.Image; -import org.eclipse.ui.ISharedImages; -import org.eclipse.ui.PlatformUI; - -/** - * Label provider for the Resource Explorer TreeView. - * Each level of the tree is represented by a different class. - *
                    - *
                  • {@link ResourceType}. This represents the list of existing Resource Type present - * in the resources. This can be matched to the subclasses inside the class R - *
                  • - *
                      - *
                    • {@link ResourceItem}. This represents one resource. The actual type can be - * {@link ConfigurableResourceItem} (which can exist in various alternate versions), - * or {@link IdResourceItem}. - * This is similar to the resource Ids defined as R.sometype.id. - *
                    • - *
                        - *
                      • {@link ResourceFile}. This represents a particular version of the {@link ResourceItem}. - * It is displayed as a list of resource qualifier. - *
                      • - *
                      - *
                    - *
                  - * - * @see ResourceContentProvider - */ -public class ResourceLabelProvider implements ILabelProvider, ITableLabelProvider { - private Image mWarningImage; - - public ResourceLabelProvider() { - mWarningImage = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( - ISharedImages.IMG_OBJS_WARN_TSK).createImage(); - } - - /** - * @see #getColumnImage(Object, int) - */ - public Image getImage(Object element) { - // pass - return null; - } - - /** - * @see #getColumnText(Object, int) - */ - public String getText(Object element) { - return getColumnText(element, 0); - } - - public void addListener(ILabelProviderListener listener) { - // pass - } - - public void dispose() { - mWarningImage.dispose(); - } - - public boolean isLabelProperty(Object element, String property) { - return false; - } - - public void removeListener(ILabelProviderListener listener) { - // pass - } - - public Image getColumnImage(Object element, int columnIndex) { - if (columnIndex == 1) { - if (element instanceof ConfigurableResourceItem) { - ConfigurableResourceItem item = (ConfigurableResourceItem)element; - if (item.hasDefault() == false) { - return mWarningImage; - } - } - } - return null; - } - - public String getColumnText(Object element, int columnIndex) { - switch (columnIndex) { - case 0: - if (element instanceof ResourceType) { - return ((ResourceType)element).getDisplayName(); - } else if (element instanceof ResourceItem) { - return ((ResourceItem)element).getName(); - } else if (element instanceof ResourceFile) { - return ((ResourceFile)element).getFolder().getConfiguration().toDisplayString(); - } - break; - case 1: - if (element instanceof ConfigurableResourceItem) { - ConfigurableResourceItem item = (ConfigurableResourceItem)element; - int count = item.getAlternateCount(); - if (count > 0) { - if (item.hasDefault()) { - count++; - } - return String.format("%1$d version(s)", count); - } - } else if (element instanceof IIdResourceItem) { - IIdResourceItem idResource = (IIdResourceItem)element; - if (idResource.isDeclaredInline()) { - return "Declared inline"; - } - } - return null; - } - return null; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java deleted file mode 100644 index bb010e3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlContentAssist.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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.editors.xml; - -import com.android.ide.eclipse.editors.AndroidContentAssist; -import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; - -/** - * Content Assist Processor for /res/xml XML files - */ -class XmlContentAssist extends AndroidContentAssist { - - /** - * Constructor for LayoutContentAssist - */ - public XmlContentAssist() { - super(XmlDescriptors.getInstance().getDescriptor().getChildren()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlEditor.java deleted file mode 100644 index 40d655a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlEditor.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * 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.editors.xml; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.editors.AndroidEditor; -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.FirstElementParser; -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.uimodel.UiDocumentNode; -import com.android.ide.eclipse.editors.xml.descriptors.XmlDescriptors; - -import org.eclipse.core.resources.IFile; -import org.eclipse.ui.IEditorInput; -import org.eclipse.ui.IEditorPart; -import org.eclipse.ui.PartInitException; -import org.eclipse.ui.part.FileEditorInput; -import org.w3c.dom.Document; - -/** - * Multi-page form editor for /res/xml XML files. - */ -public class XmlEditor extends AndroidEditor { - - public static final String ID = "com.android.ide.eclipse.editors.xml.XmlEditor"; //$NON-NLS-1$ - - /** Root node of the UI element hierarchy */ - private UiDocumentNode mUiRootNode; - /** Listener to update the root node if the resource framework changes */ - private Runnable mResourceRefreshListener; - - /** - * Creates the form editor for resources XML files. - */ - public XmlEditor() { - super(); - initUiRootNode(); - } - - /** - * Returns the root node of the UI element hierarchy, which here - * is the document node. - */ - @Override - public UiDocumentNode getUiRootNode() { - return mUiRootNode; - } - - // ---- Static ---- - - /** - * Indicates if this is a file that this {@link XmlEditor} can handle. - *

                  - * The {@link XmlEditor} can handle XML files that have a or - * root XML element with the adequate xmlns:android attribute. - * - * @return True if the {@link XmlEditor} can handle that file. - */ - public static boolean canHandleFile(IFile file) { - - FirstElementParser.Result result = FirstElementParser.parse( - file.getLocation().toOSString(), - AndroidConstants.NS_RESOURCES); - - if (result != null) { - String name = result.getElement(); - if (name != null && result.getXmlnsPrefix() != null) { - DocumentDescriptor desc = XmlDescriptors.getInstance().getDescriptor(); - for (ElementDescriptor elem : desc.getChildren()) { - if (elem.getXmlName().equals(name)) { - // This is an element that this document can handle - return true; - } - } - } - } - - return false; - } - - // ---- Base Class Overrides ---- - - @Override - public void dispose() { - if (mResourceRefreshListener != null) { - EditorsPlugin.getDefault().removeResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener = null; - } - super.dispose(); - } - - /** - * Returns whether the "save as" operation is supported by this editor. - *

                  - * Save-As is a valid operation for the ManifestEditor since it acts on a - * single source file. - * - * @see IEditorPart - */ - @Override - public boolean isSaveAsAllowed() { - return true; - } - - /** - * Create the various form pages. - */ - @Override - protected void createFormPages() { - try { - addPage(new XmlTreePage(this)); - } catch (PartInitException e) { - EditorsPlugin.log(e, "Error creating nested page"); //$NON-NLS-1$ - } - - } - - /* (non-java doc) - * Change the tab/title name to include the project name. - */ - @Override - protected void setInput(IEditorInput input) { - super.setInput(input); - if (input instanceof FileEditorInput) { - FileEditorInput fileInput = (FileEditorInput) input; - IFile file = fileInput.getFile(); - setPartName(String.format("%1$s", file.getName())); - } - } - - /** - * Processes the new XML Model, which XML root node is given. - * - * @param xml_doc The XML document, if available, or null if none exists. - */ - @Override - protected void xmlModelChanged(Document xml_doc) { - mUiRootNode.loadFromXmlNode(xml_doc); - - super.xmlModelChanged(xml_doc); - } - - - // ---- Local Methods ---- - - /** - * Creates the initial UI Root Node, including the known mandatory elements. - */ - private void initUiRootNode() { - // The root UI node is always created, even if there's no corresponding XML node. - if (mUiRootNode == null) { - DocumentDescriptor desc = XmlDescriptors.getInstance().getDescriptor(); - mUiRootNode = (UiDocumentNode) desc.createUiNode(); - mUiRootNode.setEditor(this); - - // Add a listener to refresh the root node if the resource framework changes - // by forcing it to parse its own XML - mResourceRefreshListener = new Runnable() { - public void run() { - commitPages(false /* onSave */); - - mUiRootNode.reloadFromXmlNode(mUiRootNode.getXmlNode()); - } - }; - EditorsPlugin.getDefault().addResourceChangedListener(mResourceRefreshListener); - mResourceRefreshListener.run(); - } - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java deleted file mode 100644 index d25c812..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlSourceViewerConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.editors.xml; - - -import com.android.ide.eclipse.editors.AndroidSourceViewerConfig; - -/** - * Source Viewer Configuration that calls in XmlContentAssist. - */ -public class XmlSourceViewerConfig extends AndroidSourceViewerConfig { - - public XmlSourceViewerConfig() { - super(new XmlContentAssist()); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java deleted file mode 100644 index 5bb0f72..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/XmlTreePage.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.editors.xml; - -import com.android.ide.eclipse.editors.EditorsPlugin; -import com.android.ide.eclipse.editors.ui.tree.UiTreeBlock; -import com.android.ide.eclipse.editors.uimodel.UiElementNode; - -import org.eclipse.ui.forms.IManagedForm; -import org.eclipse.ui.forms.editor.FormPage; -import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Page for the xml form editor. - */ -public final class XmlTreePage extends FormPage { - /** Page id used for switching tabs programmatically */ - public final static String PAGE_ID = "xml_tree_page"; //$NON-NLS-1$ - - /** Container editor */ - XmlEditor mEditor; - - public XmlTreePage(XmlEditor editor) { - super(editor, PAGE_ID, "Structure"); // tab's label, keep it short - mEditor = editor; - } - - /** - * Creates the content in the form hosted in this page. - * - * @param managedForm the form hosted in this page. - */ - @Override - protected void createFormContent(IManagedForm managedForm) { - super.createFormContent(managedForm); - ScrolledForm form = managedForm.getForm(); - form.setText("Android Xml"); - form.setImage(EditorsPlugin.getAndroidLogo()); - - UiElementNode rootNode = mEditor.getUiRootNode(); - UiTreeBlock block = new UiTreeBlock(mEditor, rootNode, - true /* autoCreateRoot */, - null /* no element filters */, - "Xml Elements", - "List of all xml elements in this XML file."); - block.createContent(managedForm); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java b/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java deleted file mode 100644 index 14cb9d6..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.editors/src/com/android/ide/eclipse/editors/xml/descriptors/XmlDescriptors.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * 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.editors.xml.descriptors; - -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo; -import com.android.ide.eclipse.common.resources.ViewClassInfo; -import com.android.ide.eclipse.common.resources.DeclareStyleableInfo.AttributeInfo; -import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.DescriptorsUtils; -import com.android.ide.eclipse.editors.descriptors.DocumentDescriptor; -import com.android.ide.eclipse.editors.descriptors.ElementDescriptor; -import com.android.ide.eclipse.editors.descriptors.XmlnsAttributeDescriptor; -import com.android.ide.eclipse.editors.descriptors.SeparatorAttributeDescriptor; -import com.android.ide.eclipse.editors.layout.descriptors.ViewElementDescriptor; - -import java.util.ArrayList; -import java.util.Map; - - -/** - * Description of the /res/xml structure. - * Currently supports the and root nodes. - */ -public class XmlDescriptors { - - /** Singleton instance */ - private static XmlDescriptors sThis; - - /** The root document descriptor for both searchable and preferences. */ - private DocumentDescriptor mDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ - - /** The root document descriptor for searchable. */ - private DocumentDescriptor mSearchDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ - - /** The root document descriptor for preferences. */ - private DocumentDescriptor mPrefDescriptor = new DocumentDescriptor("xml_doc", null /* children */); //$NON-NLS-1$ - - /** Returns a singleton instance of the {@link XmlDescriptors}. */ - public static synchronized XmlDescriptors getInstance() { - if (sThis == null) { - sThis = new XmlDescriptors(); - } - return sThis; - } - - /** @return the root descriptor for both searchable and preferences. */ - public DocumentDescriptor getDescriptor() { - return mDescriptor; - } - - /** @return the root descriptor for searchable. */ - public DocumentDescriptor getSearchableDescriptor() { - return mSearchDescriptor; - } - - /** @return the root descriptor for preferences. */ - public DocumentDescriptor getPreferencesDescriptor() { - return mPrefDescriptor; - } - - /** - * Updates the document descriptor. - *

                  - * It first computes the new children of the descriptor and then updates them - * all at once. - * - * @param searchableStyleMap The map style=>attributes for from the attrs.xml file - * @param prefs The list of non-group preference descriptions - * @param prefGroups The list of preference group descriptions - */ - public synchronized void updateDescriptors( - Map searchableStyleMap, - ViewClassInfo[] prefs, ViewClassInfo[] prefGroups) { - - XmlnsAttributeDescriptor xmlns = new XmlnsAttributeDescriptor( - "android", //$NON-NLS-1$ - AndroidConstants.NS_RESOURCES); - - ElementDescriptor searchable = createSearchable(searchableStyleMap, xmlns); - ElementDescriptor preferences = createPreference(prefs, prefGroups, xmlns); - ArrayList list = new ArrayList(); - if (searchable != null) { - list.add(searchable); - mSearchDescriptor.setChildren(new ElementDescriptor[]{ searchable }); - } - if (preferences != null) { - list.add(preferences); - mPrefDescriptor.setChildren(new ElementDescriptor[]{ preferences }); - } - - if (list.size() > 0) { - mDescriptor.setChildren(list.toArray(new ElementDescriptor[list.size()])); - } - } - - //------------------------- - // Creation of - //------------------------- - - /** - * Returns the new ElementDescriptor for - */ - private ElementDescriptor createSearchable( - Map searchableStyleMap, - XmlnsAttributeDescriptor xmlns) { - - ElementDescriptor action_key = createElement(searchableStyleMap, - "SearchableActionKey", //$NON-NLS-1$ styleName - "actionkey", //$NON-NLS-1$ xmlName - "Action Key", // uiName - null, // sdk url - null, // extraAttribute - null, // childrenElements - false /* mandatory */ ); - - ElementDescriptor searchable = createElement(searchableStyleMap, - "Searchable", //$NON-NLS-1$ styleName - "searchable", //$NON-NLS-1$ xmlName - "Searchable", // uiName - null, // sdk url - xmlns, // extraAttribute - new ElementDescriptor[] { action_key }, // childrenElements - false /* mandatory */ ); - return searchable; - } - - /** - * Returns a new ElementDescriptor constructed from the information given here - * and the javadoc & attributes extracted from the style map if any. - */ - private ElementDescriptor createElement( - Map styleMap, String styleName, - String xmlName, String uiName, String sdkUrl, - AttributeDescriptor extraAttribute, - ElementDescriptor[] childrenElements, boolean mandatory) { - - ElementDescriptor element = new ElementDescriptor(xmlName, uiName, null, sdkUrl, - null, childrenElements, mandatory); - - return updateElement(element, styleMap, styleName, extraAttribute); - } - - /** - * Updates an ElementDescriptor with the javadoc & attributes extracted from the style - * map if any. - */ - private ElementDescriptor updateElement(ElementDescriptor element, - Map styleMap, - String styleName, - AttributeDescriptor extraAttribute) { - ArrayList descs = new ArrayList(); - - DeclareStyleableInfo style = styleMap != null ? styleMap.get(styleName) : null; - if (style != null) { - DescriptorsUtils.appendAttributes(descs, - null, // elementName - AndroidConstants.NS_RESOURCES, - style.getAttributes(), - null, // requiredAttributes - null); // overrides - element.setTooltip(style.getJavaDoc()); - } - - if (extraAttribute != null) { - descs.add(extraAttribute); - } - - element.setAttributes(descs.toArray(new AttributeDescriptor[descs.size()])); - return element; - } - - //-------------------------- - // Creation of - //-------------------------- - - /** - * Returns the new ElementDescriptor for - */ - private ElementDescriptor createPreference(ViewClassInfo[] prefs, - ViewClassInfo[] prefGroups, XmlnsAttributeDescriptor xmlns) { - - ArrayList newPrefs = new ArrayList(); - if (prefs != null) { - for (ViewClassInfo info : prefs) { - ElementDescriptor desc = convertPref(info); - newPrefs.add(desc); - } - } - - ElementDescriptor topPreferences = null; - - ArrayList newGroups = new ArrayList(); - if (prefGroups != null) { - for (ViewClassInfo info : prefGroups) { - ElementDescriptor desc = convertPref(info); - newGroups.add(desc); - - if (info.getCanonicalClassName() == AndroidConstants.CLASS_PREFERENCES) { - topPreferences = desc; - } - } - } - - ArrayList everything = new ArrayList(); - everything.addAll(newGroups); - everything.addAll(newPrefs); - ElementDescriptor[] newArray = everything.toArray(new ElementDescriptor[everything.size()]); - - // Link all groups to everything else here.. recursively - for (ElementDescriptor layoutDesc : newGroups) { - layoutDesc.setChildren(newArray); - } - - // The "top" element to be returned corresponds to the class "Preferences". - // Its descriptor has already been created. However the root one also needs - // the hidden xmlns:android definition.. - if (topPreferences != null) { - AttributeDescriptor[] attrs = topPreferences.getAttributes(); - AttributeDescriptor[] newAttrs = new AttributeDescriptor[attrs.length + 1]; - System.arraycopy(attrs, 0, newAttrs, 0, attrs.length); - newAttrs[attrs.length] = xmlns; - return new ElementDescriptor( - topPreferences.getXmlName(), - topPreferences.getUiName(), - topPreferences.getTooltip(), - topPreferences.getSdkUrl(), - newAttrs, - topPreferences.getChildren(), - false /* mandatory */); - } else { - return null; - } - } - - /** - * Creates an element descriptor from a given {@link ViewClassInfo}. - */ - private ElementDescriptor convertPref(ViewClassInfo info) { - String xml_name = info.getShortClassName(); - String tooltip = info.getJavaDoc(); - - // Process all Preference attributes - ArrayList attributes = new ArrayList(); - DescriptorsUtils.appendAttributes(attributes, - null, // elementName - AndroidConstants.NS_RESOURCES, - info.getAttributes(), - null, // requiredAttributes - null); // overrides - - for (ViewClassInfo link = info.getSuperClass(); - link != null; - link = link.getSuperClass()) { - AttributeInfo[] attrList = link.getAttributes(); - if (attrList.length > 0) { - attributes.add(new SeparatorAttributeDescriptor( - String.format("Attributes from %1$s", link.getShortClassName()))); - DescriptorsUtils.appendAttributes(attributes, - null, // elementName - AndroidConstants.NS_RESOURCES, - attrList, - null, // requiredAttributes - null); // overrides - } - } - - return new ViewElementDescriptor(xml_name, - xml_name, // ui_name - info.getCanonicalClassName(), - tooltip, - null, // sdk_url - attributes.toArray(new AttributeDescriptor[attributes.size()]), - null, - null, // children - false /* mandatory */); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/.classpath b/eclipse/plugins/com.android.ide.eclipse.platform/.classpath deleted file mode 100644 index 751c8f2..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/.project b/eclipse/plugins/com.android.ide.eclipse.platform/.project deleted file mode 100644 index 145d97c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - platform - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.platform/META-INF/MANIFEST.MF deleted file mode 100644 index 178275a..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/META-INF/MANIFEST.MF +++ /dev/null @@ -1,15 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-Name: Android Platform Development Toolkit -Bundle-SymbolicName: com.android.ide.eclipse.platform;singleton:=true -Bundle-Version: 0.8.1.qualifier -Bundle-ClassPath: . -Bundle-Activator: com.android.ide.eclipse.platform.AndroidPlatformPlugin -Bundle-Vendor: The Android Open Source Project -Require-Bundle: org.eclipse.ui, - org.eclipse.core.runtime, - com.android.ide.eclipse.ddms, - com.android.ide.eclipse.common, - org.eclipse.core.resources, - org.eclipse.jdt.core -Eclipse-LazyStart: true diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/MODULE_LICENSE_EPL b/eclipse/plugins/com.android.ide.eclipse.platform/MODULE_LICENSE_EPL deleted file mode 100644 index e69de29..0000000 diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/NOTICE b/eclipse/plugins/com.android.ide.eclipse.platform/NOTICE deleted file mode 100644 index 49c101d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/NOTICE +++ /dev/null @@ -1,224 +0,0 @@ -*Eclipse Public License - v 1.0* - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE -PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF -THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -*1. DEFINITIONS* - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and -documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: - -i) changes to the Program, and - -ii) additions to the Program; - -where such changes and/or additions to the Program originate from and -are distributed by that particular Contributor. A Contribution -'originates' from a Contributor if it was added to the Program by such -Contributor itself or anyone acting on such Contributor's behalf. -Contributions do not include additions to the Program which: (i) are -separate modules of software distributed in conjunction with the Program -under their own license agreement, and (ii) are not derivative works of -the Program. - -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents " mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, -including all Contributors. - -*2. GRANT OF RIGHTS* - -a) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free copyright -license to reproduce, prepare derivative works of, publicly display, -publicly perform, distribute and sublicense the Contribution of such -Contributor, if any, and such derivative works, in source code and -object code form. - -b) Subject to the terms of this Agreement, each Contributor hereby -grants Recipient a non-exclusive, worldwide, royalty-free patent license -under Licensed Patents to make, use, sell, offer to sell, import and -otherwise transfer the Contribution of such Contributor, if any, in -source code and object code form. This patent license shall apply to the -combination of the Contribution and the Program if, at the time the -Contribution is added by the Contributor, such addition of the -Contribution causes such combination to be covered by the Licensed -Patents. The patent license shall not apply to any other combinations -which include the Contribution. No hardware per se is licensed hereunder. - -c) Recipient understands that although each Contributor grants the -licenses to its Contributions set forth herein, no assurances are -provided by any Contributor that the Program does not infringe the -patent or other intellectual property rights of any other entity. Each -Contributor disclaims any liability to Recipient for claims brought by -any other entity based on infringement of intellectual property rights -or otherwise. As a condition to exercising the rights and licenses -granted hereunder, each Recipient hereby assumes sole responsibility to -secure any other intellectual property rights needed, if any. For -example, if a third party patent license is required to allow Recipient -to distribute the Program, it is Recipient's responsibility to acquire -that license before distributing the Program. - -d) Each Contributor represents that to its knowledge it has sufficient -copyright rights in its Contribution, if any, to grant the copyright -license set forth in this Agreement. - -*3. REQUIREMENTS* - -A Contributor may choose to distribute the Program in object code form -under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and - -b) its license agreement: - -i) effectively disclaims on behalf of all Contributors all warranties -and conditions, express and implied, including warranties or conditions -of title and non-infringement, and implied warranties or conditions of -merchantability and fitness for a particular purpose; - -ii) effectively excludes on behalf of all Contributors all liability for -damages, including direct, indirect, special, incidental and -consequential damages, such as lost profits; - -iii) states that any provisions which differ from this Agreement are -offered by that Contributor alone and not by any other party; and - -iv) states that source code for the Program is available from such -Contributor, and informs licensees how to obtain it in a reasonable -manner on or through a medium customarily used for software exchange. - -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and - -b) a copy of this Agreement must be included with each copy of the Program. - -Contributors may not remove or alter any copyright notices contained -within the Program. - -Each Contributor must identify itself as the originator of its -Contribution, if any, in a manner that reasonably allows subsequent -Recipients to identify the originator of the Contribution. - -*4. COMMERCIAL DISTRIBUTION* - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, the -Contributor who includes the Program in a commercial product offering -should do so in a manner which does not create potential liability for -other Contributors. Therefore, if a Contributor includes the Program in -a commercial product offering, such Contributor ("Commercial -Contributor") hereby agrees to defend and indemnify every other -Contributor ("Indemnified Contributor") against any losses, damages and -costs (collectively "Losses") arising from claims, lawsuits and other -legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the -Program in a commercial product offering. The obligations in this -section do not apply to any claims or Losses relating to any actual or -alleged intellectual property infringement. In order to qualify, an -Indemnified Contributor must: a) promptly notify the Commercial -Contributor in writing of such claim, and b) allow the Commercial -Contributor to control, and cooperate with the Commercial Contributor -in, the defense and any related settlement negotiations. The Indemnified -Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those -performance claims and warranties, and if a court requires any other -Contributor to pay any damages as a result, the Commercial Contributor -must pay those damages. - -*5. NO WARRANTY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED -ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, -EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES -OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR -A PARTICULAR PURPOSE. Each Recipient is solely responsible for -determining the appropriateness of using and distributing the Program -and assumes all risks associated with its exercise of rights under this -Agreement , including but not limited to the risks and costs of program -errors, compliance with applicable laws, damage to or loss of data, -programs or equipment, and unavailability or interruption of operations. - -*6. DISCLAIMER OF LIABILITY* - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR -ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING -WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR -DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED -HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -*7. GENERAL* - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further action -by the parties hereto, such provision shall be reformed to the minimum -extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including -a cross-claim or counterclaim in a lawsuit) alleging that the Program -itself (excluding combinations of the Program with other software or -hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails -to comply with any of the material terms or conditions of this Agreement -and does not cure such failure in a reasonable period of time after -becoming aware of such noncompliance. If all Recipient's rights under -this Agreement terminate, Recipient agrees to cease use and distribution -of the Program as soon as reasonably practicable. However, Recipient's -obligations under this Agreement and any licenses granted by Recipient -relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and may -only be modified in the following manner. The Agreement Steward reserves -the right to publish new versions (including revisions) of this -Agreement from time to time. No one other than the Agreement Steward has -the right to modify this Agreement. The Eclipse Foundation is the -initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is -published, Contributor may elect to distribute the Program (including -its Contributions) under the new version. Except as expressly stated in -Sections 2(a) and 2(b) above, Recipient receives no rights or licenses -to the intellectual property of any Contributor under this Agreement, -whether expressly, by implication, estoppel or otherwise. All rights in -the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the -intellectual property laws of the United States of America. No party to -this Agreement will bring a legal action under this Agreement more than -one year after the cause of action arose. Each party waives its rights -to a jury trial in any resulting litigation. - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/build.properties b/eclipse/plugins/com.android.ide.eclipse.platform/build.properties deleted file mode 100644 index 6c480f3..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/build.properties +++ /dev/null @@ -1,6 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - plugin.xml,\ - icons/ diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/icons/android_project.png b/eclipse/plugins/com.android.ide.eclipse.platform/icons/android_project.png deleted file mode 100644 index 6171025..0000000 Binary files a/eclipse/plugins/com.android.ide.eclipse.platform/icons/android_project.png and /dev/null differ diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.platform/plugin.xml deleted file mode 100644 index 1c5d067..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/plugin.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - -

                  - - - - - - - - - - - - - - diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/AndroidPlatformPlugin.java b/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/AndroidPlatformPlugin.java deleted file mode 100644 index 5fa8a29..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/AndroidPlatformPlugin.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (C) 2007 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.platform; - -import com.android.ddmuilib.StackTracePanel; -import com.android.ddmuilib.StackTracePanel.ISourceRevealer; -import com.android.ide.eclipse.common.AndroidConstants; -import com.android.ide.eclipse.common.project.BaseProjectHelper; -import com.android.ide.eclipse.ddms.DdmsPlugin; -import com.android.ide.eclipse.platform.project.PlatformNature; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IWorkspace; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.Preferences; -import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; -import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.preference.IPreferenceStore; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.plugin.AbstractUIPlugin; -import org.osgi.framework.BundleContext; - -import java.io.File; - -/** - * The activator class controls the plug-in life cycle - */ -public class AndroidPlatformPlugin extends AbstractUIPlugin { - - // The plug-in ID - public static final String PLUGIN_ID = "com.android.ide.eclipse.apdt"; //$NON-NLS-1$ - - public final static String PREFS_DEVICE_DIRECTORY = PLUGIN_ID + ".deviceDir"; //$NON-NLS-1$ - - // The shared instance - private static AndroidPlatformPlugin sPlugin; - - private IPreferenceStore mStore; - private String mOsDeviceDirectory; - - /** - * The constructor - */ - public AndroidPlatformPlugin() { - sPlugin = this; - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) - */ - @Override - public void start(BundleContext context) throws Exception { - super.start(context); - - // get the eclipse store - mStore = getPreferenceStore(); - - // set the listener for the preference change - Preferences prefs = getPluginPreferences(); - prefs.addPropertyChangeListener(new IPropertyChangeListener() { - public void propertyChange(PropertyChangeEvent event) { - // get the name of the property that changed. - String property = event.getProperty(); - - // if the SDK changed, we update the cached version - if (PREFS_DEVICE_DIRECTORY.equals(property)) { - // get the new one from the preferences - mOsDeviceDirectory = (String)event.getNewValue(); - - // make sure it does not ends with a separator - if (mOsDeviceDirectory.endsWith(File.separator)) { - mOsDeviceDirectory = mOsDeviceDirectory.substring(0, - mOsDeviceDirectory.length() - 1); - } - - // finally restart adb, in case it's a different version - String adbLocation = getOsAdbLocation(); - if (adbLocation != null) { - DdmsPlugin.setAdb(adbLocation, true /* startAdb */); - } - } - } - }); - - - mOsDeviceDirectory = mStore.getString(PREFS_DEVICE_DIRECTORY); - - if (mOsDeviceDirectory.length() == 0) { - // get the current Display - final Display display = sPlugin.getWorkbench().getDisplay(); - - // dialog box only run in ui thread.. - display.asyncExec(new Runnable() { - public void run() { - Shell shell = display.getActiveShell(); - MessageDialog.openError(shell, "Android Preferences", - "Location of the device directory is missing."); - } - }); - } else { - // give the location of adb to ddms - String adbLocation = getOsAdbLocation(); - if (adbLocation != null) { - DdmsPlugin.setAdb(adbLocation, true); - } - } - - // and give it the debug launcher for android projects - DdmsPlugin.setRunningAppDebugLauncher(new DdmsPlugin.IDebugLauncher() { - public boolean debug(String packageName, int port) { - return false; - } - }); - - StackTracePanel.setSourceRevealer(new ISourceRevealer() { - public void reveal(String applicationName, String className, int line) { - IProject project = getDeviceProject(); - if (project != null) { - BaseProjectHelper.revealSource(project, className, line); - } - } - }); - - } - - /* - * (non-Javadoc) - * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) - */ - @Override - public void stop(BundleContext context) throws Exception { - sPlugin = null; - super.stop(context); - } - - /** - * Returns the shared instance - * - * @return the shared instance - */ - public static AndroidPlatformPlugin getDefault() { - return sPlugin; - } - - /** - * Returns the Android project. This is the first project which has the PlatformNature. - * @return the IProject of the Android project, or null if it was - * not found. - */ - public static IProject getDeviceProject() { - // Get the list of projects for the current workspace. - IWorkspace workspace = ResourcesPlugin.getWorkspace(); - IProject[] projects = workspace.getRoot().getProjects(); - - for (IProject project : projects) { - try { - if (project.hasNature(PlatformNature.ID)) { - return project; - } - } catch (CoreException e) { - // Failed to get the nature for this project. Let's just ignore - // it and move on to the next one. - } - } - - return null; - } - - /** - * Returns the OS path of the adb location. - * @return the location of adb or null if it cannot be computed. - */ - private String getOsAdbLocation() { - if (mOsDeviceDirectory == null || mOsDeviceDirectory.length() == 0) { - return null; - } - - if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_LINUX) { - return mOsDeviceDirectory + "/out/host/linux-x86/bin/adb"; //$NON-NLS-1$ - } else if (AndroidConstants.CURRENT_PLATFORM == AndroidConstants.PLATFORM_DARWIN) { - return mOsDeviceDirectory + "/out/host/darwin-x86/bin/adb"; //$NON-NLS-1$ - } - return null; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/preferences/AndroidPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/preferences/AndroidPreferencePage.java deleted file mode 100644 index 8427bad..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/preferences/AndroidPreferencePage.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2007 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.platform.preferences; - -import com.android.ide.eclipse.platform.AndroidPlatformPlugin; - -import org.eclipse.jface.preference.DirectoryFieldEditor; -import org.eclipse.jface.preference.FieldEditorPreferencePage; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.IWorkbenchPreferencePage; - -/** - * This class represents a preference page that is contributed to the - * Preferences dialog. By subclassing FieldEditorPreferencePage, - * we can use the field support built into JFace that allows us to create a page - * that is small and knows how to save, restore and apply itself. - *

                  - * This page is used to modify preferences only. They are stored in the - * preference store that belongs to the main plug-in class. That way, - * preferences can be accessed directly via the preference store. - */ -public class AndroidPreferencePage extends FieldEditorPreferencePage implements - IWorkbenchPreferencePage { - - public AndroidPreferencePage() { - super(GRID); - setPreferenceStore(AndroidPlatformPlugin.getDefault().getPreferenceStore()); - setDescription("Android Preferences"); - } - - /** - * Creates the field editors. Field editors are abstractions of the common - * GUI blocks needed to manipulate various types of preferences. Each field - * editor knows how to save and restore itself. - */ - @Override - public void createFieldEditors() { - addField(new DirectoryFieldEditor(AndroidPlatformPlugin.PREFS_DEVICE_DIRECTORY, - "Location of //device", getFieldEditorParent())); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench) - */ - public void init(IWorkbench workbench) { - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/ConvertToPlatformAction.java b/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/ConvertToPlatformAction.java deleted file mode 100644 index 58d55e5..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/ConvertToPlatformAction.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * 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.platform.project; - -import com.android.ide.eclipse.platform.AndroidPlatformPlugin; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectDescription; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.core.runtime.jobs.Job; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.JavaModelException; -import org.eclipse.jface.action.IAction; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.ui.IObjectActionDelegate; -import org.eclipse.ui.IWorkbenchPart; - -import java.util.Iterator; - -/** - * Converts a project created with the activity creator into an - * Android project. - */ -public class ConvertToPlatformAction implements IObjectActionDelegate { - - private ISelection mSelection; - - /* - * (non-Javadoc) - * - * @see IObjectActionDelegate#setActivePart(IAction, IWorkbenchPart) - */ - public void setActivePart(IAction action, IWorkbenchPart targetPart) { - // pass - } - - /* - * (non-Javadoc) - * - * @see IActionDelegate#run(IAction) - */ - public void run(IAction action) { - if (mSelection instanceof IStructuredSelection) { - for (Iterator it = ((IStructuredSelection)mSelection).iterator(); it.hasNext();) { - Object element = it.next(); - IProject project = null; - if (element instanceof IProject) { - project = (IProject)element; - } else if (element instanceof IAdaptable) { - project = (IProject)((IAdaptable)element).getAdapter(IProject.class); - } - if (project != null) { - convertProject(project); - } - } - } - } - - /* - * (non-Javadoc) - * - * @see IActionDelegate#selectionChanged(IAction, ISelection) - */ - public void selectionChanged(IAction action, ISelection selection) { - this.mSelection = selection; - } - - /** - * Toggles sample nature on a project - * - * @param project to have sample nature added or removed - */ - private void convertProject(final IProject project) { - new Job("Convert Project") { - @Override - protected IStatus run(IProgressMonitor monitor) { - try { - if (monitor != null) { - monitor.beginTask(String.format( - "Convert %1$s to Android", project.getName()), 5); - } - - IProjectDescription description = project.getDescription(); - String[] natures = description.getNatureIds(); - - // check if the project already has the android nature. - for (int i = 0; i < natures.length; ++i) { - if (PlatformNature.ID.equals(natures[i])) { - // we shouldn't be here as the visibility of the item - // is dependent on the project. - return new Status(Status.WARNING, AndroidPlatformPlugin.PLUGIN_ID, - "Project is already an Android Platform Project"); - } - } - - // add the platform nature - String[] newNatures = new String[natures.length + 1]; - System.arraycopy(natures, 0, newNatures, 1, natures.length); - newNatures[0] = PlatformNature.ID; - - // set the new nature list in the project - description.setNatureIds(newNatures); - project.setDescription(description, null); - - IJavaProject javaProject = JavaCore.create(project); - IClasspathEntry[] entries = javaProject.getRawClasspath(); - - int n = entries.length; - IClasspathEntry[] newEntries = new IClasspathEntry[n + 1]; - System.arraycopy(entries, 0, newEntries, 0, n); - newEntries[n] = PlatformClasspathContainerInitializer.getContainerEntry(); - - javaProject.setRawClasspath(newEntries, monitor); - - return Status.OK_STATUS; - } catch (JavaModelException e) { - return e.getJavaModelStatus(); - } catch (CoreException e) { - return e.getStatus(); - } finally { - if (monitor != null) { - monitor.done(); - } - } - } - }.schedule(); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformClasspathContainerInitializer.java b/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformClasspathContainerInitializer.java deleted file mode 100644 index a54713c..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformClasspathContainerInitializer.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.platform.project; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.Path; -import org.eclipse.jdt.core.ClasspathContainerInitializer; -import org.eclipse.jdt.core.IClasspathContainer; -import org.eclipse.jdt.core.IClasspathEntry; -import org.eclipse.jdt.core.IJavaProject; -import org.eclipse.jdt.core.JavaCore; - -/** - * Classpath container initializer responsible for binding {@link PlatformClasspathContainer} to - * {@link IProject}s. Because any projects with this container force Eclipse to load the - * plugin, this is a hack to make sure the android platform plugin is launched as soon as an - * android project is opened. - */ -public class PlatformClasspathContainerInitializer extends ClasspathContainerInitializer { - - /** The container id for the android framework jar file */ - private final static String CONTAINER_ID = "com.android.ide.eclipse.platform.DUMMY_CONTAINER"; //$NON-NLS-1$ - - public PlatformClasspathContainerInitializer() { - // pass - } - - /** - * Binds a classpath container to a {@link IClasspathContainer} for a given project, - * or silently fails if unable to do so. - * @param containerPath the container path that is the container id. - * @param the project to bind - */ - @Override - public void initialize(IPath containerPath, IJavaProject project) throws CoreException { - // pass - } - - /** - * Creates a new {@link IClasspathEntry} of type {@link IClasspathEntry#CPE_CONTAINER} - * linking to the Android Framework. - */ - public static IClasspathEntry getContainerEntry() { - return JavaCore.newContainerEntry(new Path(CONTAINER_ID)); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformNature.java b/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformNature.java deleted file mode 100644 index 884dc58..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.platform/src/com/android/ide/eclipse/platform/project/PlatformNature.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.platform.project; - -import org.eclipse.core.resources.IProject; -import org.eclipse.core.resources.IProjectNature; -import org.eclipse.core.runtime.CoreException; - -/** - * Project nature for the Android Projects. - */ -public class PlatformNature implements IProjectNature { - - public final static String ID = "com.android.ide.eclipse.platform.PlatformNature"; //$NON-NLS-1$ - - /** the project this nature object is associated with */ - private IProject mProject; - - /** - * Configures this nature for its project. This is called by the workspace - * when natures are added to the project using - * IProject.setDescription and should not be called directly - * by clients. The nature extension id is added to the list of natures - * before this method is called, and need not be added here. - * - * Exceptions thrown by this method will be propagated back to the caller of - * IProject.setDescription, but the nature will remain in - * the project description. - * - * In this implementation there is nothing to be done, since there's no builder associated - * with this nature. - * - * @see org.eclipse.core.resources.IProjectNature#configure() - * @throws CoreException if configuration fails. - */ - public void configure() throws CoreException { - // pass - } - - /** - * De-configures this nature for its project. This is called by the - * workspace when natures are removed from the project using - * IProject.setDescription and should not be called directly - * by clients. The nature extension id is removed from the list of natures - * before this method is called, and need not be removed here. - * - * Exceptions thrown by this method will be propagated back to the caller of - * IProject.setDescription, but the nature will still be - * removed from the project description. - * - * In this implementation there is nothing to be done, since there's no builder associated - * with this nature. - * - * @see org.eclipse.core.resources.IProjectNature#deconfigure() - * @throws CoreException if configuration fails. - */ - public void deconfigure() throws CoreException { - // pass - } - - /** - * Returns the project to which this project nature applies. - * - * @return the project handle - * @see org.eclipse.core.resources.IProjectNature#getProject() - */ - public IProject getProject() { - return mProject; - } - - /** - * Sets the project to which this nature applies. Used when instantiating - * this project nature runtime. This is called by - * IProject.create() or - * IProject.setDescription() and should not be called - * directly by clients. - * - * @param project the project to which this nature applies - * @see org.eclipse.core.resources.IProjectNature#setProject(org.eclipse.core.resources.IProject) - */ - public void setProject(IProject project) { - mProject = project; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF index a121266..266008c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF +++ b/eclipse/plugins/com.android.ide.eclipse.tests/META-INF/MANIFEST.MF @@ -2,18 +2,17 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Android Plugin Tests Bundle-SymbolicName: com.android.ide.eclipse.tests -Bundle-Version: 0.8.1.qualifier +Bundle-Version: 0.9.0.qualifier Bundle-Activator: com.android.ide.eclipse.tests.AndroidTestPlugin Require-Bundle: org.eclipse.ui, org.eclipse.core.runtime, org.eclipse.core.resources, com.android.ide.eclipse.adt, org.junit, - com.android.ide.eclipse.common, - com.android.ide.eclipse.editors, org.eclipse.jdt.core, org.eclipse.jdt.launching, - org.eclipse.ui.views + org.eclipse.ui.views, + com.android.ide.eclipse.ddms Eclipse-LazyStart: true Bundle-Vendor: The Android Open Source Project Bundle-ClassPath: kxml2-2.3.0.jar, diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectCreationPage.java deleted file mode 100644 index 3202c67..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectCreationPage.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 20078The 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.project.internal; - -import com.android.ide.eclipse.adt.project.internal.NewProjectCreationPage; - -import java.io.File; - -/** - * Stub class for project creation page Returns canned responses for creating a - * sample project - */ -public class StubSampleProjectCreationPage extends NewProjectCreationPage { - - private String mSampleProjectName; - private String mOsSdkLocation; - - public StubSampleProjectCreationPage(String pageName, - String sampleProjectName, String osSdkLocation) { - super(pageName); - this.mSampleProjectName = sampleProjectName; - this.mOsSdkLocation = osSdkLocation; - } - - @Override - public String getProjectName() { - return mSampleProjectName; - } - - @Override - public String getPackageName() { - return "com.android.samples"; - } - - @Override - public String getActivityName() { - return mSampleProjectName; - } - - @Override - public String getApplicationName() { - return mSampleProjectName; - } - - @Override - public boolean isNewProject() { - return false; - } - - @Override - public String getProjectLocation() { - return mOsSdkLocation + File.separator + "samples" + File.separator + mSampleProjectName; - } - - @Override - public String getSourceFolder() { - return "src"; - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectWizard.java deleted file mode 100644 index 49a853d..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/project/internal/StubSampleProjectWizard.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.project.internal; - -import com.android.ide.eclipse.adt.project.internal.NewProjectCreationPage; -import com.android.ide.eclipse.adt.project.internal.NewProjectWizard; - -import org.eclipse.core.runtime.NullProgressMonitor; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.wizard.IWizardContainer; -import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.swt.widgets.Shell; - -import java.lang.reflect.InvocationTargetException; - -/** - * Stub class for project creation wizard Created so project creation logic can - * be run without UI creation/manipulation Returns canned responses for creating - * a sample project - */ -public class StubSampleProjectWizard extends NewProjectWizard { - - private final String mSampleProjectName; - private final String mOsSdkLocation; - - /** - * Constructor - * - * @param sampleProjectName - * @param osSdkLocation - */ - public StubSampleProjectWizard(String sampleProjectName, String osSdkLocation) { - this.mSampleProjectName = sampleProjectName; - this.mOsSdkLocation = osSdkLocation; - } - - /** - * Override parent to return stub page - */ - @Override - protected NewProjectCreationPage createMainPage() { - return new StubSampleProjectCreationPage(MAIN_PAGE_NAME, - mSampleProjectName, mOsSdkLocation); - } - - /** - * Overrides parent to return dummy wizard container - */ - @Override - public IWizardContainer getContainer() { - return new IWizardContainer() { - - public IWizardPage getCurrentPage() { - return null; - } - - public Shell getShell() { - return null; - } - - public void showPage(IWizardPage page) { - // pass - } - - public void updateButtons() { - // pass - } - - public void updateMessage() { - // pass - } - - public void updateTitleBar() { - // pass - } - - public void updateWindowTitle() { - // pass - } - - /** - * Executes runnable on current thread - */ - public void run(boolean fork, boolean cancelable, - IRunnableWithProgress runnable) - throws InvocationTargetException, InterruptedException { - runnable.run(new NullProgressMonitor()); - } - - }; - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java new file mode 100644 index 0000000..42f8df0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectCreationPage.java @@ -0,0 +1,71 @@ +/* + * 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.wizards.newproject; + +import java.io.File; + +/** + * Stub class for project creation page Returns canned responses for creating a + * sample project + */ +public class StubSampleProjectCreationPage extends NewProjectCreationPage { + + private String mSampleProjectName; + private String mOsSdkLocation; + + public StubSampleProjectCreationPage(String pageName, + String sampleProjectName, String osSdkLocation) { + super(pageName); + this.mSampleProjectName = sampleProjectName; + this.mOsSdkLocation = osSdkLocation; + } + + @Override + public String getProjectName() { + return mSampleProjectName; + } + + @Override + public String getPackageName() { + return "com.android.samples"; + } + + @Override + public String getActivityName() { + return mSampleProjectName; + } + + @Override + public String getApplicationName() { + return mSampleProjectName; + } + + @Override + public boolean isNewProject() { + return false; + } + + @Override + public String getProjectLocation() { + return mOsSdkLocation + File.separator + "samples" + File.separator + mSampleProjectName; + } + + @Override + public String getSourceFolder() { + return "src"; + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java new file mode 100644 index 0000000..40cd636 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/wizards/newproject/StubSampleProjectWizard.java @@ -0,0 +1,102 @@ +/* + * 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.wizards.newproject; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.widgets.Shell; + +import java.lang.reflect.InvocationTargetException; + +/** + * Stub class for project creation wizard Created so project creation logic can + * be run without UI creation/manipulation Returns canned responses for creating + * a sample project + */ +public class StubSampleProjectWizard extends NewProjectWizard { + + private final String mSampleProjectName; + private final String mOsSdkLocation; + + /** + * Constructor + * + * @param sampleProjectName + * @param osSdkLocation + */ + public StubSampleProjectWizard(String sampleProjectName, String osSdkLocation) { + this.mSampleProjectName = sampleProjectName; + this.mOsSdkLocation = osSdkLocation; + } + + /** + * Override parent to return stub page + */ + @Override + protected NewProjectCreationPage createMainPage() { + return new StubSampleProjectCreationPage(MAIN_PAGE_NAME, + mSampleProjectName, mOsSdkLocation); + } + + /** + * Overrides parent to return dummy wizard container + */ + @Override + public IWizardContainer getContainer() { + return new IWizardContainer() { + + public IWizardPage getCurrentPage() { + return null; + } + + public Shell getShell() { + return null; + } + + public void showPage(IWizardPage page) { + // pass + } + + public void updateButtons() { + // pass + } + + public void updateMessage() { + // pass + } + + public void updateTitleBar() { + // pass + } + + public void updateWindowTitle() { + // pass + } + + /** + * Executes runnable on current thread + */ + public void run(boolean fork, boolean cancelable, + IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + runnable.run(new NullProgressMonitor()); + } + + }; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java index 5315db8..98817c6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/tests/functests/sampleProjects/SampleProjectTest.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -16,10 +16,9 @@ package com.android.ide.eclipse.tests.functests.sampleProjects; import com.android.ide.eclipse.adt.project.ProjectHelper; -import com.android.ide.eclipse.adt.project.internal.StubSampleProjectWizard; +import com.android.ide.eclipse.adt.wizards.newproject.StubSampleProjectWizard; import com.android.ide.eclipse.tests.FuncTestCase; - import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/AndroidJarLoaderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/AndroidJarLoaderTest.java deleted file mode 100644 index 9d89d18..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/AndroidJarLoaderTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass; -import com.android.ide.eclipse.tests.AdtTestData; - -import junit.framework.TestCase; - -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; - -/** - * Unit Test for {@link FrameworkClassLoader}. - * - * Uses the classes jar.example.Class1/Class2 stored in tests/data/jar_example.jar. - */ -public class AndroidJarLoaderTest extends TestCase { - - private AndroidJarLoader mFrameworkClassLoader; - - /** Creates an instance of {@link FrameworkClassLoader} on our test data JAR */ - @Override - public void setUp() throws Exception { - String jarfilePath = AdtTestData.getInstance().getTestFilePath("jar_example.jar"); //$NON-NLS-1$ - mFrameworkClassLoader = new AndroidJarLoader(jarfilePath); - } - - @Override - public void tearDown() throws Exception { - } - - /** Preloads classes. They should load just fine. */ - public final void testPreLoadClasses() throws Exception { - mFrameworkClassLoader.preLoadClasses("jar.example.", null); //$NON-NLS-1$ - HashMap> map = getPrivateClassCache(); - assertEquals(0, map.size()); - HashMap data = getPrivateEntryCache(); - assertTrue(data.containsKey("jar.example.Class1")); //$NON-NLS-1$ - assertTrue(data.containsKey("jar.example.Class2")); //$NON-NLS-1$ - assertTrue(data.containsKey("jar.example.Class1$InnerStaticClass1")); //$NON-NLS-1$ - assertTrue(data.containsKey("jar.example.Class1$InnerClass2")); //$NON-NLS-1$ - assertEquals(4, data.size()); - } - - /** Preloads a class not in the JAR. Preloading does nothing in this case. */ - public final void testPreLoadClasses_classNotFound() throws Exception { - mFrameworkClassLoader.preLoadClasses("not.a.package.", null); //$NON-NLS-1$ - HashMap> map = getPrivateClassCache(); - assertEquals(0, map.size()); - HashMap data = getPrivateEntryCache(); - assertEquals(0, data.size()); - } - - /** Finds a class we just preloaded. It should work. */ - public final void testFindClass_classFound() throws Exception { - Class c = _findClass(mFrameworkClassLoader, "jar.example.Class2"); //$NON-NLS-1$ - assertEquals("jar.example.Class2", c.getName()); //$NON-NLS-1$ - HashMap> map = getPrivateClassCache(); - assertTrue(map.containsKey("jar.example.Class2")); //$NON-NLS-1$ - assertEquals(1, map.size()); - } - - /** call the protected method findClass */ - private Class _findClass(AndroidJarLoader jarLoader, String name) throws Exception { - Method findClassMethod = AndroidJarLoader.class.getDeclaredMethod( - "findClass", String.class); //$NON-NLS-1$ - findClassMethod.setAccessible(true); - try { - return (Class)findClassMethod.invoke(jarLoader, name); - } - catch (InvocationTargetException e) { - throw (Exception)e.getCause(); - } - } - - /** Trying to find a class that we fail to preload should throw a CNFE. */ - public final void testFindClass_classNotFound() throws Exception { - try { - // Will throw ClassNotFoundException - _findClass(mFrameworkClassLoader, "not.a.valid.ClassName"); //$NON-NLS-1$ - } catch (ClassNotFoundException e) { - // check the message in the CNFE - assertEquals("not.a.valid.ClassName", e.getMessage()); //$NON-NLS-1$ - return; - } - // Exception not thrown - this is a failure - fail("Expected ClassNotFoundException not thrown"); - } - - public final void testFindClassesDerivingFrom() throws Exception { - HashMap> found = - mFrameworkClassLoader.findClassesDerivingFrom("jar.example.", new String[] { //$NON-NLS-1$ - "jar.example.Class1", //$NON-NLS-1$ - "jar.example.Class2" }); //$NON-NLS-1$ - - assertTrue(found.containsKey("jar.example.Class1")); //$NON-NLS-1$ - assertTrue(found.containsKey("jar.example.Class2")); //$NON-NLS-1$ - assertEquals(2, found.size()); - // Only Class2 derives from Class1.. - // Class1 and Class1$InnerStaticClass1 derive from Object and are thus ignored. - // Class1$InnerClass2 should never be seen either. - assertEquals("jar.example.Class2", //$NON-NLS-1$ - found.get("jar.example.Class1").get(0).getCanonicalName()); //$NON-NLS-1$ - assertEquals(1, found.get("jar.example.Class1").size()); //$NON-NLS-1$ - assertEquals(0, found.get("jar.example.Class2").size()); //$NON-NLS-1$ - } - - // --- Utilities --- - - /** - * Retrieves the private mFrameworkClassLoader.mClassCache field using reflection. - * - * @throws NoSuchFieldException - * @throws SecurityException - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - @SuppressWarnings("unchecked") - private HashMap > getPrivateClassCache() - throws SecurityException, NoSuchFieldException, - IllegalArgumentException, IllegalAccessException { - Field field = AndroidJarLoader.class.getDeclaredField("mClassCache"); //$NON-NLS-1$ - field.setAccessible(true); - return (HashMap>) field.get(mFrameworkClassLoader); - } - - /** - * Retrieves the private mFrameworkClassLoader.mEntryCache field using reflection. - * - * @throws NoSuchFieldException - * @throws SecurityException - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - @SuppressWarnings("unchecked") - private HashMap getPrivateEntryCache() - throws SecurityException, NoSuchFieldException, - IllegalArgumentException, IllegalAccessException { - Field field = AndroidJarLoader.class.getDeclaredField("mEntryCache"); //$NON-NLS-1$ - field.setAccessible(true); - return (HashMap) field.get(mFrameworkClassLoader); - } -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/LayoutParamsParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/LayoutParamsParserTest.java deleted file mode 100644 index 1a2ff9b..0000000 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/resources/LayoutParamsParserTest.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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.resources; - -import com.android.ide.eclipse.adt.resources.AndroidJarLoader.ClassWrapper; -import com.android.ide.eclipse.adt.resources.LayoutParamsParser.IClass; -import com.android.ide.eclipse.common.resources.AttrsXmlParser; -import com.android.ide.eclipse.common.resources.ViewClassInfo; -import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.TreeMap; - -import junit.framework.TestCase; - -/** - * Test the inner private methods of FrameworkResourceParser. - * - * Convention: method names that start with an underscore are actually local wrappers - * that call private methods from {@link FrameworkResourceParser} using reflection. - * This is inspired by the Python coding rule which mandates underscores prefixes for - * "private" methods. - */ -public class LayoutParamsParserTest extends TestCase { - - private static class MockFrameworkClassLoader extends AndroidJarLoader { - MockFrameworkClassLoader() { - super(null /* osFrameworkLocation */); - } - - @Override - public HashMap> findClassesDerivingFrom( - String rootPackage, String[] superClasses) throws ClassFormatError { - return new HashMap>(); - } - } - - private static class MockAttrsXmlPath { - public String getPath() { - ClassLoader cl = this.getClass().getClassLoader(); - URL res = cl.getResource("data/mock_attrs.xml"); //$NON-NLS-1$ - return res.getFile(); - } - } - - private static class MockLayoutParamsParser extends LayoutParamsParser { - public MockLayoutParamsParser() { - super(new MockFrameworkClassLoader(), - new AttrsXmlParser(new MockAttrsXmlPath().getPath()).preload()); - - mTopViewClass = new ClassWrapper(mock_android.view.View.class); - mTopGroupClass = new ClassWrapper(mock_android.view.ViewGroup.class); - mTopLayoutParamsClass = new ClassWrapper(mock_android.view.ViewGroup.LayoutParams.class); - - mViewList = new ArrayList(); - mGroupList = new ArrayList(); - mViewMap = new TreeMap(); - mGroupMap = new TreeMap(); - mLayoutParamsMap = new HashMap(); - } - } - - private MockLayoutParamsParser mParser; - - @Override - public void setUp() throws Exception { - mParser = new MockLayoutParamsParser(); - } - - @Override - public void tearDown() throws Exception { - } - - public final void testFindLayoutParams() throws Exception { - assertEquals(mock_android.view.ViewGroup.LayoutParams.class, - ((ClassWrapper)_findLayoutParams(mock_android.view.ViewGroup.class)).wrappedClass()); - - assertEquals(mock_android.widget.LinearLayout.LayoutParams.class, - ((ClassWrapper)_findLayoutParams(mock_android.widget.LinearLayout.class)).wrappedClass()); - - assertEquals(mock_android.widget.TableLayout.LayoutParams.class, - ((ClassWrapper)_findLayoutParams(mock_android.widget.TableLayout.class)).wrappedClass()); - } - - public final void testGetLayoutParamsInfo() throws Exception { - LayoutParamsInfo info1 = _getLayoutParamsInfo( - mock_android.view.ViewGroup.LayoutParams.class); - assertNotNull(info1); - // ViewGroup.LayoutData has Object for superClass, which we don't map - assertNull(info1.getSuperClass()); - - LayoutParamsInfo info2 = _getLayoutParamsInfo( - mock_android.widget.LinearLayout.LayoutParams.class); - assertNotNull(info2); - // LinearLayout.LayoutData links to ViewGroup.LayoutParams - assertSame(info1, info2.getSuperClass()); - - LayoutParamsInfo info3 = _getLayoutParamsInfo( - mock_android.widget.TableLayout.LayoutParams.class); - assertNotNull(info3); - // TableLayout.LayoutData does not link to ViewGroup.LayoutParams nor - // LinearLayout.LayoutParams - assertNotSame(info1, info3.getSuperClass()); - assertNotSame(info2, info3.getSuperClass()); - // TableLayout.LayoutParams => ViewGroup.MarginLayoutParams => ViewGroup.LayoutParams - assertSame(info1, info3.getSuperClass().getSuperClass()); - } - - public final void testGetLayoutClasses() throws Exception { - // _getLayoutClasses(); - } - - //---- access to private methods - - /** Calls the private constructor of the parser */ - private FrameworkResourceParser _Constructor(String osJarPath) throws Exception { - Constructor constructor = - FrameworkResourceParser.class.getDeclaredConstructor(String.class); - constructor.setAccessible(true); - return constructor.newInstance(osJarPath); - } - - /** calls the private getLayoutClasses() of the parser */ - private void _getLayoutClasses() throws Exception { - Method method = FrameworkResourceParser.class.getDeclaredMethod("getLayoutClasses"); //$NON-NLS-1$ - method.setAccessible(true); - method.invoke(mParser); - } - - /** calls the private addGroup() of the parser */ - private ViewClassInfo _addGroup(Class groupClass) throws Exception { - Method method = LayoutParamsParser.class.getDeclaredMethod("addGroup", //$NON-NLS-1$ - IClass.class); - method.setAccessible(true); - return (ViewClassInfo) method.invoke(mParser, new ClassWrapper(groupClass)); - } - - /** calls the private addLayoutParams() of the parser */ - private LayoutParamsInfo _addLayoutParams(Class groupClass) throws Exception { - Method method = LayoutParamsParser.class.getDeclaredMethod("addLayoutParams", //$NON-NLS-1$ - IClass.class); - method.setAccessible(true); - return (LayoutParamsInfo) method.invoke(mParser, new ClassWrapper(groupClass)); - } - - /** calls the private getLayoutParamsInfo() of the parser */ - private LayoutParamsInfo _getLayoutParamsInfo(Class layoutParamsClass) throws Exception { - Method method = LayoutParamsParser.class.getDeclaredMethod("getLayoutParamsInfo", //$NON-NLS-1$ - IClass.class); - method.setAccessible(true); - return (LayoutParamsInfo) method.invoke(mParser, new ClassWrapper(layoutParamsClass)); - } - - /** calls the private findLayoutParams() of the parser */ - private IClass _findLayoutParams(Class groupClass) throws Exception { - Method method = LayoutParamsParser.class.getDeclaredMethod("findLayoutParams", //$NON-NLS-1$ - IClass.class); - method.setAccessible(true); - return (IClass) method.invoke(mParser, new ClassWrapper(groupClass)); - } - -} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java new file mode 100644 index 0000000..f3d9b79 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/AndroidJarLoaderTest.java @@ -0,0 +1,161 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor; +import com.android.ide.eclipse.tests.AdtTestData; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; + +import junit.framework.TestCase; + +/** + * Unit Test for {@link FrameworkClassLoader}. + * + * Uses the classes jar.example.Class1/Class2 stored in tests/data/jar_example.jar. + */ +public class AndroidJarLoaderTest extends TestCase { + + private AndroidJarLoader mFrameworkClassLoader; + + /** Creates an instance of {@link FrameworkClassLoader} on our test data JAR */ + @Override + public void setUp() throws Exception { + String jarfilePath = AdtTestData.getInstance().getTestFilePath("jar_example.jar"); //$NON-NLS-1$ + mFrameworkClassLoader = new AndroidJarLoader(jarfilePath); + } + + @Override + public void tearDown() throws Exception { + } + + /** Preloads classes. They should load just fine. */ + public final void testPreLoadClasses() throws Exception { + mFrameworkClassLoader.preLoadClasses("jar.example.", null, null); //$NON-NLS-1$ + HashMap> map = getPrivateClassCache(); + assertEquals(0, map.size()); + HashMap data = getPrivateEntryCache(); + assertTrue(data.containsKey("jar.example.Class1")); //$NON-NLS-1$ + assertTrue(data.containsKey("jar.example.Class2")); //$NON-NLS-1$ + assertTrue(data.containsKey("jar.example.Class1$InnerStaticClass1")); //$NON-NLS-1$ + assertTrue(data.containsKey("jar.example.Class1$InnerClass2")); //$NON-NLS-1$ + assertEquals(4, data.size()); + } + + /** Preloads a class not in the JAR. Preloading does nothing in this case. */ + public final void testPreLoadClasses_classNotFound() throws Exception { + mFrameworkClassLoader.preLoadClasses("not.a.package.", null, null); //$NON-NLS-1$ + HashMap> map = getPrivateClassCache(); + assertEquals(0, map.size()); + HashMap data = getPrivateEntryCache(); + assertEquals(0, data.size()); + } + + /** Finds a class we just preloaded. It should work. */ + public final void testFindClass_classFound() throws Exception { + Class c = _findClass(mFrameworkClassLoader, "jar.example.Class2"); //$NON-NLS-1$ + assertEquals("jar.example.Class2", c.getName()); //$NON-NLS-1$ + HashMap> map = getPrivateClassCache(); + assertTrue(map.containsKey("jar.example.Class2")); //$NON-NLS-1$ + assertEquals(1, map.size()); + } + + /** call the protected method findClass */ + private Class _findClass(AndroidJarLoader jarLoader, String name) throws Exception { + Method findClassMethod = AndroidJarLoader.class.getDeclaredMethod( + "findClass", String.class); //$NON-NLS-1$ + findClassMethod.setAccessible(true); + try { + return (Class)findClassMethod.invoke(jarLoader, name); + } + catch (InvocationTargetException e) { + throw (Exception)e.getCause(); + } + } + + /** Trying to find a class that we fail to preload should throw a CNFE. */ + public final void testFindClass_classNotFound() throws Exception { + try { + // Will throw ClassNotFoundException + _findClass(mFrameworkClassLoader, "not.a.valid.ClassName"); //$NON-NLS-1$ + } catch (ClassNotFoundException e) { + // check the message in the CNFE + assertEquals("not.a.valid.ClassName", e.getMessage()); //$NON-NLS-1$ + return; + } + // Exception not thrown - this is a failure + fail("Expected ClassNotFoundException not thrown"); + } + + public final void testFindClassesDerivingFrom() throws Exception { + HashMap> found = + mFrameworkClassLoader.findClassesDerivingFrom("jar.example.", new String[] { //$NON-NLS-1$ + "jar.example.Class1", //$NON-NLS-1$ + "jar.example.Class2" }); //$NON-NLS-1$ + + assertTrue(found.containsKey("jar.example.Class1")); //$NON-NLS-1$ + assertTrue(found.containsKey("jar.example.Class2")); //$NON-NLS-1$ + assertEquals(2, found.size()); + // Only Class2 derives from Class1.. + // Class1 and Class1$InnerStaticClass1 derive from Object and are thus ignored. + // Class1$InnerClass2 should never be seen either. + assertEquals("jar.example.Class2", //$NON-NLS-1$ + found.get("jar.example.Class1").get(0).getCanonicalName()); //$NON-NLS-1$ + assertEquals(1, found.get("jar.example.Class1").size()); //$NON-NLS-1$ + assertEquals(0, found.get("jar.example.Class2").size()); //$NON-NLS-1$ + } + + // --- Utilities --- + + /** + * Retrieves the private mFrameworkClassLoader.mClassCache field using reflection. + * + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @SuppressWarnings("unchecked") + private HashMap > getPrivateClassCache() + throws SecurityException, NoSuchFieldException, + IllegalArgumentException, IllegalAccessException { + Field field = AndroidJarLoader.class.getDeclaredField("mClassCache"); //$NON-NLS-1$ + field.setAccessible(true); + return (HashMap>) field.get(mFrameworkClassLoader); + } + + /** + * Retrieves the private mFrameworkClassLoader.mEntryCache field using reflection. + * + * @throws NoSuchFieldException + * @throws SecurityException + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @SuppressWarnings("unchecked") + private HashMap getPrivateEntryCache() + throws SecurityException, NoSuchFieldException, + IllegalArgumentException, IllegalAccessException { + Field field = AndroidJarLoader.class.getDeclaredField("mEntryCache"); //$NON-NLS-1$ + field.setAccessible(true); + return (HashMap) field.get(mFrameworkClassLoader); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java new file mode 100644 index 0000000..b66fcd6 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/sdk/LayoutParamsParserTest.java @@ -0,0 +1,180 @@ +/* + * 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.sdk; + +import com.android.ide.eclipse.adt.sdk.AndroidJarLoader.ClassWrapper; +import com.android.ide.eclipse.adt.sdk.IAndroidClassLoader.IClassDescriptor; +import com.android.ide.eclipse.common.resources.AttrsXmlParser; +import com.android.ide.eclipse.common.resources.ViewClassInfo; +import com.android.ide.eclipse.common.resources.ViewClassInfo.LayoutParamsInfo; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.TreeMap; + +import junit.framework.TestCase; + +/** + * Test the inner private methods of PlatformDataParser. + * + * Convention: method names that start with an underscore are actually local wrappers + * that call private methods from {@link FrameworkResourceParser} using reflection. + * This is inspired by the Python coding rule which mandates underscores prefixes for + * "private" methods. + */ +public class LayoutParamsParserTest extends TestCase { + + private static class MockFrameworkClassLoader extends AndroidJarLoader { + MockFrameworkClassLoader() { + super(null /* osFrameworkLocation */); + } + + @Override + public HashMap> findClassesDerivingFrom( + String rootPackage, String[] superClasses) throws ClassFormatError { + return new HashMap>(); + } + } + + private static class MockAttrsXmlPath { + public String getPath() { + ClassLoader cl = this.getClass().getClassLoader(); + URL res = cl.getResource("data/mock_attrs.xml"); //$NON-NLS-1$ + return res.getFile(); + } + } + + private static class MockLayoutParamsParser extends LayoutParamsParser { + public MockLayoutParamsParser() { + super(new MockFrameworkClassLoader(), + new AttrsXmlParser(new MockAttrsXmlPath().getPath()).preload()); + + mTopViewClass = new ClassWrapper(mock_android.view.View.class); + mTopGroupClass = new ClassWrapper(mock_android.view.ViewGroup.class); + mTopLayoutParamsClass = new ClassWrapper(mock_android.view.ViewGroup.LayoutParams.class); + + mViewList = new ArrayList(); + mGroupList = new ArrayList(); + mViewMap = new TreeMap(); + mGroupMap = new TreeMap(); + mLayoutParamsMap = new HashMap(); + } + } + + private MockLayoutParamsParser mParser; + + @Override + public void setUp() throws Exception { + mParser = new MockLayoutParamsParser(); + } + + @Override + public void tearDown() throws Exception { + } + + public final void testFindLayoutParams() throws Exception { + assertEquals(mock_android.view.ViewGroup.LayoutParams.class, + ((ClassWrapper)_findLayoutParams(mock_android.view.ViewGroup.class)).wrappedClass()); + + assertEquals(mock_android.widget.LinearLayout.LayoutParams.class, + ((ClassWrapper)_findLayoutParams(mock_android.widget.LinearLayout.class)).wrappedClass()); + + assertEquals(mock_android.widget.TableLayout.LayoutParams.class, + ((ClassWrapper)_findLayoutParams(mock_android.widget.TableLayout.class)).wrappedClass()); + } + + public final void testGetLayoutParamsInfo() throws Exception { + LayoutParamsInfo info1 = _getLayoutParamsInfo( + mock_android.view.ViewGroup.LayoutParams.class); + assertNotNull(info1); + // ViewGroup.LayoutData has Object for superClass, which we don't map + assertNull(info1.getSuperClass()); + + LayoutParamsInfo info2 = _getLayoutParamsInfo( + mock_android.widget.LinearLayout.LayoutParams.class); + assertNotNull(info2); + // LinearLayout.LayoutData links to ViewGroup.LayoutParams + assertSame(info1, info2.getSuperClass()); + + LayoutParamsInfo info3 = _getLayoutParamsInfo( + mock_android.widget.TableLayout.LayoutParams.class); + assertNotNull(info3); + // TableLayout.LayoutData does not link to ViewGroup.LayoutParams nor + // LinearLayout.LayoutParams + assertNotSame(info1, info3.getSuperClass()); + assertNotSame(info2, info3.getSuperClass()); + // TableLayout.LayoutParams => ViewGroup.MarginLayoutParams => ViewGroup.LayoutParams + assertSame(info1, info3.getSuperClass().getSuperClass()); + } + + public final void testGetLayoutClasses() throws Exception { + // _getLayoutClasses(); + } + + //---- access to private methods + + /** Calls the private constructor of the parser */ + private AndroidTargetParser _Constructor(String osJarPath) throws Exception { + Constructor constructor = + AndroidTargetParser.class.getDeclaredConstructor(String.class); + constructor.setAccessible(true); + return constructor.newInstance(osJarPath); + } + + /** calls the private getLayoutClasses() of the parser */ + private void _getLayoutClasses() throws Exception { + Method method = AndroidTargetParser.class.getDeclaredMethod("getLayoutClasses"); //$NON-NLS-1$ + method.setAccessible(true); + method.invoke(mParser); + } + + /** calls the private addGroup() of the parser */ + private ViewClassInfo _addGroup(Class groupClass) throws Exception { + Method method = LayoutParamsParser.class.getDeclaredMethod("addGroup", //$NON-NLS-1$ + IClassDescriptor.class); + method.setAccessible(true); + return (ViewClassInfo) method.invoke(mParser, new ClassWrapper(groupClass)); + } + + /** calls the private addLayoutParams() of the parser */ + private LayoutParamsInfo _addLayoutParams(Class groupClass) throws Exception { + Method method = LayoutParamsParser.class.getDeclaredMethod("addLayoutParams", //$NON-NLS-1$ + IClassDescriptor.class); + method.setAccessible(true); + return (LayoutParamsInfo) method.invoke(mParser, new ClassWrapper(groupClass)); + } + + /** calls the private getLayoutParamsInfo() of the parser */ + private LayoutParamsInfo _getLayoutParamsInfo(Class layoutParamsClass) throws Exception { + Method method = LayoutParamsParser.class.getDeclaredMethod("getLayoutParamsInfo", //$NON-NLS-1$ + IClassDescriptor.class); + method.setAccessible(true); + return (LayoutParamsInfo) method.invoke(mParser, new ClassWrapper(layoutParamsClass)); + } + + /** calls the private findLayoutParams() of the parser */ + private IClassDescriptor _findLayoutParams(Class groupClass) throws Exception { + Method method = LayoutParamsParser.class.getDeclaredMethod("findLayoutParams", //$NON-NLS-1$ + IClassDescriptor.class); + method.setAccessible(true); + return (IClassDescriptor) method.invoke(mParser, new ClassWrapper(groupClass)); + } + +} diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/layout/UiElementPullParserTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/layout/UiElementPullParserTest.java index 521bb62..1427eee 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/layout/UiElementPullParserTest.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/editors/layout/UiElementPullParserTest.java @@ -216,9 +216,6 @@ public class UiElementPullParserTest extends TestCase { } catch (XmlPullParserException e) { e.printStackTrace(); assertTrue(false); - } catch (IOException e) { - e.printStackTrace(); - assertTrue(false); } } -- cgit v1.1