aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java6
-rw-r--r--ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java116
-rw-r--r--eclipse/dictionary.txt7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/.classpath1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.pngbin0 -> 332 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.pngbin0 -> 297 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.pngbin0 -> 422 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.pngbin0 -> 424 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.pngbin0 -> 372 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.pngbin0 -> 404 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.pngbin0 -> 442 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.pngbin0 -> 387 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.pngbin0 -> 430 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.pngbin0 -> 421 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.pngbin0 -> 486 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.pngbin0 -> 345 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml66
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtConstants.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java227
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java54
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java30
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java71
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java81
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java114
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java104
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java56
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java60
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java23
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java125
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java174
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java176
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java48
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java78
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java42
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java29
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java35
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java104
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java182
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java189
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java76
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java74
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java36
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java250
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/AppSkeletonPage.java157
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmActivityToLayoutMethod.java59
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmCamelCaseToUnderscoreMethod.java38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmLayoutToActivityMethod.java51
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmSlashedPackageNameMethod.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmUnderscoreToCamelCaseMethod.java39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java638
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java334
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizardState.java103
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java638
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java242
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java108
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java327
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/StringEvaluator.java101
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java956
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java145
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestPage.java163
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestWizard.java76
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/globals.xml.ftl5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/recipe.xml.ftl56
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/AndroidManifest.xml.ftl15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-hdpi/ic_action_search.pngbin0 -> 3120 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-mdpi/ic_action_search.pngbin0 -> 3030 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-xhdpi/ic_action_search.pngbin0 -> 3199 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_fragment_container.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_pager.xml.ftl22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_simple.xml8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/menu/main.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values-large/dimens.xml5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/dimens.xml5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/strings.xml.ftl13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/DropdownActivity.java.ftl98
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/SimpleActivity.java.ftl19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsActivity.java.ftl94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl151
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template.xml69
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity.pngbin0 -> 4322 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_dropdown.pngbin0 -> 5662 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_pager.pngbin0 -> 5441 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs.pngbin0 -> 4873 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs_pager.pngbin0 -> 5257 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/globals.xml.ftl5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/recipe.xml.ftl13
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/layout/sample.xml.ftl18
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/values/attrs.xml.ftl8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/src/app_package/CustomView.java.ftl181
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/template.xml26
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/globals.xml.ftl8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/recipe.xml.ftl31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/AndroidManifest.xml.ftl16
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-large/refs.xml.ftl3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values/strings.xml.ftl5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl38
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl41
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl97
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl39
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template.xml45
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template_master_detail.pngbin0 -> 7173 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/globals.xml.ftl4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/recipe.xml.ftl15
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/AndroidManifest.xml.ftl14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_action_search.pngbin0 -> 3120 bytes
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.pngbin0 -> 4996 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_action_search.pngbin0 -> 3030 bytes
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.pngbin0 -> 3065 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_action_search.pngbin0 -> 3199 bytes
-rwxr-xr-xeclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.pngbin0 -> 6679 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values-large/dimens.xml5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/dimens.xml5
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/strings.xml.ftl3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/styles.xml3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template.xml60
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template_new_project.pngbin0 -> 12408 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/layout.template2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/templates/res/android-support-v4.jar.binbin0 -> 247894 bytes
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatColorsPage.java65
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java17
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/HyperlinksTest.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt18
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt46
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion69.txt30
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate15.txt7
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java18
-rwxr-xr-xeclipse/scripts/create_all_symlinks.sh1
167 files changed, 8164 insertions, 620 deletions
diff --git a/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java b/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
index 6ad98ad..285d922 100644
--- a/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
+++ b/chimpchat/src/com/android/chimpchat/hierarchyviewer/HierarchyViewer.java
@@ -170,7 +170,11 @@ public class HierarchyViewer {
}
ViewNode.Property textProperty = node.namedProperties.get("text:mText");
if (textProperty == null) {
- throw new RuntimeException("No text property on node");
+ // give it another chance, ICS ViewServer returns mText
+ textProperty = node.namedProperties.get("mText");
+ if (textProperty == null) {
+ throw new RuntimeException("No text property on node");
+ }
}
return textProperty.value;
}
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
index c2942d0..233c7c1 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/logcat/LogCatPanel.java
@@ -41,6 +41,8 @@ import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.ModifyEvent;
@@ -52,6 +54,7 @@ import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
@@ -92,6 +95,15 @@ public final class LogCatPanel extends SelectionDependentPanel
/** Preference key to use for storing font settings. */
public static final String LOGCAT_VIEW_FONT_PREFKEY = "logcat.view.font";
+ // Preference keys for message colors based on severity level
+ private static final String MSG_COLOR_PREFKEY_PREFIX = "logcat.msg.color.";
+ public static final String VERBOSE_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "verbose"; //$NON-NLS-1$
+ public static final String DEBUG_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "debug"; //$NON-NLS-1$
+ public static final String INFO_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "info"; //$NON-NLS-1$
+ public static final String WARN_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "warn"; //$NON-NLS-1$
+ public static final String ERROR_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "error"; //$NON-NLS-1$
+ public static final String ASSERT_COLOR_PREFKEY = MSG_COLOR_PREFKEY_PREFIX + "assert"; //$NON-NLS-1$
+
// Use a monospace font family
private static final String FONT_FAMILY =
DdmConstants.CURRENT_PLATFORM == DdmConstants.PLATFORM_DARWIN ? "Monaco":"Courier New";
@@ -163,6 +175,13 @@ public final class LogCatPanel extends SelectionDependentPanel
private Font mFont;
private int mWrapWidthInChars;
+ private Color mVerboseColor;
+ private Color mDebugColor;
+ private Color mInfoColor;
+ private Color mWarnColor;
+ private Color mErrorColor;
+ private Color mAssertColor;
+
private SashForm mSash;
// messages added since last refresh, synchronized on mLogBuffer
@@ -185,6 +204,20 @@ public final class LogCatPanel extends SelectionDependentPanel
initializePreferenceUpdateListeners();
mFont = getFontFromPrefStore();
+ loadMessageColorPreferences();
+ }
+
+ private void loadMessageColorPreferences() {
+ if (mVerboseColor != null) {
+ disposeMessageColors();
+ }
+
+ mVerboseColor = getColorFromPrefStore(VERBOSE_COLOR_PREFKEY);
+ mDebugColor = getColorFromPrefStore(DEBUG_COLOR_PREFKEY);
+ mInfoColor = getColorFromPrefStore(INFO_COLOR_PREFKEY);
+ mWarnColor = getColorFromPrefStore(WARN_COLOR_PREFKEY);
+ mErrorColor = getColorFromPrefStore(ERROR_COLOR_PREFKEY);
+ mAssertColor = getColorFromPrefStore(ASSERT_COLOR_PREFKEY);
}
private void initializeFilters() {
@@ -209,6 +242,20 @@ public final class LogCatPanel extends SelectionDependentPanel
mPrefStore.setDefault(LogCatMessageList.MAX_MESSAGES_PREFKEY,
LogCatMessageList.MAX_MESSAGES_DEFAULT);
mPrefStore.setDefault(DISPLAY_FILTERS_COLUMN_PREFKEY, true);
+
+ /* Default Colors for different log levels. */
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.VERBOSE_COLOR_PREFKEY,
+ new RGB(0, 0, 0));
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.DEBUG_COLOR_PREFKEY,
+ new RGB(0, 0, 127));
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.INFO_COLOR_PREFKEY,
+ new RGB(0, 127, 0));
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.WARN_COLOR_PREFKEY,
+ new RGB(255, 127, 0));
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.ERROR_COLOR_PREFKEY,
+ new RGB(255, 0, 0));
+ PreferenceConverter.setDefault(mPrefStore, LogCatPanel.ASSERT_COLOR_PREFKEY,
+ new RGB(255, 0, 0));
}
private void initializePreferenceUpdateListeners() {
@@ -230,6 +277,21 @@ public final class LogCatPanel extends SelectionDependentPanel
}
}
});
+ } else if (changedProperty.startsWith(MSG_COLOR_PREFKEY_PREFIX)) {
+ loadMessageColorPreferences();
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ Color c = mVerboseColor;
+ for (TableItem it: mTable.getItems()) {
+ Object data = it.getData();
+ if (data instanceof LogCatMessage) {
+ c = getForegroundColor((LogCatMessage) data);
+ }
+ it.setForeground(c);
+ }
+ }
+ });
} else if (changedProperty.equals(LogCatMessageList.MAX_MESSAGES_PREFKEY)) {
mReceiver.resizeFifo(mPrefStore.getInt(
LogCatMessageList.MAX_MESSAGES_PREFKEY));
@@ -834,6 +896,13 @@ public final class LogCatPanel extends SelectionDependentPanel
addRightClickMenu(mTable);
initDoubleClickListener();
recomputeWrapWidth();
+
+ mTable.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(DisposeEvent arg0) {
+ dispose();
+ }
+ });
}
/** Setup menu to be displayed when right clicking a log message. */
@@ -914,6 +983,11 @@ public final class LogCatPanel extends SelectionDependentPanel
return new Font(Display.getDefault(), fd);
}
+ private Color getColorFromPrefStore(String key) {
+ RGB rgb = PreferenceConverter.getColor(mPrefStore, key);
+ return new Color(Display.getDefault(), rgb);
+ }
+
private void setupDefaults() {
int defaultFilterIndex = 0;
mFiltersTableViewer.getTable().setSelection(defaultFilterIndex);
@@ -1239,29 +1313,24 @@ public final class LogCatPanel extends SelectionDependentPanel
return wrappedMessages;
}
- /* Default Colors for different log levels. */
- private static final Color INFO_MSG_COLOR = new Color(null, 0, 127, 0);
- private static final Color DEBUG_MSG_COLOR = new Color(null, 0, 0, 127);
- private static final Color ERROR_MSG_COLOR = new Color(null, 255, 0, 0);
- private static final Color WARN_MSG_COLOR = new Color(null, 255, 127, 0);
- private static final Color VERBOSE_MSG_COLOR = new Color(null, 0, 0, 0);
-
- private static Color getForegroundColor(LogCatMessage m) {
+ private Color getForegroundColor(LogCatMessage m) {
LogLevel l = m.getLogLevel();
if (l.equals(LogLevel.VERBOSE)) {
- return VERBOSE_MSG_COLOR;
+ return mVerboseColor;
} else if (l.equals(LogLevel.INFO)) {
- return INFO_MSG_COLOR;
+ return mInfoColor;
} else if (l.equals(LogLevel.DEBUG)) {
- return DEBUG_MSG_COLOR;
+ return mDebugColor;
} else if (l.equals(LogLevel.ERROR)) {
- return ERROR_MSG_COLOR;
+ return mErrorColor;
} else if (l.equals(LogLevel.WARN)) {
- return WARN_MSG_COLOR;
+ return mWarnColor;
+ } else if (l.equals(LogLevel.ASSERT)) {
+ return mAssertColor;
}
- return null;
+ return mVerboseColor;
}
private List<ILogCatMessageSelectionListener> mMessageSelectionListeners;
@@ -1344,4 +1413,23 @@ public final class LogCatPanel extends SelectionDependentPanel
public void selectAll() {
mTable.selectAll();
}
+
+ private void dispose() {
+ if (mFont != null && !mFont.isDisposed()) {
+ mFont.dispose();
+ }
+
+ if (mVerboseColor != null && !mVerboseColor.isDisposed()) {
+ disposeMessageColors();
+ }
+ }
+
+ private void disposeMessageColors() {
+ mVerboseColor.dispose();
+ mDebugColor.dispose();
+ mInfoColor.dispose();
+ mWarnColor.dispose();
+ mErrorColor.dispose();
+ mAssertColor.dispose();
+ }
}
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index fe8cc4b..dbb51e5 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -1,6 +1,7 @@
&apos;
aapt
accessor
+accessors
adb
addon
adt
@@ -42,6 +43,7 @@ checkboxes
classloader
classpath
clickable
+clipart
clipboard
clipboards
clueless
@@ -279,16 +281,20 @@ subregion
superclasses
supertype
symlinks
+synced
syncs
synthetically
tad
temp
+templated
textfield
textfields
thematically
themed
thumbnail
+thumbnails
timestamp
+timestamps
tmp
toolbar
tooltip
@@ -317,6 +323,7 @@ url
urls
utf
validator
+validators
varargs
verbosity
viewport
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index 4c132ed..e30e8c9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -17,6 +17,7 @@
<classpathentry exported="true" kind="lib" path="libs/ant-glob.jar"/>
<classpathentry exported="true" kind="lib" path="libs/manifmerger.jar" sourcepath="/ManifestMerger"/>
<classpathentry kind="var" path="ANDROID_SRC/sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs/propertysheet.jar" sourcepath="/ANDROID_SRC/external/eclipse-windowbuilder/propertysheet/src"/>
+ <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/freemarker/freemarker-2.3.19.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/freemarker/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/lombok-ast/src.zip"/>
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 38d2885..dc443db 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
@@ -7,6 +7,7 @@ Bundle-ClassPath: .,
libs/sdkuilib.jar,
libs/ninepatch.jar,
libs/kxml2-2.3.0.jar,
+ libs/freemarker-2.3.19.jar,
libs/layoutlib_api.jar,
libs/ide_common.jar,
libs/rule_api.jar,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png
new file mode 100644
index 0000000..2e63c35
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/error-badge.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png
new file mode 100644
index 0000000..aa6f067
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint1.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png
new file mode 100644
index 0000000..8434b78
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint2.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png
new file mode 100644
index 0000000..43ab63e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint3.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png
new file mode 100644
index 0000000..67fee2d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint4.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png
new file mode 100644
index 0000000..3d0c188
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint5.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png
new file mode 100644
index 0000000..42a9767
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint6.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png
new file mode 100644
index 0000000..743aabc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint7.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png
new file mode 100644
index 0000000..927067a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint8.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png
new file mode 100644
index 0000000..44e66a0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png
new file mode 100644
index 0000000..853ace9
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/lint9p.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png
new file mode 100644
index 0000000..d71d6e3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/icons/warning-badge.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 5c3e9a0..1f402b9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -149,6 +149,21 @@
<category
id="com.android.ide.eclipse.wizards.category"
name="Android" />
+
+ <!-- New template-based project wizard -->
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_adt_project.png"
+ id="ccom.android.ide.eclipse.adt.project.NewProjectWizard"
+ name="Android Application"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="true" />
+
+ <!-- Old custom-coded project wizard -->
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
@@ -156,8 +171,8 @@
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
icon="icons/new_adt_project.png"
- id="com.android.ide.eclipse.adt.project.NewProjectWizard"
- name="Android Project"
+ id="com.android.ide.eclipse.adt.project.NewProjectWizard.Old"
+ name="Empty Android Project"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="true" />
<wizard
@@ -223,6 +238,39 @@
<wizard
canFinishEarly="false"
category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard$NewActivityWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_adt_project.png"
+ id="com.android.ide.eclipse.editors.wizards.NewTemplateWizard.Activity"
+ name="Blank Activity"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="false" />
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard$MasterDetailWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/new_adt_project.png"
+ id="com.android.ide.eclipse.editors.wizards.NewTemplateWizard.MasterDetail"
+ name="Master Detail Flow"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="false" />
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard$NewCustomViewWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/customView.png"
+ id="com.android.ide.eclipse.editors.wizards.NewTemplateWizard.CustomView"
+ name="Custom View"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="false" />
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
class="com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizard"
finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
hasPages="true"
@@ -231,6 +279,20 @@
name="Android Icon Set"
preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
project="false" />
+
+ <!-- During development only: Support for testing templates -->
+ <wizard
+ canFinishEarly="false"
+ category="com.android.ide.eclipse.wizards.category"
+ class="com.android.ide.eclipse.adt.internal.wizards.templates.TemplateTestWizard"
+ finalPerspective="org.eclipse.jdt.ui.JavaPerspective"
+ hasPages="true"
+ icon="icons/androidjunit.png"
+ id="com.android.ide.eclipse.editors.wizards.TemplateTestWizard"
+ name="Template Development Wizard"
+ preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective"
+ project="false" />
+
</extension>
<extension point="org.eclipse.debug.core.launchConfigurationTypes">
<launchConfigurationType
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
index 75326fe..507bed5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/BaseViewRule.java
@@ -16,7 +16,6 @@
package com.android.ide.common.layout;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ATTR_HINT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
@@ -34,7 +33,10 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_MATCH_PARENT;
import static com.android.ide.common.layout.LayoutConstants.VALUE_TRUE;
import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_FRAGMENT;
+import static com.android.util.XmlUtils.ANDROID_URI;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.common.api.AbstractViewRule;
import com.android.ide.common.api.IAttributeInfo;
import com.android.ide.common.api.IAttributeInfo.Format;
@@ -971,7 +973,8 @@ public class BaseViewRule extends AbstractViewRule {
* @param id attribute to be stripped
* @return the id name without the {@code @+id} or {@code @id} prefix
*/
- public static String stripIdPrefix(String id) {
+ @NonNull
+ public static String stripIdPrefix(@Nullable String id) {
if (id == null) {
return ""; //$NON-NLS-1$
} else if (id.startsWith(NEW_ID_PREFIX)) {
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 e5e84d9..cb5faba 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
@@ -125,6 +125,8 @@ public class AdtConstants {
public static final String DOT_BMP = ".bmp"; //$NON-NLS-1$
/** Dot-Extension for SVG files, i.e. ".svg" */
public static final String DOT_SVG = ".svg"; //$NON-NLS-1$
+ /** Dot-Extension for template files */
+ public static final String DOT_FTL = ".ftl"; //$NON-NLS-1$
/** Name of the android sources directory */
public static final String FD_ANDROID_SOURCES = "sources"; //$NON-NLS-1$
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtPlugin.java
index f8f6311..10cd093 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
@@ -47,6 +47,7 @@ import com.android.ide.eclipse.ddms.DdmsPlugin;
import com.android.io.StreamException;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
+import com.android.sdklib.ISdkLog;
import com.android.sdklib.SdkConstants;
import org.eclipse.core.commands.Command;
@@ -123,7 +124,7 @@ import java.util.List;
/**
* The activator class controls the plug-in life cycle
*/
-public class AdtPlugin extends AbstractUIPlugin implements ILogger {
+public class AdtPlugin extends AbstractUIPlugin implements ILogger, ISdkLog {
/**
* Temporary logging code to help track down
* http://code.google.com/p/android/issues/detail?id=15003
@@ -729,15 +730,18 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
InputStream is = readEmbeddedFileAsStream(filepath);
if (is != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ try {
+ String line;
+ StringBuilder total = new StringBuilder(reader.readLine());
+ while ((line = reader.readLine()) != null) {
+ total.append('\n');
+ total.append(line);
+ }
- String line;
- StringBuilder total = new StringBuilder(reader.readLine());
- while ((line = reader.readLine()) != null) {
- total.append('\n');
- total.append(line);
+ return total.toString();
+ } finally {
+ reader.close();
}
-
- return total.toString();
}
} catch (IOException e) {
// we'll just return null
@@ -759,16 +763,19 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
if (is != null) {
// create a buffered reader to facilitate reading.
BufferedInputStream stream = new BufferedInputStream(is);
+ try {
+ // get the size to read.
+ int avail = stream.available();
- // get the size to read.
- int avail = stream.available();
-
- // create the buffer and reads it.
- byte[] buffer = new byte[avail];
- stream.read(buffer);
+ // create the buffer and reads it.
+ byte[] buffer = new byte[avail];
+ stream.read(buffer);
- // and return.
- return buffer;
+ // and return.
+ return buffer;
+ } finally {
+ stream.close();
+ }
}
} catch (IOException e) {
// we'll just return null;.
@@ -1853,6 +1860,8 @@ public class AdtPlugin extends AbstractUIPlugin implements ILogger {
openFile(file, region, true);
}
+ // TODO: Make an openEditor which does the above, and make the above pass false for showEditor
+
/**
* Opens the given file and shows the given (optional) region
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
index 8ba909f..09092e7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/AdtUtils.java
@@ -25,6 +25,9 @@ import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper.IProjectFilter;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
import com.android.util.XmlUtils;
import org.eclipse.core.filesystem.URIUtil;
@@ -36,6 +39,7 @@ import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
@@ -43,6 +47,8 @@ import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
@@ -61,6 +67,7 @@ import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
@@ -167,7 +174,8 @@ public class AdtUtils {
* @return the string as a Java class, or null if a class name could not be
* extracted
*/
- public static String extractClassName(String string) {
+ @Nullable
+ public static String extractClassName(@NonNull String string) {
StringBuilder sb = new StringBuilder(string.length());
int n = string.length();
@@ -264,6 +272,62 @@ public class AdtUtils {
return sb.toString();
}
+ /**
+ * Converts a CamelCase word into an underlined_word
+ *
+ * @param string the CamelCase version of the word
+ * @return the underlined version of the word
+ */
+ public static String camelCaseToUnderlines(String string) {
+ if (string.isEmpty()) {
+ return string;
+ }
+
+ StringBuilder sb = new StringBuilder(2 * string.length());
+ int n = string.length();
+ boolean lastWasUpperCase = Character.isUpperCase(string.charAt(0));
+ for (int i = 0; i < n; i++) {
+ char c = string.charAt(i);
+ boolean isUpperCase = Character.isUpperCase(c);
+ if (isUpperCase && !lastWasUpperCase) {
+ sb.append('_');
+ }
+ lastWasUpperCase = isUpperCase;
+ c = Character.toLowerCase(c);
+ sb.append(c);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Converts an underlined_word into a CamelCase word
+ *
+ * @param string the underlined word to convert
+ * @return the CamelCase version of the word
+ */
+ public static String underlinesToCamelCase(String string) {
+ StringBuilder sb = new StringBuilder(string.length());
+ int n = string.length();
+
+ int i = 0;
+ boolean upcaseNext = true;
+ for (; i < n; i++) {
+ char c = string.charAt(i);
+ if (c == '_') {
+ upcaseNext = true;
+ } else {
+ if (upcaseNext) {
+ c = Character.toUpperCase(c);
+ }
+ upcaseNext = false;
+ sb.append(c);
+ }
+ }
+
+ return sb.toString();
+ }
+
/** For use by {@link #getLineSeparator()} */
private static String sLineSeparator;
@@ -561,7 +625,10 @@ public class AdtUtils {
String name = i == 1 ? base : base + conjunction + Integer.toString(i);
boolean found = false;
for (IProject project : projects) {
- if (project.getName().equals(name)) {
+ // Need to make case insensitive comparison, since otherwise we can hit
+ // org.eclipse.core.internal.resources.ResourceException:
+ // A resource exists with a different case: '/test'.
+ if (project.getName().equalsIgnoreCase(name)) {
found = true;
break;
}
@@ -686,4 +753,160 @@ public class AdtUtils {
});
}
+ /**
+ * Returns the Android version and code name of the given API level
+ *
+ * @param api the api level
+ * @return a suitable version display name
+ */
+ public static String getAndroidName(int api) {
+ // See http://source.android.com/source/build-numbers.html
+ switch (api) {
+ case 1: return "API 1: Android 1.0";
+ case 2: return "API 2: Android 1.1";
+ case 3: return "API 3: Android 1.5 (Cupcake)";
+ case 4: return "API 4: Android 1.6 (Donut)";
+ case 5: return "API 5: Android 2.0 (Eclair)";
+ case 6: return "API 6: Android 2.0.1 (Eclair)";
+ case 7: return "API 7: Android 2.1 (Eclair)";
+ case 8: return "API 8: Android 2.2 (Froyo)";
+ case 9: return "API 9: Android 2.3 (Gingerbread)";
+ case 10: return "API 10: Android 2.3.3 (Gingerbread)";
+ case 11: return "API 11: Android 3.0 (Honeycomb)";
+ case 12: return "API 12: Android 3.1 (Honeycomb)";
+ case 13: return "API 13: Android 3.2 (Honeycomb)";
+ case 14: return "API 14: Android 4.0 (IceCreamSandwich)";
+ case 15: return "API 15: Android 4.0.3 (IceCreamSandwich)";
+ default: {
+ // Consult SDK manager to see if we know any more (later) names,
+ // installed by user
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ for (IAndroidTarget target : sdk.getTargets()) {
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (version.getApiLevel() == api) {
+ return version.getApiString();
+ }
+ }
+ }
+ }
+
+ return "API " + api;
+
+ }
+ }
+ }
+
+ /**
+ * Returns the highest known API level to this version of ADT. The
+ * {@link #getAndroidName(int)} method will return real names up to and
+ * including this number.
+ *
+ * @return the highest known API number
+ */
+ public static int getHighestKnownApiLevel() {
+ return 15;
+ }
+
+ /**
+ * Returns a list of known API names
+ *
+ * @return a list of string API names, starting from 1 and up through the
+ * maximum known versions (with no gaps)
+ */
+ public static String[] getKnownVersions() {
+ int max = 15;
+ Sdk sdk = Sdk.getCurrent();
+ if (sdk != null) {
+ for (IAndroidTarget target : sdk.getTargets()) {
+ if (target.isPlatform()) {
+ AndroidVersion version = target.getVersion();
+ if (!version.isPreview()) {
+ max = Math.max(max, version.getApiLevel());
+ }
+ }
+ }
+ }
+
+ String[] versions = new String[max];
+ for (int api = 1; api <= max; api++) {
+ versions[api-1] = getAndroidName(api);
+ }
+
+ return versions;
+ }
+
+ /**
+ * Returns the Android project(s) that are selected or active, if any. This
+ * considers the selection, the active editor, etc.
+ *
+ * @param selection the current selection
+ * @return a list of projects, possibly empty (but never null)
+ */
+ @NonNull
+ public static List<IProject> getSelectedProjects(@Nullable ISelection selection) {
+ List<IProject> projects = new ArrayList<IProject>();
+
+ if (selection instanceof IStructuredSelection) {
+ IStructuredSelection structuredSelection = (IStructuredSelection) selection;
+ // get the unique selected item.
+ Iterator<?> iterator = structuredSelection.iterator();
+ while (iterator.hasNext()) {
+ Object element = iterator.next();
+
+ // First look up the resource (since some adaptables
+ // provide an IResource but not an IProject, and we can
+ // always go from IResource to IProject)
+ IResource resource = null;
+ if (element instanceof IResource) { // may include IProject
+ resource = (IResource) element;
+ } else if (element instanceof IAdaptable) {
+ IAdaptable adaptable = (IAdaptable)element;
+ Object adapter = adaptable.getAdapter(IResource.class);
+ resource = (IResource) adapter;
+ }
+
+ // get the project object from it.
+ IProject project = null;
+ if (resource != null) {
+ project = resource.getProject();
+ } else if (element instanceof IAdaptable) {
+ project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
+ }
+
+ if (project != null && !projects.contains(project)) {
+ projects.add(project);
+ }
+ }
+ }
+
+ if (projects.isEmpty()) {
+ // Try to look at the active editor instead
+ IFile file = AdtUtils.getActiveFile();
+ if (file != null) {
+ projects.add(file.getProject());
+ }
+ }
+
+ if (projects.isEmpty()) {
+ // If we didn't find a default project based on the selection, check how many
+ // open Android projects we can find in the current workspace. If there's only
+ // one, we'll just select it by default.
+ IJavaProject[] open = AdtUtils.getOpenAndroidProjects();
+ for (IJavaProject project : open) {
+ projects.add(project.getProject());
+ }
+ return projects;
+ } else {
+ // Make sure all the projects are Android projects
+ List<IProject> androidProjects = new ArrayList<IProject>(projects.size());
+ for (IProject project : projects) {
+ if (BaseProjectHelper.isAndroidProject(project)) {
+ androidProjects.add(project);
+ }
+ }
+ return androidProjects;
+ }
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
index 1f45570..4167010 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/assetstudio/ConfigureAssetSetPage.java
@@ -28,6 +28,7 @@ import com.android.assetstudiolib.TabIconGenerator;
import com.android.assetstudiolib.TextRenderUtil;
import com.android.assetstudiolib.Util;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState.SourceType;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SwtUtils;
@@ -505,6 +506,9 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
// Initial image - use the most recently used image, or the default launcher
// icon created in our default projects, if there
+ if (mValues.imagePath != null) {
+ sImagePath = mValues.imagePath.getPath();;
+ }
if (sImagePath == null) {
IProject project = mValues.project;
if (project != null) {
@@ -544,6 +548,11 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
setSourceType(mValues.sourceType);
setShape(mValues.shape);
+ if (mValues.sourceType == SourceType.CLIPART
+ && mValues.clipartName != null) {
+ updateClipartPreview();
+ }
+
// Initial color
Display display = mPreviewArea.getDisplay();
//updateColor(display, new RGB(0xa4, 0xc6, 0x39), true /*background*/);
@@ -758,27 +767,7 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
mValues.clipartName = (String) image.getData();
close();
- for (Control c : mClipartPreviewPanel.getChildren()) {
- c.dispose();
- }
- if (mClipartPreviewPanel.getChildren().length == 0) {
- try {
- BufferedImage icon = GraphicGenerator.getClipartIcon(
- mValues.clipartName);
- if (icon != null) {
- Display display = mClipartForm.getDisplay();
- Image swtImage = SwtUtils.convertToSwt(display, icon,
- true, -1);
- new ImageControl(mClipartPreviewPanel,
- SWT.NONE, swtImage);
- }
- } catch (IOException e1) {
- AdtPlugin.log(e1, null);
- }
- mClipartPreviewPanel.pack();
- mClipartPreviewPanel.layout();
- }
-
+ updateClipartPreview();
updatePreview();
}
}
@@ -866,6 +855,29 @@ public class ConfigureAssetSetPage extends WizardPage implements SelectionListen
requestUpdatePreview(updateQuickly);
}
+ private void updateClipartPreview() {
+ for (Control c : mClipartPreviewPanel.getChildren()) {
+ c.dispose();
+ }
+ if (mClipartPreviewPanel.getChildren().length == 0) {
+ try {
+ BufferedImage icon = GraphicGenerator.getClipartIcon(
+ mValues.clipartName);
+ if (icon != null) {
+ Display display = mClipartForm.getDisplay();
+ Image swtImage = SwtUtils.convertToSwt(display, icon,
+ true, -1);
+ new ImageControl(mClipartPreviewPanel,
+ SWT.NONE, swtImage);
+ }
+ } catch (IOException e1) {
+ AdtPlugin.log(e1, null);
+ }
+ mClipartPreviewPanel.pack();
+ mClipartPreviewPanel.layout();
+ }
+ }
+
private void setShape(GraphicGenerator.Shape shape) {
if (shape == GraphicGenerator.Shape.SQUARE) {
mSquareRadio.setSelection(true);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
index 803478b..396e172 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
@@ -1018,23 +1018,21 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
model.changedModel();
if (AdtPrefs.getPrefs().getFormatGuiXml() && mFormatNode != null) {
- if (!mFormatNode.hasError()) {
- if (mFormatNode == getUiRootNode()) {
- reformatDocument();
- } else {
- Node node = mFormatNode.getXmlNode();
- if (node instanceof IndexedRegion) {
- IndexedRegion region = (IndexedRegion) node;
- int begin = region.getStartOffset();
- int end = region.getEndOffset();
-
- if (!mFormatChildren) {
- // This will format just the attribute list
- end = begin + 1;
- }
-
- reformatRegion(begin, end);
+ if (mFormatNode == getUiRootNode()) {
+ reformatDocument();
+ } else {
+ Node node = mFormatNode.getXmlNode();
+ if (node instanceof IndexedRegion) {
+ IndexedRegion region = (IndexedRegion) node;
+ int begin = region.getStartOffset();
+ int end = region.getEndOffset();
+
+ if (!mFormatChildren) {
+ // This will format just the attribute list
+ end = begin + 1;
}
+
+ reformatRegion(begin, end);
}
}
mFormatNode = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
index 6e035af..c63ed18 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/Hyperlinks.java
@@ -16,14 +16,15 @@
package com.android.ide.eclipse.adt.internal.editors;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ATTR_NAME;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ON_CLICK;
import static com.android.ide.common.layout.LayoutConstants.NEW_ID_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.VIEW;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_ANDROID_THEME_REF;
import static com.android.ide.common.resources.ResourceResolver.PREFIX_RESOURCE_REF;
+import static com.android.ide.common.resources.ResourceResolver.PREFIX_THEME_REF;
import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
import static com.android.ide.eclipse.adt.AdtConstants.FN_RESOURCE_BASE;
@@ -38,6 +39,10 @@ import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_NAME;
import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_PACKAGE;
import static com.android.sdklib.xml.AndroidManifest.NODE_ACTIVITY;
import static com.android.sdklib.xml.AndroidManifest.NODE_SERVICE;
+import static com.android.tools.lint.detector.api.LintConstants.ANDROID_STYLE_RESOURCE_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.NEW_ID_RESOURCE_PREFIX;
+import static com.android.tools.lint.detector.api.LintConstants.STYLE_RESOURCE_PREFIX;
+import static com.android.util.XmlUtils.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -210,14 +215,14 @@ public class Hyperlinks {
}
String value = attribute.getValue();
- if (value.startsWith("@+")) { //$NON-NLS-1$
+ if (value.startsWith(NEW_ID_RESOURCE_PREFIX)) {
// It's a value -declaration-, nowhere else to jump
// (though we could consider jumping to the R-file; would that
// be helpful?)
return false;
}
- Pair<ResourceType,String> resource = ResourceHelper.parseResource(value);
+ Pair<ResourceType,String> resource = parseResource(value);
if (resource != null) {
ResourceType type = resource.getFirst();
if (type != null) {
@@ -1026,23 +1031,28 @@ public class Hyperlinks {
if (type == ResourceType.ID) {
// Ids are recorded in <item> tags instead of <id> tags
targetTag = "item"; //$NON-NLS-1$
- } else if (type == ResourceType.ATTR) {
+ }
+
+ Pair<File, Integer> result = findTag(name, file, parser, document, targetTag);
+ if (result == null && type == ResourceType.ATTR) {
// Attributes seem to be defined in <public> tags
targetTag = "public"; //$NON-NLS-1$
+ result = findTag(name, file, parser, document, targetTag);
}
- Element root = document.getDocumentElement();
- if (root.getTagName().equals(ROOT_ELEMENT)) {
- NodeList children = root.getChildNodes();
- for (int i = 0, n = children.getLength(); i < n; i++) {
- Node child = children.item(i);
- if (child.getNodeType() == Node.ELEMENT_NODE) {
- Element element = (Element) child;
- if (element.getTagName().equals(targetTag)) {
- String elementName = element.getAttribute(NAME_ATTR);
- if (elementName.equals(name)) {
+ return result;
+ }
- return Pair.of(file, parser.getOffset(element));
- }
+ private static Pair<File, Integer> findTag(String name, File file, OffsetTrackingParser parser,
+ Document document, String targetTag) {
+ NodeList children = document.getElementsByTagName(targetTag);
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) child;
+ if (element.getTagName().equals(targetTag)) {
+ String elementName = element.getAttribute(NAME_ATTR);
+ if (elementName.equals(name)) {
+ return Pair.of(file, parser.getOffset(element));
}
}
}
@@ -1067,15 +1077,17 @@ public class Hyperlinks {
}
}
- Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
+ Pair<ResourceType,String> resource = parseResource(url);
if (resource == null) {
- String androidStyle = "@android:style/"; //$NON-NLS-1$
+ String androidStyle = ANDROID_STYLE_RESOURCE_PREFIX;
if (url.startsWith(PREFIX_ANDROID_RESOURCE_REF)) {
url = androidStyle + url.substring(PREFIX_ANDROID_RESOURCE_REF.length());
+ } else if (url.startsWith(PREFIX_ANDROID_THEME_REF)) {
+ url = androidStyle + url.substring(PREFIX_ANDROID_THEME_REF.length());
} else if (url.startsWith(ANDROID_PKG + ':')) {
url = androidStyle + url.substring(ANDROID_PKG.length() + 1);
} else {
- url = "@style/" + url; //$NON-NLS-1$
+ url = STYLE_RESOURCE_PREFIX + url;
}
}
return getResourceLinks(range, url);
@@ -1087,6 +1099,16 @@ public class Hyperlinks {
return getResourceLinks(range, url, project, configuration);
}
+ /** Parse a resource reference or a theme reference and return the individual parts */
+ private static Pair<ResourceType,String> parseResource(String url) {
+ if (url.startsWith(PREFIX_THEME_REF)) {
+ url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
+ return ResourceHelper.parseResource(url);
+ }
+
+ return ResourceHelper.parseResource(url);
+ }
+
/**
* Computes hyperlinks to resource definitions for resource urls (e.g.
* {@code @android:string/ok} or {@code @layout/foo}. May create multiple links.
@@ -1101,14 +1123,15 @@ public class Hyperlinks {
@NonNull IProject project, @Nullable FolderConfiguration configuration) {
List<IHyperlink> links = new ArrayList<IHyperlink>();
- Pair<ResourceType,String> resource = ResourceHelper.parseResource(url);
+ Pair<ResourceType,String> resource = parseResource(url);
if (resource == null || resource.getFirst() == null) {
return null;
}
ResourceType type = resource.getFirst();
String name = resource.getSecond();
- boolean isFramework = url.startsWith("@android"); //$NON-NLS-1$
+ boolean isFramework = url.startsWith(PREFIX_ANDROID_RESOURCE_REF)
+ || url.startsWith(PREFIX_ANDROID_THEME_REF);
if (project == null) {
// Local reference *within* a framework
isFramework = true;
@@ -1217,10 +1240,10 @@ public class Hyperlinks {
return getStyleLinks(context, range, attribute.getValue());
}
if (attribute != null
- && attribute.getValue().startsWith(PREFIX_RESOURCE_REF)) {
+ && (attribute.getValue().startsWith(PREFIX_RESOURCE_REF)
+ || attribute.getValue().startsWith(PREFIX_THEME_REF))) {
// Instantly create links for resources since we can use the existing
// resolved maps for this and offer multiple choices for the user
-
String url = attribute.getValue();
return getResourceLinks(range, url);
}
@@ -1253,7 +1276,7 @@ public class Hyperlinks {
int offset = caretOffset;
while (offset > lineStart) {
char c = document.getChar(offset);
- if (c == '@') {
+ if (c == '@' || c == '?') {
urlStart = offset;
break;
} else if (!isValidResourceUrlChar(c)) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
index 0a12ba4..4017cf1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/IconFactory.java
@@ -20,7 +20,9 @@ package com.android.ide.eclipse.adt.internal.editors;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
import com.android.sdklib.SdkConstants;
+import com.google.common.collect.Maps;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
@@ -36,7 +38,8 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import java.net.URL;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Map;
/**
* Factory to generate icons for Android Editors.
@@ -55,9 +58,11 @@ public class IconFactory {
private static IconFactory sInstance;
- private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
- private HashMap<URL, Image> mUrlMap = new HashMap<URL, Image>();
- private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
+ private Map<String, Image> mIconMap = Maps.newHashMap();
+ private Map<URL, Image> mUrlMap = Maps.newHashMap();
+ private Map<String, ImageDescriptor> mImageDescMap = Maps.newHashMap();
+ private Map<Image, Image> mErrorIcons;
+ private Map<Image, Image> mWarningIcons;
private IconFactory() {
}
@@ -85,6 +90,24 @@ public class IconFactory {
}
}
mUrlMap.clear();
+ if (mErrorIcons != null) {
+ for (Image icon : mErrorIcons.values()) {
+ // The map can contain null values
+ if (icon != null) {
+ icon.dispose();
+ }
+ }
+ mErrorIcons = null;
+ }
+ if (mWarningIcons != null) {
+ for (Image icon : mWarningIcons.values()) {
+ // The map can contain null values
+ if (icon != null) {
+ icon.dispose();
+ }
+ }
+ mWarningIcons = null;
+ }
}
/**
@@ -253,6 +276,56 @@ public class IconFactory {
}
/**
+ * Returns an image with an error icon overlaid on it. The icons are cached,
+ * so the base image should be cached as well, or this method will keep
+ * storing new overlays into its cache.
+ *
+ * @param image the base image
+ * @return the combined image
+ */
+ @NonNull
+ public Image addErrorIcon(@NonNull Image image) {
+ if (mErrorIcons != null) {
+ Image combined = mErrorIcons.get(image);
+ if (combined != null) {
+ return combined;
+ }
+ } else {
+ mErrorIcons = new IdentityHashMap<Image, Image>();
+ }
+
+ Image combined = new ErrorImageComposite(image, false).createImage();
+ mErrorIcons.put(image, combined);
+
+ return combined;
+ }
+
+ /**
+ * Returns an image with a warning icon overlaid on it. The icons are
+ * cached, so the base image should be cached as well, or this method will
+ * keep storing new overlays into its cache.
+ *
+ * @param image the base image
+ * @return the combined image
+ */
+ @NonNull
+ public Image addWarningIcon(@NonNull Image image) {
+ if (mWarningIcons != null) {
+ Image combined = mWarningIcons.get(image);
+ if (combined != null) {
+ return combined;
+ }
+ } else {
+ mWarningIcons = new IdentityHashMap<Image, Image>();
+ }
+
+ Image combined = new ErrorImageComposite(image, true).createImage();
+ mWarningIcons.put(image, combined);
+
+ return combined;
+ }
+
+ /**
* A simple image description that generates a 16x16 image which consists
* of a colored letter inside a black & white circle.
*/
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
index 613a68f..7cfe791 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/descriptors/ElementDescriptor.java
@@ -469,4 +469,17 @@ public class ElementDescriptor implements Comparable<ElementDescriptor> {
public int compareTo(ElementDescriptor o) {
return mUiName.compareToIgnoreCase(o.mUiName);
}
+
+ /**
+ * Ensures that this view descriptor's attribute list is up to date. This is
+ * always the case for all the builtin descriptors, but for example for a
+ * custom view, it could be changing dynamically so caches may have to be
+ * recomputed. This method will return true if nothing changed, and false if
+ * it recomputed its info.
+ *
+ * @return true if the attributes are already up to date and nothing changed
+ */
+ public boolean syncAttributes() {
+ return true;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
index 4bc7641..89dd263 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditorDelegate.java
@@ -39,13 +39,18 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gle2.OutlinePage;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionManager;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
+import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceFolderType;
import com.android.sdklib.IAndroidTarget;
+import com.android.tools.lint.client.api.IssueRegistry;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
@@ -78,8 +83,11 @@ import org.eclipse.wst.sse.ui.StructuredTextEditor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
@@ -138,6 +146,7 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
private final HashMap<String, ElementDescriptor> mUnknownDescriptorMap =
new HashMap<String, ElementDescriptor>();
+ private EclipseLintClient mClient;
/**
* Flag indicating if the replacement file is due to a config change.
@@ -405,27 +414,106 @@ public class LayoutEditorDelegate extends CommonXmlDelegate
return true;
}
+ /**
+ * Returns one of the issues for the given node (there could be more than one)
+ *
+ * @param node the node to look up lint issues for
+ * @return the marker for one of the issues found for the given node
+ */
+ @Nullable
+ public IMarker getIssueForNode(@Nullable UiViewElementNode node) {
+ if (node == null) {
+ return null;
+ }
+
+ if (mClient != null) {
+ return mClient.getIssueForNode(node);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a collection of nodes that have one or more lint warnings
+ * associated with them (retrievable via
+ * {@link #getIssueForNode(UiViewElementNode)})
+ *
+ * @return a collection of nodes, which should <b>not</b> be modified by the
+ * caller
+ */
+ @Nullable
+ public Collection<Node> getLintNodes() {
+ if (mClient != null) {
+ return mClient.getIssueNodes();
+ }
+
+ return null;
+ }
+
@Override
public Job delegateRunLint() {
- Job job = super.delegateRunLint();
+ // We want to customize the {@link EclipseLintClient} created to run this
+ // single file lint, in particular such that we can set the mode which collects
+ // nodes on that lint job, such that we can quickly look up error nodes
+ //Job job = super.delegateRunLint();
+
+ Job job = null;
+ IFile file = getEditor().getInputFile();
+ if (file != null) {
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ List<IFile> resources = Collections.singletonList(file);
+ mClient = new EclipseLintClient(registry,
+ resources, getEditor().getStructuredDocument(), false /*fatal*/);
+
+ mClient.setCollectNodes(true);
+
+ job = EclipseLintRunner.startLint(mClient, resources, file,
+ false /*show*/);
+ }
if (job != null) {
- job.addJobChangeListener(new JobChangeAdapter() {
- @Override
- public void done(IJobChangeEvent event) {
- GraphicalEditorPart graphicalEditor = getGraphicalEditor();
- if (graphicalEditor != null) {
- LayoutActionBar bar = graphicalEditor.getLayoutActionBar();
- if (!bar.isDisposed()) {
- bar.updateErrorIndicator();
- }
- }
- }
- });
+ GraphicalEditorPart graphicalEditor = getGraphicalEditor();
+ if (graphicalEditor != null) {
+ job.addJobChangeListener(new LintJobListener(graphicalEditor));
+ }
}
return job;
}
+ private class LintJobListener extends JobChangeAdapter implements Runnable {
+ private final GraphicalEditorPart mEditor;
+ private final LayoutCanvas mCanvas;
+
+ LintJobListener(GraphicalEditorPart editor) {
+ mEditor = editor;
+ mCanvas = editor.getCanvasControl();
+ }
+
+ @Override
+ public void done(IJobChangeEvent event) {
+ LayoutActionBar bar = mEditor.getLayoutActionBar();
+ if (!bar.isDisposed()) {
+ bar.updateErrorIndicator();
+ }
+
+ // Redraw
+ if (!mCanvas.isDisposed()) {
+ mCanvas.getDisplay().asyncExec(this);
+ }
+ }
+
+ @Override
+ public void run() {
+ if (!mCanvas.isDisposed()) {
+ mCanvas.redraw();
+
+ OutlinePage outlinePage = mCanvas.getOutlinePage();
+ if (outlinePage != null) {
+ outlinePage.refreshIcons();
+ }
+ }
+ }
+ }
/**
* Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
index 1fa3f06..5330752 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ConfigurationComposite.java
@@ -1020,7 +1020,11 @@ public class ConfigurationComposite extends Composite implements SelectionListen
}
private IAndroidTarget getSelectedTarget() {
- return (IAndroidTarget) mTargetCombo.getData();
+ if (!mTargetCombo.isDisposed()) {
+ return (IAndroidTarget) mTargetCombo.getData();
+ }
+
+ return null;
}
void selectTheme(String theme) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
index 194cb3f..7e7d65b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/configuration/ThemeMenuAction.java
@@ -188,7 +188,7 @@ class ThemeMenuAction extends SubmenuAction {
}
}
- String manifestTheme = manifest.getmManifestTheme();
+ String manifestTheme = manifest.getManifestTheme();
if (activityThemes.size() > 0 || manifestTheme != null) {
Set<String> allThemes = new HashSet<String>(activityThemes.values());
if (manifestTheme != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
index a110c65..ab4e1e9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/descriptors/CustomViewDescriptorService.java
@@ -16,11 +16,11 @@
package com.android.ide.eclipse.adt.internal.editors.layout.descriptors;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.sdklib.SdkConstants.CLASS_VIEWGROUP;
import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
import static com.android.util.XmlUtils.ANDROID_NS_NAME_PREFIX;
+import static com.android.util.XmlUtils.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
@@ -44,12 +44,14 @@ import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.Maps;
import com.google.common.collect.ObjectArrays;
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.IClassFile;
@@ -193,7 +195,8 @@ public final class CustomViewDescriptorService {
// we have a valid parent, lets create a new ViewElementDescriptor.
List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
- findCustomDescriptors(project, type, attrList, paramList);
+ Map<ResourceFile, Long> files = findCustomDescriptors(project, type,
+ attrList, paramList);
AttributeDescriptor[] attributes =
getAttributeDescriptor(type, parentDescriptor);
@@ -209,7 +212,8 @@ public final class CustomViewDescriptorService {
ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
attributes,
layoutAttributes,
- parentDescriptor.getChildren());
+ parentDescriptor.getChildren(),
+ project, files);
descriptor.setSuperClass(parentDescriptor);
synchronized (mCustomDescriptorMap) {
@@ -270,7 +274,7 @@ public final class CustomViewDescriptorService {
}
/** Compute/find the styleable resources for the given type, if possible */
- private void findCustomDescriptors(
+ private Map<ResourceFile, Long> findCustomDescriptors(
IProject project,
IType type,
List<AttributeDescriptor> customAttributes,
@@ -286,6 +290,8 @@ public final class CustomViewDescriptorService {
Set<ResourceFile> resourceFiles = findAttrsFiles(library, className);
if (resourceFiles != null && resourceFiles.size() > 0) {
String appUri = getAppResUri(project);
+ Map<ResourceFile, Long> timestamps =
+ Maps.newHashMapWithExpectedSize(resourceFiles.size());
for (ResourceFile file : resourceFiles) {
AttrsXmlParser attrsXmlParser = getParser(file);
String fqcn = type.getFullyQualifiedName();
@@ -300,8 +306,14 @@ public final class CustomViewDescriptorService {
classInfo, "Layout", null /*superClassInfo*/); //$NON-NLS-1$
attrsXmlParser.loadLayoutParamsAttributes(layoutInfo);
appendAttributes(customLayoutAttributes, layoutInfo.getAttributes(), appUri);
+
+ timestamps.put(file, file.getFile().getModificationStamp());
}
+
+ return timestamps;
}
+
+ return null;
}
/**
@@ -309,7 +321,7 @@ public final class CustomViewDescriptorService {
* attributes for the given class name
*/
@Nullable
- private Set<ResourceFile> findAttrsFiles(IProject library, String className) {
+ private static Set<ResourceFile> findAttrsFiles(IProject library, String className) {
Set<ResourceFile> resourceFiles = null;
ResourceManager manager = ResourceManager.getInstance();
ProjectResources resources = manager.getProjectResources(library);
@@ -338,7 +350,7 @@ public final class CustomViewDescriptorService {
* attrs.xml file in the same project.
*/
@Nullable
- private IProject getProjectDeclaringType(IType type) {
+ private static IProject getProjectDeclaringType(IType type) {
IClassFile classFile = type.getClassFile();
if (classFile != null) {
IPath path = classFile.getPath();
@@ -358,7 +370,7 @@ public final class CustomViewDescriptorService {
}
/** Returns the name space to use for application attributes */
- private String getAppResUri(IProject project) {
+ private static String getAppResUri(IProject project) {
String appResource;
ProjectState projectState = Sdk.getProjectState(project);
if (projectState != null && projectState.isLibrary()) {
@@ -459,7 +471,7 @@ public final class CustomViewDescriptorService {
ViewElementDescriptor descriptor = new CustomViewDescriptor(name, fqcn,
getAttributeDescriptor(type, parentDescriptor),
getLayoutAttributeDescriptors(type, parentDescriptor),
- children);
+ children, project, null);
descriptor.setSuperClass(parentDescriptor);
// add it to the map
@@ -504,10 +516,14 @@ public final class CustomViewDescriptorService {
return parentDescriptor.getLayoutAttributes();
}
- private static class CustomViewDescriptor extends ViewElementDescriptor {
+ private class CustomViewDescriptor extends ViewElementDescriptor {
+ private Map<ResourceFile, Long> mTimeStamps;
+ private IProject mProject;
+
public CustomViewDescriptor(String name, String fqcn, AttributeDescriptor[] attributes,
AttributeDescriptor[] layoutAttributes,
- ElementDescriptor[] children) {
+ ElementDescriptor[] children, IProject project,
+ Map<ResourceFile, Long> timestamps) {
super(
fqcn, // xml name
name, // ui name
@@ -519,6 +535,8 @@ public final class CustomViewDescriptorService {
children,
false // mandatory
);
+ mTimeStamps = timestamps;
+ mProject = project;
}
@Override
@@ -533,5 +551,71 @@ public final class CustomViewDescriptorService {
return iconFactory.getIcon("customView"); //$NON-NLS-1$
}
+
+ @Override
+ public boolean syncAttributes() {
+ // Check if any of the descriptors
+ if (mTimeStamps != null) {
+ // Prevent checking actual file timestamps too frequently on rapid burst calls
+ long now = System.currentTimeMillis();
+ if (now - sLastCheck < 1000) {
+ return true;
+ }
+ sLastCheck = now;
+
+ // Check whether the resource files (typically just one) which defined
+ // custom attributes for this custom view have changed, and if so,
+ // refresh the attribute descriptors.
+ // This doesn't work the cases where you add descriptors for a custom
+ // view after using it, or add attributes in a separate file, but those
+ // scenarios aren't quite as common (and would require a bit more expensive
+ // analysis.)
+ for (Map.Entry<ResourceFile, Long> entry : mTimeStamps.entrySet()) {
+ ResourceFile file = entry.getKey();
+ Long timestamp = entry.getValue();
+ boolean recompute = false;
+ if (file.getFile().getModificationStamp() > timestamp.longValue()) {
+ // One or more attributes changed: recompute
+ recompute = true;
+ mParserCache.remove(file);
+ }
+
+ if (recompute) {
+ IJavaProject javaProject = JavaCore.create(mProject);
+ String fqcn = getFullClassName();
+ IType type = null;
+ try {
+ type = javaProject.findType(fqcn);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ if (type == null || !type.exists()) {
+ return true;
+ }
+
+ List<AttributeDescriptor> attrList = new ArrayList<AttributeDescriptor>();
+ List<AttributeDescriptor> paramList = new ArrayList<AttributeDescriptor>();
+
+ mTimeStamps = findCustomDescriptors(mProject, type, attrList, paramList);
+
+ ViewElementDescriptor parentDescriptor = getSuperClassDesc();
+ AttributeDescriptor[] attributes =
+ getAttributeDescriptor(type, parentDescriptor);
+ if (!attrList.isEmpty()) {
+ attributes = join(attrList, attributes);
+ }
+ attributes = attrList.toArray(new AttributeDescriptor[attrList.size()]);
+ setAttributes(attributes);
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
}
+
+ /** Timestamp of the most recent {@link CustomViewDescriptor#syncAttributes} check */
+ private static long sLastCheck;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java
index a515640..b3dce07 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/AccordionControl.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.custom.ScrolledComposite;
@@ -28,7 +29,6 @@ import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
-import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
@@ -218,10 +218,7 @@ public abstract class AccordionControl extends Composite {
label.setText(header.toString().replace("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$
updateBackground(label, false);
if (labelFont == null) {
- labelFont = label.getFont();
- FontData normal = labelFont.getFontData()[0];
- FontData bold = new FontData(normal.getName(), normal.getHeight(), SWT.BOLD);
- labelFont = new Font(null, bold);
+ labelFont = JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT);
}
label.setFont(labelFont);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
index 0d7fc28..7cbb8f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/CanvasTransform.java
@@ -179,4 +179,8 @@ public class CanvasTransform {
public int inverseTranslate(int screenX) {
return (int) ((screenX - mMargin + mTranslate) / mScale);
}
+
+ public int inverseScale(int canwasW) {
+ return (int) (canwasW / mScale);
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
index d4f25f8..493f996 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GraphicalEditorPart.java
@@ -17,7 +17,6 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
import static com.android.ide.common.layout.LayoutConstants.ANDROID_STRING_PREFIX;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
@@ -30,6 +29,7 @@ import static com.android.ide.common.layout.LayoutConstants.VALUE_WRAP_CONTENT;
import static com.android.ide.eclipse.adt.AdtConstants.ANDROID_PKG;
import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor.viewNeedsPackage;
import static com.android.sdklib.SdkConstants.FD_GEN_SOURCES;
+import static com.android.util.XmlUtils.ANDROID_URI;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_EAST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.DOCK_WEST;
import static org.eclipse.wb.core.controls.flyout.IFlyoutPreferences.STATE_COLLAPSED;
@@ -74,7 +74,6 @@ import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertyFa
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
-import com.android.ide.eclipse.adt.internal.lint.EclipseLintClient;
import com.android.ide.eclipse.adt.internal.resources.ResourceHelper;
import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources;
import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
@@ -273,6 +272,7 @@ public class GraphicalEditorPart extends EditorPart
private FlyoutControlComposite mStructureFlyout;
private FlyoutControlComposite mPaletteComposite;
private PropertyFactory mPropertyFactory;
+ private boolean mRenderedOnce;
/**
* Flags which tracks whether this editor is currently active which is set whenever
@@ -400,7 +400,7 @@ public class GraphicalEditorPart extends EditorPart
GridData detailsData = new GridData(SWT.FILL, SWT.FILL, true, false, 1, 1);
mActionBar.setLayoutData(detailsData);
if (file != null) {
- mActionBar.updateErrorIndicator(EclipseLintClient.hasMarkers(file));
+ mActionBar.updateErrorIndicator(file);
}
mSashError = new SashForm(layoutBarAndCanvas, SWT.VERTICAL | SWT.BORDER);
@@ -1611,6 +1611,21 @@ public class GraphicalEditorPart extends EditorPart
} else {
// Nope, no missing or broken classes. Clear success, congrats!
hideError();
+
+ // First time this layout is opened, run lint on the file (after a delay)
+ if (!mRenderedOnce) {
+ mRenderedOnce = true;
+ Job job = new Job("Run Lint") {
+ @Override
+ protected IStatus run(IProgressMonitor monitor) {
+ getEditorDelegate().delegateRunLint();
+ return Status.OK_STATUS;
+ }
+
+ };
+ job.setSystem(true);
+ job.schedule(3000); // 3 seconds
+ }
}
model.refreshUi();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
index 23ddaa1..53dd881 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageControl.java
@@ -16,6 +16,9 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.MouseEvent;
@@ -60,7 +63,7 @@ public class ImageControl extends Canvas implements MouseTrackListener {
* the control is disposed (unless the {@link #setDisposeImage} method is
* called to turn off auto dispose)
*/
- public ImageControl(Composite parent, int style, Image image) {
+ public ImageControl(@NonNull Composite parent, int style, @Nullable Image image) {
super(parent, style | SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED);
this.mImage = image;
@@ -72,11 +75,32 @@ public class ImageControl extends Canvas implements MouseTrackListener {
});
}
- public void setImage(Image image) {
- if (mDisposeImage) {
+ @Nullable
+ public Image getImage() {
+ return mImage;
+ }
+
+ public void setImage(@Nullable Image image) {
+ if (mDisposeImage && mImage != null) {
mImage.dispose();
}
mImage = image;
+ redraw();
+ }
+
+ public void fitToWidth(int width) {
+ if (mImage == null) {
+ return;
+ }
+ Rectangle imageRect = mImage.getBounds();
+ int imageWidth = imageRect.width;
+ if (imageWidth <= width) {
+ mScale = 1.0f;
+ return;
+ }
+
+ mScale = width / (float) imageWidth;
+ redraw();
}
public void setScale(float scale) {
@@ -87,13 +111,17 @@ public class ImageControl extends Canvas implements MouseTrackListener {
return mScale;
}
- public void setHoverColor(Color hoverColor) {
+ public void setHoverColor(@Nullable Color hoverColor) {
+ if (mHoverColor != null) {
+ removeMouseTrackListener(this);
+ }
mHoverColor = hoverColor;
if (hoverColor != null) {
addMouseTrackListener(this);
}
}
+ @Nullable
public Color getHoverColor() {
return mHoverColor;
}
@@ -102,7 +130,7 @@ public class ImageControl extends Canvas implements MouseTrackListener {
public void dispose() {
super.dispose();
- if (mDisposeImage) {
+ if (mDisposeImage && mImage != null && !mImage.isDisposed()) {
mImage.dispose();
}
mImage = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
index a5df4bd..c402b2b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ImageOverlay.java
@@ -32,6 +32,7 @@ import org.eclipse.swt.graphics.PaletteData;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
+import java.lang.ref.SoftReference;
/**
* The {@link ImageOverlay} class renders an image as an overlay.
@@ -56,7 +57,17 @@ public class ImageOverlay extends Overlay implements IImageFactory {
/** Current background AWT image. This is created by {@link #getImage()}, which is called
* by the LayoutLib. */
- private BufferedImage mAwtImage;
+ private SoftReference<BufferedImage> mAwtImage = new SoftReference<BufferedImage>(null);
+
+ /**
+ * Strong reference to the image in the above soft reference, to prevent
+ * garbage collection when {@link PRESCALE} is set, until the scaled image
+ * is created (lazily as part of the next paint call, where this strong
+ * reference is nulled out and the above soft reference becomes eligible to
+ * be reclaimed when memory is low.)
+ */
+ @SuppressWarnings("unused") // Used by the garbage collector to keep mAwtImage non-soft
+ private BufferedImage mAwtImageStrongRef;
/** The associated {@link LayoutCanvas}. */
private LayoutCanvas mCanvas;
@@ -109,8 +120,10 @@ public class ImageOverlay extends Overlay implements IImageFactory {
* @return The corresponding SWT image, or null.
*/
public synchronized Image setImage(BufferedImage awtImage, boolean isAlphaChannelImage) {
- if (awtImage != mAwtImage || awtImage == null) {
- mAwtImage = null;
+ BufferedImage oldAwtImage = mAwtImage.get();
+ if (awtImage != oldAwtImage || awtImage == null) {
+ mAwtImage.clear();
+ mAwtImageStrongRef = null;
if (mImage != null) {
mImage.dispose();
@@ -165,7 +178,12 @@ public class ImageOverlay extends Overlay implements IImageFactory {
*/
@Nullable
BufferedImage getAwtImage() {
- return mAwtImage;
+ BufferedImage awtImage = mAwtImage.get();
+ if (awtImage == null && mImage != null) {
+ awtImage = SwtUtils.convertToAwt(mImage);
+ }
+
+ return awtImage;
}
@Override
@@ -184,11 +202,12 @@ public class ImageOverlay extends Overlay implements IImageFactory {
// This is done lazily in paint rather than when the image changes because
// the image must be rescaled each time the zoom level changes, which varies
// independently from when the image changes.
- if (PRESCALE && mAwtImage != null) {
+ BufferedImage awtImage = mAwtImage.get();
+ if (PRESCALE && awtImage != null) {
if (mPreScaledImage == null ||
mPreScaledImage.getImageData().width != hi.getScalledImgSize()) {
- double xScale = hi.getScalledImgSize() / (double) mAwtImage.getWidth();
- double yScale = vi.getScalledImgSize() / (double) mAwtImage.getHeight();
+ double xScale = hi.getScalledImgSize() / (double) awtImage.getWidth();
+ double yScale = vi.getScalledImgSize() / (double) awtImage.getHeight();
BufferedImage scaledAwtImage;
// NOTE: == comparison on floating point numbers is okay
@@ -198,9 +217,9 @@ public class ImageOverlay extends Overlay implements IImageFactory {
// of rounding errors.
if (xScale == 1.0 && yScale == 1.0) {
// Scaling to 100% is easy!
- scaledAwtImage = mAwtImage;
+ scaledAwtImage = awtImage;
} else {
- scaledAwtImage = ImageUtils.scale(mAwtImage, xScale, yScale);
+ scaledAwtImage = ImageUtils.scale(awtImage, xScale, yScale);
}
assert scaledAwtImage.getWidth() == hi.getScalledImgSize();
if (mPreScaledImage != null && !mPreScaledImage.isDisposed()) {
@@ -208,6 +227,8 @@ public class ImageOverlay extends Overlay implements IImageFactory {
}
mPreScaledImage = SwtUtils.convertToSwt(mCanvas.getDisplay(), scaledAwtImage,
true /*transferAlpha*/, -1);
+ // We can't just clear the mAwtImageStrongRef here, because if the
+ // zooming factor changes, we may need to use it again
}
if (mPreScaledImage != null) {
@@ -349,14 +370,19 @@ public class ImageOverlay extends Overlay implements IImageFactory {
*/
@Override
public BufferedImage getImage(int w, int h) {
- if (mAwtImage == null ||
- mAwtImage.getWidth() != w ||
- mAwtImage.getHeight() != h) {
-
- mAwtImage = SwtReadyBufferedImage.createImage(w, h, getDevice());
+ BufferedImage awtImage = mAwtImage.get();
+ if (awtImage == null ||
+ awtImage.getWidth() != w ||
+ awtImage.getHeight() != h) {
+ mAwtImage.clear();
+ awtImage = SwtReadyBufferedImage.createImage(w, h, getDevice());
+ mAwtImage = new SoftReference<BufferedImage>(awtImage);
+ if (PRESCALE) {
+ mAwtImageStrongRef = awtImage;
+ }
}
- return mAwtImage;
+ return awtImage;
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
index 1c83430..63a36f2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutActionBar.java
@@ -15,8 +15,8 @@
*/
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-import static com.android.util.XmlUtils.ANDROID_URI;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+import static com.android.util.XmlUtils.ANDROID_URI;
import com.android.annotations.NonNull;
import com.android.ide.common.api.INode;
@@ -26,6 +26,7 @@ import com.android.ide.common.api.RuleAction.Separator;
import com.android.ide.common.api.RuleAction.Toggle;
import com.android.ide.common.layout.BaseViewRule;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
@@ -35,6 +36,7 @@ import com.android.sdkuilib.internal.widgets.ResolutionChooserDialog;
import com.google.common.base.Strings;
import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
@@ -553,9 +555,10 @@ public class LayoutActionBar extends Composite {
mLintButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
- IFile file = mEditor.getEditorDelegate().getEditor().getInputFile();
+ CommonXmlEditor editor = mEditor.getEditorDelegate().getEditor();
+ IFile file = editor.getInputFile();
if (file != null) {
- EclipseLintClient.showErrors(getShell(), file);
+ EclipseLintClient.showErrors(getShell(), file, editor);
}
}
});
@@ -567,7 +570,17 @@ public class LayoutActionBar extends Composite {
* Updates the lint indicator state in the given layout editor
*/
public void updateErrorIndicator() {
- updateErrorIndicator(EclipseLintClient.hasMarkers(mEditor.getEditedFile()));
+ updateErrorIndicator(mEditor.getEditedFile());
+ }
+
+ /**
+ * Updates the lint indicator state for the given file
+ *
+ * @param file the file to show the indicator status for
+ */
+ public void updateErrorIndicator(IFile file) {
+ IMarker[] markers = EclipseLintClient.getMarkers(file);
+ updateErrorIndicator(markers.length);
}
/**
@@ -575,14 +588,14 @@ public class LayoutActionBar extends Composite {
*
* @param hasLintWarnings whether there are lint errors to be shown
*/
- void updateErrorIndicator(final boolean hasLintWarnings) {
+ private void updateErrorIndicator(final int markerCount) {
Display display = getDisplay();
if (display.getThread() != Thread.currentThread()) {
display.asyncExec(new Runnable() {
@Override
public void run() {
if (!isDisposed()) {
- updateErrorIndicator(hasLintWarnings);
+ updateErrorIndicator(markerCount);
}
}
});
@@ -590,10 +603,37 @@ public class LayoutActionBar extends Composite {
}
GridData layoutData = (GridData) mLintToolBar.getLayoutData();
- if (layoutData.exclude == hasLintWarnings) {
- layoutData.exclude = !hasLintWarnings;
- mLintToolBar.setVisible(hasLintWarnings);
- layout();
+ Integer existing = (Integer) mLintToolBar.getData();
+ Integer current = Integer.valueOf(markerCount);
+ if (!current.equals(existing)) {
+ mLintToolBar.setData(current);
+ boolean layout = false;
+ boolean hasLintWarnings = markerCount > 0;
+ if (layoutData.exclude == hasLintWarnings) {
+ layoutData.exclude = !hasLintWarnings;
+ mLintToolBar.setVisible(hasLintWarnings);
+ layout = true;
+ }
+ if (markerCount > 0) {
+ String iconName = "";
+ switch (markerCount) {
+ case 1: iconName = "lint1"; break; //$NON-NLS-1$
+ case 2: iconName = "lint2"; break; //$NON-NLS-1$
+ case 3: iconName = "lint3"; break; //$NON-NLS-1$
+ case 4: iconName = "lint4"; break; //$NON-NLS-1$
+ case 5: iconName = "lint5"; break; //$NON-NLS-1$
+ case 6: iconName = "lint6"; break; //$NON-NLS-1$
+ case 7: iconName = "lint7"; break; //$NON-NLS-1$
+ case 8: iconName = "lint8"; break; //$NON-NLS-1$
+ case 9: iconName = "lint9"; break; //$NON-NLS-1$
+ default: iconName = "lint9p"; break;//$NON-NLS-1$
+ }
+ mLintButton.setImage(IconFactory.getInstance().getIcon(iconName));
+ }
+ if (layout) {
+ layout();
+ }
+ redraw();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
index 481c215..2154179 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java
@@ -197,6 +197,9 @@ public class LayoutCanvas extends Canvas {
/** The overlay which paints the mouse hover. */
private HoverOverlay mHoverOverlay;
+ /** The overlay which paints the lint warnings */
+ private LintOverlay mLintOverlay;
+
/** The overlay which paints the selection. */
private SelectionOverlay mSelectionOverlay;
@@ -267,6 +270,8 @@ public class LayoutCanvas extends Canvas {
mImageOverlay = new ImageOverlay(this, mHScale, mVScale);
mIncludeOverlay = new IncludeOverlay(this);
mImageOverlay.create(display);
+ mLintOverlay = new LintOverlay(this);
+ mLintOverlay.create(display);
// --- Set up listeners
addPaintListener(new PaintListener() {
@@ -310,6 +315,8 @@ public class LayoutCanvas extends Canvas {
if (editorDelegate != null) {
mOutlinePage = editorDelegate.getGraphicalOutline();
}
+
+ new LintTooltipManager(this).register();
}
private Runnable mZoomCheck = new Runnable() {
@@ -423,6 +430,11 @@ public class LayoutCanvas extends Canvas {
mIncludeOverlay = null;
}
+ if (mLintOverlay != null) {
+ mLintOverlay.dispose();
+ mLintOverlay = null;
+ }
+
mViewHierarchy.dispose();
}
@@ -651,7 +663,11 @@ public class LayoutCanvas extends Canvas {
* rendered image, but it will never zoom in.
*/
void setFitScale(boolean onlyZoomOut) {
- Image image = getImageOverlay().getImage();
+ ImageOverlay imageOverlay = getImageOverlay();
+ if (imageOverlay == null) {
+ return;
+ }
+ Image image = imageOverlay.getImage();
if (image != null) {
Rectangle canvasSize = getClientArea();
int canvasWidth = canvasSize.width;
@@ -774,6 +790,11 @@ public class LayoutCanvas extends Canvas {
if (!mHoverOverlay.isHiding()) {
mHoverOverlay.paint(gc);
}
+
+ if (!mLintOverlay.isHiding()) {
+ mLintOverlay.paint(gc);
+ }
+
if (!mIncludeOverlay.isHiding()) {
mIncludeOverlay.paint(gc);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
new file mode 100644
index 0000000..d5d5ce7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintOverlay.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.Rectangle;
+import org.w3c.dom.Node;
+
+import java.util.Collection;
+
+import lombok.ast.libs.org.parboiled.google.collect.Lists;
+
+/**
+ * The {@link LintOverlay} paints an icon over each view that contains at least one
+ * lint error (unless the view is smaller than the icon)
+ */
+public class LintOverlay extends Overlay {
+ /** Approximate size of lint overlay icons */
+ static final int ICON_SIZE = 8;
+ /** Alpha to draw lint overlay icons with */
+ private static final int ALPHA = 192;
+
+ private final LayoutCanvas mCanvas;
+ private Image mWarningImage;
+ private Image mErrorImage;
+
+ /**
+ * Constructs a new {@link LintOverlay}
+ *
+ * @param canvas the associated canvas
+ */
+ public LintOverlay(LayoutCanvas canvas) {
+ mCanvas = canvas;
+ }
+
+ @Override
+ public void paint(GC gc) {
+ LayoutEditorDelegate editor = mCanvas.getEditorDelegate();
+ Collection<Node> nodes = editor.getLintNodes();
+ if (nodes != null && !nodes.isEmpty()) {
+ // Copy list before iterating through it to avoid a concurrent list modification
+ // in case lint runs in the background while painting and updates this list
+ nodes = Lists.newArrayList(nodes);
+ ViewHierarchy hierarchy = mCanvas.getViewHierarchy();
+ Image icon = getWarningIcon();
+ ImageData imageData = icon.getImageData();
+ int iconWidth = imageData.width;
+ int iconHeight = imageData.height;
+ CanvasTransform mHScale = mCanvas.getHorizontalTransform();
+ CanvasTransform mVScale = mCanvas.getVerticalTransform();
+
+ int oldAlpha = gc.getAlpha();
+ try {
+ gc.setAlpha(ALPHA);
+ for (Node node : nodes) {
+ CanvasViewInfo vi = hierarchy.findViewInfoFor(node);
+ if (vi != null) {
+ Rectangle bounds = vi.getAbsRect();
+ int x = mHScale.translate(bounds.x);
+ int y = mVScale.translate(bounds.y);
+ int w = mHScale.scale(bounds.width);
+ int h = mVScale.scale(bounds.height);
+ if (w < iconWidth || h < iconHeight) {
+ // Don't draw badges on tiny widgets (including those
+ // that aren't tiny but are zoomed out too far)
+ continue;
+ }
+
+ x += w - iconWidth;
+ y += h - iconHeight;
+
+ boolean isError = false;
+ IMarker marker = editor.getIssueForNode(vi.getUiViewNode());
+ if (marker != null) {
+ int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+ isError = severity == IMarker.SEVERITY_ERROR;
+ }
+
+ icon = isError ? getErrorIcon() : getWarningIcon();
+
+ gc.drawImage(icon, x, y);
+ }
+ }
+ } finally {
+ gc.setAlpha(oldAlpha);
+ }
+ }
+ }
+
+ private Image getWarningIcon() {
+ if (mWarningImage == null) {
+ mWarningImage = IconFactory.getInstance().getIcon("warning-badge"); //$NON-NLS-1$
+ }
+
+ return mWarningImage;
+ }
+
+ private Image getErrorIcon() {
+ if (mErrorImage == null) {
+ mErrorImage = IconFactory.getInstance().getIcon("error-badge"); //$NON-NLS-1$
+ }
+
+ return mErrorImage;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java
new file mode 100644
index 0000000..bc2f390
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltip.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ID;
+
+import com.android.ide.common.layout.BaseLayoutRule;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+import java.util.List;
+
+/** Actual tooltip showing multiple lines for various widgets that have lint errors */
+class LintTooltip extends Shell {
+ private final LayoutCanvas mCanvas;
+ private final List<UiViewElementNode> mNodes;
+
+ LintTooltip(LayoutCanvas canvas, List<UiViewElementNode> nodes) {
+ super(canvas.getDisplay(), SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
+ mCanvas = canvas;
+ mNodes = nodes;
+
+ createContents();
+ }
+
+ protected void createContents() {
+ Display display = getDisplay();
+ Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
+ Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
+ setBackground(bg);
+ GridLayout gridLayout = new GridLayout(2, false);
+ setLayout(gridLayout);
+
+ LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
+
+ boolean first = true;
+ for (UiViewElementNode node : mNodes) {
+ IMarker marker = delegate.getIssueForNode(node);
+ if (marker != null) {
+ String message = marker.getAttribute(IMarker.MESSAGE, null);
+ if (message != null) {
+ Label icon = new Label(this, SWT.NONE);
+ icon.setForeground(fg);
+ icon.setBackground(bg);
+ icon.setImage(node.getIcon());
+
+ Label label = new Label(this, SWT.WRAP);
+ if (first) {
+ label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
+ first = false;
+ }
+
+ String id = BaseLayoutRule.stripIdPrefix(node.getAttributeValue(ATTR_ID));
+ if (id.isEmpty()) {
+ if (node.getXmlNode() != null) {
+ id = node.getXmlNode().getNodeName();
+ } else {
+ id = node.getDescriptor().getUiName();
+ }
+ }
+
+ label.setText(String.format("%1$s: %2$s", id, message));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void checkSubclass() {
+ // Disable the check that prevents subclassing of SWT components
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java
new file mode 100644
index 0000000..a2bbdf4
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LintTooltipManager.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
+
+import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.LintOverlay.ICON_SIZE;
+
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Point;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Shell;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** Tooltip in the layout editor showing lint errors under the cursor */
+class LintTooltipManager implements Listener {
+ private final LayoutCanvas mCanvas;
+ private Shell mTip = null;
+ private List<UiViewElementNode> mShowingNodes;
+
+ /**
+ * Sets up a custom tooltip when hovering over tree items. It currently displays the error
+ * message for the lint warning associated with each node, if any (and only if the hover
+ * is over the icon portion).
+ */
+ LintTooltipManager(LayoutCanvas canvas) {
+ mCanvas = canvas;
+ }
+
+ void register() {
+ mCanvas.addListener(SWT.Dispose, this);
+ mCanvas.addListener(SWT.KeyDown, this);
+ mCanvas.addListener(SWT.MouseMove, this);
+ mCanvas.addListener(SWT.MouseHover, this);
+ }
+
+ void unregister() {
+ mCanvas.removeListener(SWT.Dispose, this);
+ mCanvas.removeListener(SWT.KeyDown, this);
+ mCanvas.removeListener(SWT.MouseMove, this);
+ mCanvas.removeListener(SWT.MouseHover, this);
+ }
+
+ @Override
+ public void handleEvent(Event event) {
+ switch(event.type) {
+ case SWT.MouseMove:
+ // See if we're still overlapping this or *other* errors; if so, keep the
+ // tip up (or update it).
+ if (mShowingNodes != null) {
+ List<UiViewElementNode> nodes = computeNodes(event);
+ if (nodes != null && !nodes.isEmpty()) {
+ if (nodes.equals(mShowingNodes)) {
+ return;
+ } else {
+ show(nodes);
+ }
+ break;
+ }
+ }
+
+ // If not, fall through and hide the tooltip
+
+ //$FALL-THROUGH$
+ case SWT.Dispose:
+ case SWT.FocusOut:
+ case SWT.KeyDown:
+ case SWT.MouseExit:
+ case SWT.MouseDown:
+ hide();
+ break;
+ case SWT.MouseHover:
+ hide();
+ show(event);
+ break;
+ }
+ }
+
+ private void hide() {
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ }
+ mShowingNodes = null;
+ }
+
+ private void show(Event event) {
+ List<UiViewElementNode> nodes = computeNodes(event);
+ if (nodes != null && !nodes.isEmpty()) {
+ show(nodes);
+ }
+ }
+
+ /** Show a tooltip listing the lint errors for the given nodes */
+ private void show(List<UiViewElementNode> nodes) {
+ hide();
+
+ mTip = new LintTooltip(mCanvas, nodes);
+ Rectangle rect = mCanvas.getBounds();
+ Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ Point pos = mCanvas.toDisplay(rect.x, rect.y + rect.height);
+ if (size.x > rect.width) {
+ size = mTip.computeSize(rect.width, SWT.DEFAULT);
+ }
+ mTip.setBounds(pos.x, pos.y, size.x, size.y);
+
+ mShowingNodes = nodes;
+ mTip.setVisible(true);
+ }
+
+ /**
+ * Compute the list of nodes which have lint warnings near the given mouse
+ * coordinates
+ *
+ * @param event the mouse cursor event
+ * @return a list of nodes, possibly empty
+ */
+ @Nullable
+ private List<UiViewElementNode> computeNodes(Event event) {
+ LayoutPoint p = ControlPoint.create(mCanvas, event.x, event.y).toLayout();
+ LayoutEditorDelegate delegate = mCanvas.getEditorDelegate();
+ ViewHierarchy viewHierarchy = mCanvas.getViewHierarchy();
+ CanvasTransform mHScale = mCanvas.getHorizontalTransform();
+ CanvasTransform mVScale = mCanvas.getVerticalTransform();
+
+ int layoutIconSize = mHScale.inverseScale(ICON_SIZE);
+ int slop = mVScale.inverseScale(10); // extra space around icon where tip triggers
+
+ Collection<Node> xmlNodes = delegate.getLintNodes();
+ if (xmlNodes == null) {
+ return null;
+ }
+ List<UiViewElementNode> nodes = new ArrayList<UiViewElementNode>();
+ for (Node xmlNode : xmlNodes) {
+ CanvasViewInfo v = viewHierarchy.findViewInfoFor(xmlNode);
+ if (v != null) {
+ Rectangle b = v.getAbsRect();
+ int x2 = b.x + b.width;
+ int y2 = b.y + b.height;
+ if (p.x < x2 - layoutIconSize - slop
+ || p.x > x2 + slop
+ || p.y < y2 - layoutIconSize - slop
+ || p.y > y2 + slop) {
+ continue;
+ }
+
+ nodes.add(v.getUiViewNode());
+ }
+ }
+
+ return nodes;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
index eca47ab..9261aff 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage.java
@@ -16,25 +16,20 @@
package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
-import static com.android.util.XmlUtils.ANDROID_URI;
-import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
import static com.android.ide.common.layout.LayoutConstants.ATTR_COLUMN_COUNT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_COLUMN_SPAN;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW;
import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_ROW_SPAN;
-import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION;
import static com.android.ide.common.layout.LayoutConstants.ATTR_ROW_COUNT;
import static com.android.ide.common.layout.LayoutConstants.ATTR_SRC;
import static com.android.ide.common.layout.LayoutConstants.ATTR_TEXT;
import static com.android.ide.common.layout.LayoutConstants.DRAWABLE_PREFIX;
import static com.android.ide.common.layout.LayoutConstants.GRID_LAYOUT;
import static com.android.ide.common.layout.LayoutConstants.LAYOUT_PREFIX;
-import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
-import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL;
-import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG;
import static com.android.tools.lint.detector.api.LintConstants.AUTO_URI;
import static com.android.tools.lint.detector.api.LintConstants.URI_PREFIX;
+import static com.android.util.XmlUtils.ANDROID_URI;
import static org.eclipse.jface.viewers.StyledString.QUALIFIER_STYLER;
import com.android.annotations.VisibleForTesting;
@@ -45,7 +40,6 @@ import com.android.ide.common.layout.GridLayoutRule;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.DescriptorsUtils;
-import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder.Reference;
@@ -53,12 +47,12 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
import com.android.ide.eclipse.adt.internal.editors.layout.properties.PropertySheetPage;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
-import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.util.Pair;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
@@ -94,9 +88,18 @@ import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
+import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
+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.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.IActionBars;
@@ -256,6 +259,17 @@ public class OutlinePage extends ContentOutlinePage
}
}
+ /** Refresh all the icon state */
+ public void refreshIcons() {
+ TreeViewer treeViewer = getTreeViewer();
+ if (treeViewer != null) {
+ Tree tree = treeViewer.getTree();
+ if (tree != null && !tree.isDisposed()) {
+ treeViewer.refresh();
+ }
+ }
+ }
+
/**
* Set whether the outline should be shown in the header
*
@@ -442,6 +456,8 @@ public class OutlinePage extends ContentOutlinePage
public void keyReleased(KeyEvent e) {
}
});
+
+ setupTooltip();
}
private void createPropertySheet() {
@@ -686,45 +702,9 @@ public class OutlinePage extends ContentOutlinePage
element = ((CanvasViewInfo) element).getUiViewNode();
}
- if (element instanceof UiElementNode) {
- UiElementNode node = (UiElementNode) element;
- ElementDescriptor desc = node.getDescriptor();
- if (desc != null) {
- Image img = null;
- // Special case for the common case of vertical linear layouts:
- // show vertical linear icon (the default icon shows horizontal orientation)
- String uiName = desc.getUiName();
- if (uiName.equals(LINEAR_LAYOUT)) {
- Element e = (Element) node.getXmlNode();
- if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI,
- ATTR_ORIENTATION))) {
- IconFactory factory = IconFactory.getInstance();
- img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$
- }
- } else if (uiName.equals(VIEW_VIEWTAG)) {
- Node xmlNode = node.getXmlNode();
- if (xmlNode instanceof Element) {
- String className = ((Element) xmlNode).getAttribute(ATTR_CLASS);
- if (className != null && className.length() > 0) {
- int index = className.lastIndexOf('.');
- if (index != -1) {
- className = className.substring(index + 1);
- }
- img = IconFactory.getInstance().getIcon(className);
- }
- }
- }
- if (img == null) {
- img = desc.getGenericIcon();
- }
- if (img != null) {
- if (node.hasError()) {
- return new ErrorImageComposite(img).createImage();
- } else {
- return img;
- }
- }
- }
+ if (element instanceof UiViewElementNode) {
+ UiViewElementNode v = (UiViewElementNode) element;
+ return v.getIcon();
}
return AdtPlugin.getAndroidLogo();
@@ -756,7 +736,8 @@ public class OutlinePage extends ContentOutlinePage
// Temporary diagnostics code when developing GridLayout
if (GridLayoutRule.sDebugGridLayout) {
String namespace;
- if (e.getParentNode().getNodeName().equals(GRID_LAYOUT)) {
+ if (e.getParentNode() != null
+ && e.getParentNode().getNodeName().equals(GRID_LAYOUT)) {
namespace = ANDROID_URI;
} else {
IProject project = mGraphicalEditorPart.getProject();
@@ -1275,4 +1256,103 @@ public class OutlinePage extends ContentOutlinePage
makeContributions(null, toolBarManager, null);
toolBarManager.update(false);
}
+
+ /**
+ * Sets up a custom tooltip when hovering over tree items. It currently displays the error
+ * message for the lint warning associated with each node, if any (and only if the hover
+ * is over the icon portion).
+ */
+ private void setupTooltip() {
+ final Tree tree = getTreeViewer().getTree();
+
+ // This is based on SWT Snippet 125
+ final Listener listener = new Listener() {
+ Shell mTip = null;
+ Label mLabel = null;
+
+ @Override
+ public void handleEvent(Event event) {
+ switch(event.type) {
+ case SWT.Dispose:
+ case SWT.KeyDown:
+ case SWT.MouseExit:
+ case SWT.MouseDown:
+ case SWT.MouseMove:
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ mLabel = null;
+ }
+ break;
+ case SWT.MouseHover:
+ if (mTip != null) {
+ mTip.dispose();
+ mTip = null;
+ mLabel = null;
+ }
+
+ String tooltip = null;
+
+ TreeItem item = tree.getItem(new Point(event.x, event.y));
+ if (item != null) {
+ Rectangle rect = item.getBounds(0);
+ if (event.x - rect.x > 16) { // 16: Standard width of our outline icons
+ return;
+ }
+
+ Object data = item.getData();
+ if (data != null && data instanceof CanvasViewInfo) {
+ LayoutEditorDelegate editor = mGraphicalEditorPart.getEditorDelegate();
+ CanvasViewInfo vi = (CanvasViewInfo) data;
+ IMarker marker = editor.getIssueForNode(vi.getUiViewNode());
+ if (marker != null) {
+ tooltip = marker.getAttribute(IMarker.MESSAGE, null);
+ }
+ }
+
+ if (tooltip != null) {
+ Shell shell = tree.getShell();
+ Display display = tree.getDisplay();
+
+ Color fg = display.getSystemColor(SWT.COLOR_INFO_FOREGROUND);
+ Color bg = display.getSystemColor(SWT.COLOR_INFO_BACKGROUND);
+ mTip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL);
+ mTip.setBackground(bg);
+ FillLayout layout = new FillLayout();
+ layout.marginWidth = 1;
+ layout.marginHeight = 1;
+ mTip.setLayout(layout);
+ mLabel = new Label(mTip, SWT.WRAP);
+ mLabel.setForeground(fg);
+ mLabel.setBackground(bg);
+ mLabel.setText(tooltip);
+ mLabel.addListener(SWT.MouseExit, this);
+ mLabel.addListener(SWT.MouseDown, this);
+
+ Point pt = tree.toDisplay(rect.x, rect.y + rect.height);
+ Rectangle displayBounds = display.getBounds();
+ // -10: Don't extend -all- the way to the edge of the screen
+ // which would make it look like it has been cropped
+ int availableWidth = displayBounds.x + displayBounds.width - pt.x - 10;
+ if (availableWidth < 80) {
+ availableWidth = 80;
+ }
+ Point size = mTip.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ if (size.x > availableWidth) {
+ size = mTip.computeSize(availableWidth, SWT.DEFAULT);
+ }
+ mTip.setBounds(pt.x, pt.y, size.x, size.y);
+
+ mTip.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/adt/internal/editors/layout/gle2/RenderLogger.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java
index 74bc4f5..9b0d7a6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java
@@ -144,15 +144,20 @@ class RenderLogger extends LayoutLog {
@Override
public void warning(String tag, String message, Object data) {
String description = describe(message);
- AdtPlugin.log(IStatus.WARNING, "%1$s: %2$s", mName, description);
+ boolean log = true;
if (TAG_RESOURCES_FORMAT.equals(tag)) {
if (description.equals("You must supply a layout_width attribute.") //$NON-NLS-1$
|| description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$
tag = TAG_MISSING_DIMENSION;
+ log = false;
}
}
+ if (log) {
+ AdtPlugin.log(IStatus.WARNING, "%1$s: %2$s", mName, description);
+ }
+
addWarning(tag, description);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
index 579ef44..23e42ef 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/ViewHierarchy.java
@@ -30,6 +30,7 @@ import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import com.android.util.Pair;
import org.eclipse.swt.graphics.Rectangle;
+import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import java.awt.image.BufferedImage;
@@ -117,6 +118,9 @@ public class ViewHierarchy {
/** Map from nodes to canvas view infos */
private Map<UiViewElementNode, CanvasViewInfo> mNodeToView = Collections.emptyMap();
+ /** Map from DOM nodes to canvas view infos */
+ private Map<Node, CanvasViewInfo> mDomNodeToView = Collections.emptyMap();
+
/**
* Disposes the view hierarchy content.
*/
@@ -220,11 +224,17 @@ public class ViewHierarchy {
mInvisibleParents.clear();
addInvisibleParents(mLastValidViewInfoRoot, explodedNodes);
+ mDomNodeToView = new HashMap<Node, CanvasViewInfo>(mNodeToView.size());
+ for (Map.Entry<UiViewElementNode, CanvasViewInfo> entry : mNodeToView.entrySet()) {
+ mDomNodeToView.put(entry.getKey().getXmlNode(), entry.getValue());
+ }
+
// Update the selection
mCanvas.getSelectionManager().sync();
} else {
mIncludedBounds = null;
mInvisibleParents.clear();
+ mDomNodeToView = Collections.emptyMap();
}
}
@@ -424,37 +434,25 @@ public class ViewHierarchy {
* @return The {@link CanvasViewInfo} corresponding to the given node, or
* null if no match was found.
*/
- public CanvasViewInfo findViewInfoFor(Node node) {
- if (mLastValidViewInfoRoot != null) {
- return findViewInfoForNode(node, mLastValidViewInfoRoot);
- }
- return null;
- }
-
- /**
- * Tries to find a child with the same view XML node in the view info sub-tree.
- * Returns null if not found.
- */
- private CanvasViewInfo findViewInfoForNode(Node xmlNode, CanvasViewInfo canvasViewInfo) {
- if (canvasViewInfo == null) {
- return null;
- }
- if (canvasViewInfo.getXmlNode() == xmlNode) {
- return canvasViewInfo;
- }
+ @Nullable
+ public CanvasViewInfo findViewInfoFor(@Nullable Node node) {
+ CanvasViewInfo vi = mDomNodeToView.get(node);
- // Try to find a matching child
- for (CanvasViewInfo child : canvasViewInfo.getChildren()) {
- CanvasViewInfo v = findViewInfoForNode(xmlNode, child);
- if (v != null) {
- return v;
+ if (vi == null) {
+ if (node == null) {
+ return null;
+ } else if (node.getNodeType() == Node.TEXT_NODE) {
+ return mDomNodeToView.get(node.getParentNode());
+ } else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
+ return mDomNodeToView.get(((Attr) node).getOwnerElement());
+ } else if (node.getNodeType() == Node.DOCUMENT_NODE) {
+ return mDomNodeToView.get(node.getOwnerDocument().getDocumentElement());
}
}
- return null;
+ return vi;
}
-
/**
* Tries to find the inner most child matching the given x,y coordinates in
* the view info sub-tree, starting at the last know view info root. This
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
index b87435c..fef6022 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/uimodel/UiViewElementNode.java
@@ -16,10 +16,21 @@
package com.android.ide.eclipse.adt.internal.editors.layout.uimodel;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_CLASS;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_ORIENTATION;
import static com.android.ide.common.layout.LayoutConstants.FQCN_FRAME_LAYOUT;
-
+import static com.android.ide.common.layout.LayoutConstants.LINEAR_LAYOUT;
+import static com.android.ide.common.layout.LayoutConstants.VALUE_VERTICAL;
+import static com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors.VIEW_VIEWTAG;
+import static com.android.util.XmlUtils.ANDROID_URI;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.AttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttributeDescriptor;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.LayoutDescriptors;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiDocumentNode;
@@ -29,7 +40,11 @@ import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.sdklib.IAndroidTarget;
import com.android.util.XmlUtils;
+import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
+import org.eclipse.swt.graphics.Image;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
/**
* Specialized version of {@link UiElementNode} for the {@link ViewElementDescriptor}s.
@@ -51,6 +66,9 @@ public class UiViewElementNode extends UiElementNode {
*/
@Override
public AttributeDescriptor[] getAttributeDescriptors() {
+ if (!getDescriptor().syncAttributes()) {
+ mCachedAttributeDescriptors = null;
+ }
if (mCachedAttributeDescriptors != null) {
return mCachedAttributeDescriptors;
}
@@ -116,6 +134,64 @@ public class UiViewElementNode extends UiElementNode {
return mCachedAttributeDescriptors;
}
+ public Image getIcon() {
+ ElementDescriptor desc = getDescriptor();
+ if (desc != null) {
+ Image img = null;
+ // Special case for the common case of vertical linear layouts:
+ // show vertical linear icon (the default icon shows horizontal orientation)
+ String uiName = desc.getUiName();
+ IconFactory icons = IconFactory.getInstance();
+ if (uiName.equals(LINEAR_LAYOUT)) {
+ Element e = (Element) getXmlNode();
+ if (VALUE_VERTICAL.equals(e.getAttributeNS(ANDROID_URI,
+ ATTR_ORIENTATION))) {
+ IconFactory factory = icons;
+ img = factory.getIcon("VerticalLinearLayout"); //$NON-NLS-1$
+ }
+ } else if (uiName.equals(VIEW_VIEWTAG)) {
+ Node xmlNode = getXmlNode();
+ if (xmlNode instanceof Element) {
+ String className = ((Element) xmlNode).getAttribute(ATTR_CLASS);
+ if (className != null && className.length() > 0) {
+ int index = className.lastIndexOf('.');
+ if (index != -1) {
+ className = className.substring(index + 1);
+ }
+ img = icons.getIcon(className);
+ }
+ }
+ }
+ if (img == null) {
+ img = desc.getGenericIcon();
+ }
+
+ if (img != null) {
+ AndroidXmlEditor editor = getEditor();
+ if (editor != null) {
+ LayoutEditorDelegate delegate = LayoutEditorDelegate.fromEditor(editor);
+ if (delegate != null) {
+ IMarker marker = delegate.getIssueForNode(this);
+ if (marker != null) {
+ int severity = marker.getAttribute(IMarker.SEVERITY, 0);
+ if (severity == IMarker.SEVERITY_ERROR) {
+ return icons.addErrorIcon(img);
+ } else {
+ return icons.addWarningIcon(img);
+ }
+ }
+ }
+ }
+
+ return img;
+ }
+
+ return img;
+ }
+
+ return AdtPlugin.getAndroidLogo();
+ }
+
/**
* Sets the parent of this UI node.
* <p/>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
index 64b22a2..22737e1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestContentAssist.java
@@ -17,8 +17,10 @@
package com.android.ide.eclipse.adt.internal.editors.manifest;
import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_MIN_SDK_VERSION;
+import static com.android.sdklib.xml.AndroidManifest.ATTRIBUTE_TARGET_SDK_VERSION;
import com.android.annotations.VisibleForTesting;
+import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidContentAssist;
import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
@@ -30,9 +32,7 @@ import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.w3c.dom.Node;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Content Assist Processor for AndroidManifest.xml
@@ -51,41 +51,31 @@ public final class ManifestContentAssist extends AndroidContentAssist {
protected void computeAttributeValues(List<ICompletionProposal> proposals, int offset,
String parentTagName, String attributeName, Node node, String wordPrefix,
boolean skipEndTag, int replaceLength) {
- if (attributeName.endsWith(':' + ATTRIBUTE_MIN_SDK_VERSION)) {
+ if (attributeName.endsWith(ATTRIBUTE_MIN_SDK_VERSION)
+ || attributeName.endsWith(ATTRIBUTE_TARGET_SDK_VERSION)) {
// The user is completing the minSdkVersion attribute: it should be
// an integer for the API version, but we'll add full Android version
// names to make it more obvious what they're selecting
List<Pair<String, String>> choices = new ArrayList<Pair<String, String>>();
- // Max: Look up what versions I have
+ int max = AdtUtils.getHighestKnownApiLevel();
+ // Look for any more recent installed versions the user may have
IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
- Map<String, IAndroidTarget> versionMap = new HashMap<String, IAndroidTarget>();
- List<String> codeNames = new ArrayList<String>();
- int maxVersion = 1;
for (IAndroidTarget target : targets) {
AndroidVersion version = target.getVersion();
int apiLevel = version.getApiLevel();
- String key;
- if (version.isPreview()) {
- key = version.getCodename();
- codeNames.add(key);
- apiLevel--;
- } else {
- key = Integer.toString(apiLevel);
+ if (apiLevel > max) {
+ if (version.isPreview()) {
+ // Use codename, not API level, as version string for preview versions
+ choices.add(Pair.of(version.getCodename(), version.getCodename()));
+ } else {
+ choices.add(Pair.of(Integer.toString(apiLevel), target.getFullName()));
+ }
}
- if (apiLevel > maxVersion) {
- maxVersion = apiLevel;
- }
-
- versionMap.put(key, target);
- }
- for (String codeName : codeNames) {
- choices.add(Pair.<String, String>of(codeName, null));
}
- for (int i = maxVersion; i >= 1; i--) {
- IAndroidTarget target = versionMap.get(Integer.toString(i));
- String version = target != null ? target.getFullName() : null;
- choices.add(Pair.of(Integer.toString(i), version));
+ for (int api = max; api >= 1; api--) {
+ String name = AdtUtils.getAndroidName(api);
+ choices.add(Pair.of(Integer.toString(api), name));
}
char needTag = 0;
addMatchingProposals(proposals, choices.toArray(), offset, node, wordPrefix,
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
index c08a67e..2f10f68 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/ManifestInfo.java
@@ -109,6 +109,8 @@ public class ManifestInfo {
private Map<String, String> mActivityThemes;
private IAbstractFile mManifestFile;
private long mLastModified;
+ private long mLastChecked;
+ private String mMinSdkName;
private int mMinSdk;
private int mTargetSdk;
private String mApplicationIcon;
@@ -163,8 +165,13 @@ public class ManifestInfo {
* with respect to the manifest file
*/
private void sync() {
- // TODO: Add a last synced timestamp so that I can avoid doing all this with rapid
- // burst calls on separate methods which all call sync() first!
+ // Since each of the accessors call sync(), allow a bunch of immediate
+ // accessors to all bypass the file stat() below
+ long now = System.currentTimeMillis();
+ if (now - mLastChecked < 50 && mManifestFile != null) {
+ return;
+ }
+ mLastChecked = now;
if (mManifestFile == null) {
IFolderWrapper projectFolder = new IFolderWrapper(mProject);
@@ -186,6 +193,7 @@ public class ManifestInfo {
mManifestTheme = null;
mTargetSdk = 1; // Default when not specified
mMinSdk = 1; // Default when not specified
+ mMinSdkName = ""; // Default when not specified
mPackage = ""; //$NON-NLS-1$
mApplicationIcon = null;
mApplicationLabel = null;
@@ -252,7 +260,7 @@ public class ManifestInfo {
}
}
- private static int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
+ private int getApiVersion(Element usesSdk, String attribute, int defaultApiLevel) {
String valueString = null;
if (usesSdk.hasAttributeNS(NS_RESOURCES, attribute)) {
valueString = usesSdk.getAttributeNS(NS_RESOURCES, attribute);
@@ -272,6 +280,10 @@ public class ManifestInfo {
apiLevel = target.getVersion().getApiLevel() + 1;
}
}
+
+ if (usesSdk.getTagName().equals(ATTRIBUTE_MIN_SDK_VERSION)) {
+ mMinSdkName = valueString;
+ }
}
return apiLevel;
@@ -309,7 +321,7 @@ public class ManifestInfo {
* @return a manifest theme, or null if none was registered
*/
@Nullable
- public String getmManifestTheme() {
+ public String getManifestTheme() {
sync();
return mManifestTheme;
}
@@ -388,6 +400,17 @@ public class ManifestInfo {
}
/**
+ * Returns the minimum SDK version name (which may not be a numeric string, e.g.
+ * it could be a codename)
+ *
+ * @return the minimum SDK version
+ */
+ public String getMinSdkName() {
+ sync();
+ return mMinSdkName;
+ }
+
+ /**
* Returns the {@link IPackageFragment} for the package registered in the manifest
*
* @return the {@link IPackageFragment} for the package registered in the manifest
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
index f6ec863..7085e5d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/ErrorImageComposite.java
@@ -1,5 +1,10 @@
package com.android.ide.eclipse.adt.internal.editors.ui;
+import static org.eclipse.ui.ISharedImages.IMG_DEC_FIELD_ERROR;
+import static org.eclipse.ui.ISharedImages.IMG_DEC_FIELD_WARNING;
+import static org.eclipse.ui.ISharedImages.IMG_OBJS_ERROR_TSK;
+import static org.eclipse.ui.ISharedImages.IMG_OBJS_WARN_TSK;
+
import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
@@ -19,10 +24,30 @@ public class ErrorImageComposite extends CompositeImageDescriptor {
private ImageDescriptor mErrorImageDescriptor;
private Point mSize;
+ /**
+ * Creates a new {@link ErrorImageComposite}
+ *
+ * @param baseImage the base image to overlay an icon on top of
+ */
public ErrorImageComposite(Image baseImage) {
+ this(baseImage, false);
+ }
+
+ /**
+ * Creates a new {@link ErrorImageComposite}
+ *
+ * @param baseImage the base image to overlay an icon on top of
+ * @param warning if true, add a warning icon, otherwise an error icon
+ */
+ public ErrorImageComposite(Image baseImage, boolean warning) {
mBaseImage = baseImage;
- mErrorImageDescriptor = PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(
- ISharedImages.IMG_OBJS_ERROR_TSK);
+ ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
+ mErrorImageDescriptor = sharedImages.getImageDescriptor(
+ warning ? IMG_DEC_FIELD_WARNING : IMG_DEC_FIELD_ERROR);
+ if (mErrorImageDescriptor == null) {
+ mErrorImageDescriptor = sharedImages.getImageDescriptor(
+ warning ? IMG_OBJS_WARN_TSK : IMG_OBJS_ERROR_TSK);
+ }
mSize = new Point(baseImage.getBounds().width, baseImage.getBounds().height);
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java
index f2ab512..3373197 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiModelTreeLabelProvider.java
@@ -17,8 +17,8 @@
package com.android.ide.eclipse.adt.internal.editors.ui.tree;
import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor;
-import com.android.ide.eclipse.adt.internal.editors.ui.ErrorImageComposite;
import com.android.ide.eclipse.adt.internal.editors.uimodel.UiElementNode;
import org.eclipse.jface.viewers.ILabelProvider;
@@ -56,8 +56,7 @@ public class UiModelTreeLabelProvider implements ILabelProvider {
Image img = desc.getCustomizedIcon();
if (img != null) {
if (node != null && node.hasError()) {
- //TODO: cache image
- return new ErrorImageComposite(img).createImage();
+ return IconFactory.getInstance().addErrorIcon(img);
} else {
return img;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
index f1c9b1a..4cad83e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/values/ValuesContentAssist.java
@@ -201,7 +201,9 @@ public class ValuesContentAssist extends AndroidContentAssist {
}
}
}
- } else if (parentNode.getNodeName().equals(ITEM_TAG)) {
+ }
+
+ if (parentNode.getNodeName().equals(ITEM_TAG)) {
// Completing text content inside an <item> tag: offer @resource completion.
if (prefix.startsWith(PREFIX_RESOURCE_REF) || prefix.trim().length() == 0) {
String[] choices = UiResourceAttributeNode.computeResourceStringMatches(
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
index 1d80c8c..e7037ff 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/AddSuppressAttribute.java
@@ -41,7 +41,6 @@ import org.w3c.dom.Node;
/**
* Fix for adding {@code tools:ignore="id"} attributes in XML files.
*/
-@SuppressWarnings("restriction") // DOM model
class AddSuppressAttribute implements ICompletionProposal {
private final AndroidXmlEditor mEditor;
private final String mId;
@@ -98,7 +97,8 @@ class AddSuppressAttribute implements ICompletionProposal {
}
/**
- * Adds any applicable suppress lint fix resolutions into the given list
+ * Returns a quickfix to suppress a specific lint issue id on the node corresponding to
+ * the given marker.
*
* @param editor the associated editor containing the marker
* @param marker the marker to create fixes for
@@ -144,4 +144,35 @@ class AddSuppressAttribute implements ICompletionProposal {
Element element = (Element) node;
return new AddSuppressAttribute(editor, id, marker, element, desc);
}
+
+ /**
+ * Returns a quickfix to suppress a given issue type on the <b>root element</b>
+ * of the given editor.
+ *
+ * @param editor the associated editor containing the marker
+ * @param marker the marker to create fixes for
+ * @param id the issue id
+ * @return a fix for this marker, or null if unable
+ */
+ @Nullable
+ public static AddSuppressAttribute createFixForAll(
+ @NonNull AndroidXmlEditor editor,
+ @NonNull IMarker marker,
+ @NonNull String id) {
+ // This only applies to XML files:
+ String fileName = marker.getResource().getName();
+ if (!fileName.endsWith(DOT_XML)) {
+ return null;
+ }
+
+ Node node = DomUtilities.getNode(editor.getStructuredDocument(), 0);
+ if (node != null) {
+ node = node.getOwnerDocument().getDocumentElement();
+ String desc = String.format("Add ignore '%1$s\' to element", id);
+ Element element = (Element) node;
+ return new AddSuppressAttribute(editor, id, marker, element, desc);
+ }
+
+ return null;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
index 68b3407..703be80 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintClient.java
@@ -23,6 +23,7 @@ import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.tools.lint.checks.BuiltinIssueRegistry;
@@ -78,13 +79,17 @@ import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel;
+import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import java.io.File;
import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
import lombok.ast.ecj.EcjTreeConverter;
import lombok.ast.grammar.ParseProblem;
@@ -102,6 +107,8 @@ public class EclipseLintClient extends LintClient implements IDomParser {
private boolean mWasFatal;
private boolean mFatalOnly;
private EclipseJavaParser mJavaParser;
+ private boolean mCollectNodes;
+ private Map<Node, IMarker> mNodeMap;
/**
* Creates a new {@link EclipseLintClient}.
@@ -119,6 +126,58 @@ public class EclipseLintClient extends LintClient implements IDomParser {
mFatalOnly = fatalOnly;
}
+ /**
+ * Returns true if lint should only check fatal issues
+ *
+ * @return true if lint should only check fatal issues
+ */
+ public boolean isFatalOnly() {
+ return mFatalOnly;
+ }
+
+ /**
+ * Sets whether the lint client should store associated XML nodes for each
+ * reported issue
+ *
+ * @param collectNodes if true, collect node positions for errors in XML
+ * files, retrievable via the {@link #getIssueForNode} method
+ */
+ public void setCollectNodes(boolean collectNodes) {
+ mCollectNodes = collectNodes;
+ }
+
+ /**
+ * Returns one of the issues for the given node (there could be more than one)
+ *
+ * @param node the node to look up lint issues for
+ * @return the marker for one of the issues found for the given node
+ */
+ @Nullable
+ public IMarker getIssueForNode(@NonNull UiViewElementNode node) {
+ if (mNodeMap != null) {
+ return mNodeMap.get(node.getXmlNode());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns a collection of nodes that have one or more lint warnings
+ * associated with them (retrievable via
+ * {@link #getIssueForNode(UiViewElementNode)})
+ *
+ * @return a collection of nodes, which should <b>not</b> be modified by the
+ * caller
+ */
+ @Nullable
+ public Collection<Node> getIssueNodes() {
+ if (mNodeMap != null) {
+ return mNodeMap.keySet();
+ }
+
+ return null;
+ }
+
// ----- Extends LintClient -----
@Override
@@ -190,6 +249,7 @@ public class EclipseLintClient extends LintClient implements IDomParser {
return null;
}
+ @NonNull
@Override
public Configuration getConfiguration(Project project) {
if (project != null) {
@@ -249,6 +309,32 @@ public class EclipseLintClient extends LintClient implements IDomParser {
if (s == Severity.FATAL) {
mWasFatal = true;
}
+
+ if (mCollectNodes && location != null && marker != null) {
+ if (location instanceof LazyLocation) {
+ LazyLocation l = (LazyLocation) location;
+ IndexedRegion region = l.mRegion;
+ if (region instanceof Node) {
+ Node node = (Node) region;
+ if (node instanceof Attr) {
+ node = ((Attr) node).getOwnerElement();
+ }
+ if (mNodeMap == null) {
+ mNodeMap = new WeakHashMap<Node, IMarker>();
+ }
+ IMarker prev = mNodeMap.get(node);
+ if (prev != null) {
+ // Only replace the node if this node has higher priority
+ int prevSeverity = prev.getAttribute(IMarker.SEVERITY, 0);
+ if (prevSeverity < severity) {
+ mNodeMap.put(node, marker);
+ }
+ } else {
+ mNodeMap.put(node, marker);
+ }
+ }
+ }
+ }
}
@Override
@@ -325,16 +411,6 @@ public class EclipseLintClient extends LintClient implements IDomParser {
}
/**
- * Returns whether the given resource has one or more lint markers
- *
- * @param resource the resource to be checked, typically a source file
- * @return true if the given resource has one or more lint markers
- */
- public static boolean hasMarkers(IResource resource) {
- return getMarkers(resource).length > 0;
- }
-
- /**
* Returns the lint marker for the given resource (which may be a project, folder or file)
*
* @param resource the resource to be checked, typically a source file
@@ -532,9 +608,13 @@ public class EclipseLintClient extends LintClient implements IDomParser {
*
* @param shell the parent shell to attach the dialog to
* @param file the file to show the errors for
+ * @param editor the editor for the file, if known
*/
- public static void showErrors(Shell shell, final IFile file) {
- LintListDialog dialog = new LintListDialog(shell, file);
+ public static void showErrors(
+ @NonNull Shell shell,
+ @NonNull IFile file,
+ @Nullable IEditorPart editor) {
+ LintListDialog dialog = new LintListDialog(shell, file, editor);
dialog.open();
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
index 25e80fc..d69412b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/EclipseLintRunner.java
@@ -15,40 +15,24 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
-import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
-import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
-import com.android.sdklib.SdkConstants;
import com.android.tools.lint.client.api.IssueRegistry;
-import com.android.tools.lint.client.api.LintDriver;
-import com.android.tools.lint.detector.api.Issue;
-import com.android.tools.lint.detector.api.Scope;
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Shell;
-import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
@@ -75,7 +59,7 @@ public class EclipseLintRunner {
@Nullable IDocument doc,
boolean fatalOnly) {
resources = addLibraries(resources);
- CheckFileJob job = (CheckFileJob) startLint(resources, source, doc, fatalOnly,
+ LintJob job = (LintJob) startLint(resources, source, doc, fatalOnly,
false /*show*/);
try {
job.join();
@@ -116,12 +100,38 @@ public class EclipseLintRunner {
@Nullable IDocument doc,
boolean fatalOnly,
boolean show) {
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ EclipseLintClient client = new EclipseLintClient(registry, resources, doc, fatalOnly);
+ return startLint(client, resources, source, show);
+ }
+
+ /**
+ * Runs lint and updates the markers. Does not wait for the job to finish -
+ * just returns immediately.
+ *
+ * @param client the lint client receiving issue reports etc
+ * @param resources the resources (project, folder or file) to be analyzed
+ * @param source if checking a single source file, the source file. When
+ * single checking an XML file, this is typically the same as the
+ * file passed in the list in the first parameter, but when
+ * checking the .class files of a Java file for example, the
+ * .class file and all the inner classes of the Java file are
+ * passed in the first parameter, and the corresponding .java
+ * source file is passed here.
+ * @param show if true, show the results in a {@link LintViewPart}
+ * @return the job running lint in the background.
+ */
+ public static Job startLint(
+ @NonNull EclipseLintClient client,
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source,
+ boolean show) {
if (resources != null && !resources.isEmpty()) {
resources = addLibraries(resources);
cancelCurrentJobs(false);
- CheckFileJob job = new CheckFileJob(resources, source, doc, fatalOnly);
+ LintJob job = new LintJob(client, resources, source);
job.schedule();
if (show) {
@@ -162,16 +172,10 @@ public class EclipseLintRunner {
return true;
}
- /** Returns the current lint jobs, if any (never returns null but array may be empty) */
- static Job[] getCurrentJobs() {
- IJobManager jobManager = Job.getJobManager();
- return jobManager.find(CheckFileJob.FAMILY_RUN_LINT);
- }
-
/** Cancels the current lint jobs, if any, and optionally waits for them to finish */
static void cancelCurrentJobs(boolean wait) {
// Cancel any current running jobs first
- Job[] currentJobs = getCurrentJobs();
+ Job[] currentJobs = LintJob.getCurrentJobs();
for (Job job : currentJobs) {
job.cancel();
}
@@ -229,132 +233,4 @@ public class EclipseLintRunner {
return resources;
}
-
- private static final class CheckFileJob extends Job {
- /** Job family */
- private static final Object FAMILY_RUN_LINT = new Object();
- private final List<? extends IResource> mResources;
- private final IResource mSource;
- private final IDocument mDocument;
- private LintDriver mLint;
- private boolean mFatal;
- private boolean mFatalOnly;
-
- private CheckFileJob(
- @NonNull List<? extends IResource> resources,
- @Nullable IResource source,
- @Nullable IDocument doc,
- boolean fatalOnly) {
- super("Running Android Lint");
- mResources = resources;
- mSource = source;
- mDocument = doc;
- mFatalOnly = fatalOnly;
- }
-
- @Override
- public boolean belongsTo(Object family) {
- return family == FAMILY_RUN_LINT;
- }
-
- @Override
- protected void canceling() {
- super.canceling();
- if (mLint != null) {
- mLint.cancel();
- }
- }
-
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- try {
- monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
- IssueRegistry registry = EclipseLintClient.getRegistry();
- EnumSet<Scope> scope = null;
- List<File> files = new ArrayList<File>(mResources.size());
- for (IResource resource : mResources) {
- File file = AdtUtils.getAbsolutePath(resource).toFile();
- files.add(file);
-
- if (resource instanceof IProject && mSource == null) {
- scope = Scope.ALL;
- } else {
- String name = resource.getName();
- if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
- if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
- scope = EnumSet.of(Scope.MANIFEST);
- } else {
- scope = Scope.RESOURCE_FILE_SCOPE;
- }
- } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
- if (scope != null) {
- if (!scope.contains(Scope.JAVA_FILE)) {
- scope = EnumSet.copyOf(scope);
- scope.add(Scope.JAVA_FILE);
- }
- } else {
- scope = Scope.JAVA_FILE_SCOPE;
- }
- } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
- if (scope != null) {
- if (!scope.contains(Scope.CLASS_FILE)) {
- scope = EnumSet.copyOf(scope);
- scope.add(Scope.CLASS_FILE);
- }
- } else {
- scope = Scope.CLASS_FILE_SCOPE;
- }
- } else {
- return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
- "Only XML files are supported for single file lint", null); //$NON-NLS-1$
- }
- }
- }
- if (scope == null) {
- scope = Scope.ALL;
- }
- if (mSource == null) {
- assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
- }
- // Check single file?
- if (mSource != null) {
- // Delete specific markers
- IMarker[] markers = EclipseLintClient.getMarkers(mSource);
- for (IMarker marker : markers) {
- String id = marker.getAttribute(MARKER_CHECKID_PROPERTY, ""); //$NON-NLS-1$
- Issue issue = registry.getIssue(id);
- if (issue == null) {
- continue;
- }
- if (issue.isAdequate(scope)) {
- marker.delete();
- }
- }
- } else {
- EclipseLintClient.clearMarkers(mResources);
- }
-
- EclipseLintClient client = new EclipseLintClient(registry, mResources,
- mDocument, mFatalOnly);
- mLint = new LintDriver(registry, client);
- mLint.analyze(files, scope);
- mFatal = client.hasFatalErrors();
- return Status.OK_STATUS;
- } catch (Exception e) {
- return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
- "Failed", e); //$NON-NLS-1$
- } finally {
- if (monitor != null) {
- monitor.done();
- }
- }
- }
-
- /**
- * Returns true if a fatal error was encountered
- */
- boolean isFatal() {
- return mFatal;
- }
- }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
index 8da3465..646d752 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/GlobalLintConfiguration.java
@@ -15,6 +15,7 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import com.android.annotations.NonNull;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
import com.android.tools.lint.client.api.Configuration;
@@ -47,6 +48,7 @@ class GlobalLintConfiguration extends Configuration {
*
* @return the singleton configuration
*/
+ @NonNull
public static GlobalLintConfiguration get() {
return sInstance;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
new file mode 100644
index 0000000..aaf55c6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintJob.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.lint;
+
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_CLASS;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAVA;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.sdklib.SdkConstants;
+import com.android.tools.lint.client.api.IssueRegistry;
+import com.android.tools.lint.client.api.LintDriver;
+import com.android.tools.lint.detector.api.Issue;
+import com.android.tools.lint.detector.api.Scope;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.Job;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+
+/** Job to check lint on a set of resources */
+final class LintJob extends Job {
+ /** Job family */
+ private static final Object FAMILY_RUN_LINT = new Object();
+ private final EclipseLintClient mClient;
+ private final List<? extends IResource> mResources;
+ private final IResource mSource;
+ private LintDriver mLint;
+ private boolean mFatal;
+
+ LintJob(
+ @NonNull EclipseLintClient client,
+ @NonNull List<? extends IResource> resources,
+ @Nullable IResource source) {
+ super("Running Android Lint");
+ mClient = client;
+ mResources = resources;
+ mSource = source;
+ }
+
+ @Override
+ public boolean belongsTo(Object family) {
+ return family == FAMILY_RUN_LINT;
+ }
+
+ @Override
+ protected void canceling() {
+ super.canceling();
+ if (mLint != null) {
+ mLint.cancel();
+ }
+ }
+
+ @Override
+ @NonNull
+ protected IStatus run(IProgressMonitor monitor) {
+ try {
+ monitor.beginTask("Looking for errors", IProgressMonitor.UNKNOWN);
+ IssueRegistry registry = EclipseLintClient.getRegistry();
+ EnumSet<Scope> scope = null;
+ List<File> files = new ArrayList<File>(mResources.size());
+ for (IResource resource : mResources) {
+ File file = AdtUtils.getAbsolutePath(resource).toFile();
+ files.add(file);
+
+ if (resource instanceof IProject && mSource == null) {
+ scope = Scope.ALL;
+ } else {
+ String name = resource.getName();
+ if (AdtUtils.endsWithIgnoreCase(name, DOT_XML)) {
+ if (name.equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
+ scope = EnumSet.of(Scope.MANIFEST);
+ } else {
+ scope = Scope.RESOURCE_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_JAVA) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.JAVA_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.JAVA_FILE);
+ }
+ } else {
+ scope = Scope.JAVA_FILE_SCOPE;
+ }
+ } else if (name.endsWith(DOT_CLASS) && resource instanceof IFile) {
+ if (scope != null) {
+ if (!scope.contains(Scope.CLASS_FILE)) {
+ scope = EnumSet.copyOf(scope);
+ scope.add(Scope.CLASS_FILE);
+ }
+ } else {
+ scope = Scope.CLASS_FILE_SCOPE;
+ }
+ } else {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Only XML & Java files are supported for single file lint", null); //$NON-NLS-1$
+ }
+ }
+ }
+ if (scope == null) {
+ scope = Scope.ALL;
+ }
+ if (mSource == null) {
+ assert !Scope.checkSingleFile(scope) : scope + " with " + mResources;
+ }
+ // Check single file?
+ if (mSource != null) {
+ // Delete specific markers
+ IMarker[] markers = EclipseLintClient.getMarkers(mSource);
+ for (IMarker marker : markers) {
+ String id = marker.getAttribute(EclipseLintRunner.MARKER_CHECKID_PROPERTY, "");
+ Issue issue = registry.getIssue(id);
+ if (issue == null) {
+ continue;
+ }
+ if (issue.isAdequate(scope)) {
+ marker.delete();
+ }
+ }
+ } else {
+ EclipseLintClient.clearMarkers(mResources);
+ }
+
+ mLint = new LintDriver(registry, mClient);
+ mLint.analyze(files, scope);
+ mFatal = mClient.hasFatalErrors();
+ return Status.OK_STATUS;
+ } catch (Exception e) {
+ return new Status(Status.ERROR, AdtPlugin.PLUGIN_ID, Status.ERROR,
+ "Failed", e); //$NON-NLS-1$
+ } finally {
+ if (monitor != null) {
+ monitor.done();
+ }
+ }
+ }
+
+ /**
+ * Returns true if a fatal error was encountered
+ *
+ * @return true if a fatal error was encountered
+ */
+ public boolean isFatal() {
+ return mFatal;
+ }
+
+ /**
+ * Returns the associated lint client
+ *
+ * @return the associated lint client
+ */
+ @NonNull
+ public EclipseLintClient getLintClient() {
+ return mClient;
+ }
+
+ /** Returns the current lint jobs, if any (never returns null but array may be empty) */
+ @NonNull
+ static Job[] getCurrentJobs() {
+ IJobManager jobManager = Job.getJobManager();
+ return jobManager.find(LintJob.FAMILY_RUN_LINT);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
index 561c5fd..3b01e05 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintList.java
@@ -261,6 +261,13 @@ class LintList extends Composite implements IResourceChangeListener, ControlList
updateColumnWidths(); // in case mSingleFile changed
}
+ /** Select the first item */
+ public void selectFirst() {
+ if (mTree.getItemCount() > 0) {
+ mTree.select(mTree.getItem(0));
+ }
+ }
+
private List<IMarker> getMarkers() {
mErrorCount = mWarningCount = 0;
List<IMarker> markerList = new ArrayList<IMarker>();
@@ -585,11 +592,25 @@ class LintList extends Composite implements IResourceChangeListener, ControlList
/** Expands all nodes */
public void expandAll() {
mTreeViewer.expandAll();
+
+ if (mExpandedIds == null) {
+ mExpandedIds = new HashSet<String>();
+ }
+ IMarker[] topMarkers = mContentProvider.getTopMarkers();
+ if (topMarkers != null) {
+ for (IMarker marker : topMarkers) {
+ String id = EclipseLintClient.getId(marker);
+ if (id != null) {
+ mExpandedIds.add(id);
+ }
+ }
+ }
}
/** Collapses all nodes */
public void collapseAll() {
mTreeViewer.collapseAll();
+ mExpandedIds = null;
}
// ---- Column Persistence ----
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
index d39916f..147327d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintListDialog.java
@@ -15,9 +15,12 @@
*/
package com.android.ide.eclipse.adt.internal.lint;
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
@@ -45,22 +48,30 @@ import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@SuppressWarnings("restriction") // WST DOM access
class LintListDialog extends TitleAreaDialog implements SelectionListener {
- private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$
- private IFile mFile;
+ private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$
+ private final IFile mFile;
+ private final IEditorPart mEditor;
private Button mFixButton;
private Button mIgnoreButton;
+ private Button mIgnoreAllButton;
private Button mShowButton;
private Text mDetailsText;
private Button mIgnoreTypeButton;
private LintList mList;
- LintListDialog(Shell parentShell, IFile file) {
+ LintListDialog(
+ @NonNull Shell parentShell,
+ @NonNull IFile file,
+ @Nullable IEditorPart editor) {
super(parentShell);
- this.mFile = file;
+ mFile = file;
+ mEditor = editor;
}
@Override
@@ -80,7 +91,8 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
Control contents = super.createContents(parent);
setTitle("Lint Warnings in Layout");
setMessage("Lint Errors found for the current layout:");
- setTitleImage(AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE).createImage());
+ setTitleImage(IconFactory.getInstance().getIcon(PROJECT_LOGO_LARGE));
+
return contents;
}
@@ -97,31 +109,42 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
if (page.getActivePart() != null) {
site = page.getActivePart().getSite();
}
+
mList = new LintList(site, container, null /*memento*/, true /*singleFile*/);
- mList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 5));
+ mList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 6));
mShowButton = new Button(container, SWT.NONE);
mShowButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
mShowButton.setText("Show");
+ mShowButton.setToolTipText("Opens the editor to reveal the XML with the issue");
mShowButton.addSelectionListener(this);
+ mFixButton = new Button(container, SWT.NONE);
+ mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mFixButton.setText("Fix");
+ mFixButton.setToolTipText("Automatically corrects the problem, if possible");
+ mFixButton.setEnabled(false);
+ mFixButton.addSelectionListener(this);
+
mIgnoreButton = new Button(container, SWT.NONE);
mIgnoreButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mIgnoreButton.setText("Ignore");
- mIgnoreButton.setEnabled(false);
+ mIgnoreButton.setText("Suppress Issue");
+ mIgnoreButton.setToolTipText("Adds a special attribute in the layout to suppress this specific warning");
mIgnoreButton.addSelectionListener(this);
+ mIgnoreAllButton = new Button(container, SWT.NONE);
+ mIgnoreAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+ mIgnoreAllButton.setText("Suppress in Layout");
+ mIgnoreAllButton.setEnabled(mEditor instanceof AndroidXmlEditor);
+ mIgnoreAllButton.setToolTipText("Adds an attribute on the root element to suppress all issues of this type in this layout");
+ mIgnoreAllButton.addSelectionListener(this);
+
mIgnoreTypeButton = new Button(container, SWT.NONE);
mIgnoreTypeButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mIgnoreTypeButton.setText("Ignore Type");
+ mIgnoreTypeButton.setText("Disable Issue Type");
+ mIgnoreTypeButton.setToolTipText("Turns off checking for this type of error everywhere");
mIgnoreTypeButton.addSelectionListener(this);
- mFixButton = new Button(container, SWT.NONE);
- mFixButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
- mFixButton.setText("Fix");
- mFixButton.setEnabled(false);
- mFixButton.addSelectionListener(this);
-
new Label(container, SWT.NONE);
mDetailsText = new Text(container, SWT.BORDER | SWT.READ_ONLY | SWT.WRAP
@@ -138,6 +161,7 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
mList.addSelectionListener(this);
mList.setResources(Collections.<IResource>singletonList(mFile));
+ mList.selectFirst();
if (mList.getSelectedMarkers().size() > 0) {
updateSelectionState();
}
@@ -205,6 +229,28 @@ class LintListDialog extends TitleAreaDialog implements SelectionListener {
LintFixGenerator.suppressDetector(id, true, mFile, true /*all*/);
}
}
+ } else if (source == mIgnoreButton) {
+ for (IMarker marker : mList.getSelectedMarkers()) {
+ LintFixGenerator.addSuppressAnnotation(marker);
+ }
+ } else if (source == mIgnoreAllButton) {
+ Set<String> ids = new HashSet<String>();
+ for (IMarker marker : mList.getSelectedMarkers()) {
+ String id = EclipseLintClient.getId(marker);
+ if (id != null && !ids.contains(id)) {
+ ids.add(id);
+ if (mEditor instanceof AndroidXmlEditor) {
+ AndroidXmlEditor editor = (AndroidXmlEditor) mEditor;
+ AddSuppressAttribute fix = AddSuppressAttribute.createFixForAll(editor,
+ marker, id);
+ if (fix != null) {
+ IStructuredDocument document = editor.getStructuredDocument();
+ fix.apply(document);
+ }
+ }
+ }
+ }
+ mList.refresh();
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
index 39c6d25..3761fde 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintViewPart.java
@@ -291,7 +291,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha
}
private void refreshStopIcon() {
- Job[] currentJobs = EclipseLintRunner.getCurrentJobs();
+ Job[] currentJobs = LintJob.getCurrentJobs();
if (currentJobs.length > 0) {
ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
mRefreshAction.setImageDescriptor(sharedImages.getImageDescriptor(
@@ -466,7 +466,7 @@ public class LintViewPart extends ViewPart implements SelectionListener, IJobCha
workbench.saveAllEditors(false /*confirm*/);
}
- Job[] jobs = EclipseLintRunner.getCurrentJobs();
+ Job[] jobs = LintJob.getCurrentJobs();
if (jobs.length > 0) {
EclipseLintRunner.cancelCurrentJobs(false);
} else {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
index 18f3db3..a0f1262 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/RunLintAction.java
@@ -21,13 +21,11 @@ import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.AdtUtils;
import com.android.ide.eclipse.adt.internal.editors.IconFactory;
-import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper;
import com.android.tools.lint.detector.api.LintUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.ui.JavaElementLabelProvider;
import org.eclipse.jface.action.Action;
@@ -39,7 +37,6 @@ import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
-import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IObjectActionDelegate;
@@ -51,8 +48,6 @@ import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.texteditor.ITextEditor;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
/**
@@ -77,81 +72,16 @@ public class RunLintAction implements IObjectActionDelegate, IMenuCreator,
if (!projects.isEmpty()) {
EclipseLintRunner.startLint(projects, null, null, false /*fatalOnly*/, true /*show*/);
- } else {
- MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(), "Lint",
- "Could not run Lint: Select a project first.");
}
}
/** Returns the Android project(s) to apply a lint run to. */
static List<IProject> getProjects(ISelection selection, boolean warn) {
- List<IProject> projects = new ArrayList<IProject>();
-
- if (selection instanceof IStructuredSelection) {
- IStructuredSelection structuredSelection = (IStructuredSelection) selection;
- // get the unique selected item.
- Iterator<?> iterator = structuredSelection.iterator();
- while (iterator.hasNext()) {
- Object element = iterator.next();
-
- // First look up the resource (since some adaptables
- // provide an IResource but not an IProject, and we can
- // always go from IResource to IProject)
- IResource resource = null;
- if (element instanceof IResource) { // may include IProject
- resource = (IResource) element;
- } else if (element instanceof IAdaptable) {
- IAdaptable adaptable = (IAdaptable)element;
- Object adapter = adaptable.getAdapter(IResource.class);
- resource = (IResource) adapter;
- }
-
- // get the project object from it.
- IProject project = null;
- if (resource != null) {
- project = resource.getProject();
- } else if (element instanceof IAdaptable) {
- project = (IProject) ((IAdaptable) element).getAdapter(IProject.class);
- }
-
- if (project != null && !projects.contains(project)) {
- projects.add(project);
- }
- }
- }
-
- if (projects.isEmpty()) {
- // Try to look at the active editor instead
- IFile file = AdtUtils.getActiveFile();
- if (file != null) {
- projects.add(file.getProject());
- }
- }
-
- if (projects.isEmpty()) {
- // If we didn't find a default project based on the selection, check how many
- // open Android projects we can find in the current workspace. If there's only
- // one, we'll just select it by default.
- IJavaProject[] open = AdtUtils.getOpenAndroidProjects();
- for (IJavaProject project : open) {
- projects.add(project.getProject());
- }
- } else {
- // Make sure all the projects are Android projects
- for (IProject project : projects) {
- if (!BaseProjectHelper.isAndroidProject(project)) {
- if (warn) {
- MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(),
- "Lint", "Select Android projects.");
- }
- return Collections.emptyList();
- }
- }
- }
+ List<IProject> projects = AdtUtils.getSelectedProjects(selection);
if (projects.isEmpty() && warn) {
MessageDialog.openWarning(AdtPlugin.getDisplay().getActiveShell(), "Lint",
- "Could not run Lint: Select a project first.");
+ "Could not run Lint: Select an Android project first.");
}
return projects;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
index 827f8a4..b2d7361 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/LintPreferencePage.java
@@ -338,6 +338,10 @@ public class LintPreferencePage extends PropertyPage implements IWorkbenchPrefer
prefs.setLintOnSave(mCheckFileCheckbox.getSelection());
}
+ if (mConfiguration == null) {
+ return;
+ }
+
mConfiguration.startBulkEditing();
try {
// Severities
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
index 8f6de3a..45590b7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/project/CompatibilityLibraryHelper.java
@@ -163,7 +163,7 @@ public class CompatibilityLibraryHelper {
for (IJavaProject javaProject : AdtUtils.getOpenAndroidProjects()) {
IProject project = javaProject.getProject();
ProjectState state = Sdk.getProjectState(project);
- if (state.isLibrary()) {
+ if (state != null && state.isLibrary()) {
ManifestInfo manifestInfo = ManifestInfo.get(project);
if (manifestInfo.getPackage().equals("android.support.v7.gridlayout")) { //$NON-NLS-1$
return project;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
index de27011..35ef1c7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
@@ -171,6 +171,7 @@ public class WelcomeWizardPage extends WizardPage implements ModifyListener, Sel
String file = dialog.open();
String path = mExistingDirText.getText().trim();
if (path.length() > 0) {
+ // TODO: Shouldn't this be done before the open() call?
dialog.setFilterPath(path);
}
if (file != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
index 0d7e9ae..cda13b5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ApplicationInfoPage.java
@@ -52,7 +52,7 @@ import java.io.FileFilter;
import java.net.URI;
/** Page where you choose the application name, activity name, and optional test project info */
-class ApplicationInfoPage extends WizardPage implements SelectionListener, ModifyListener,
+public class ApplicationInfoPage extends WizardPage implements SelectionListener, ModifyListener,
ITargetChangeListener {
private static final String JDK_15 = "1.5"; //$NON-NLS-1$
private final static String DUMMY_PACKAGE = "your.package.namespace";
@@ -637,7 +637,7 @@ class ApplicationInfoPage extends WizardPage implements SelectionListener, Modif
return null;
}
- private IStatus validatePackage(String packageFieldContents) {
+ public static IStatus validatePackage(String packageFieldContents) {
// Validate package
if (packageFieldContents == null || packageFieldContents.length() == 0) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
@@ -677,8 +677,17 @@ class ApplicationInfoPage extends WizardPage implements SelectionListener, Modif
return null;
}
+ return validateActivity(mValues.activityName);
+ }
+
+ /**
+ * Validates the given activity name
+ *
+ * @param activityFieldContents the activity name to validate
+ * @return a status for whether the activity name is valid
+ */
+ public static IStatus validateActivity(String activityFieldContents) {
// Validate activity field
- String activityFieldContents = mValues.activityName;
if (activityFieldContents == null || activityFieldContents.length() == 0) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Activity name must be specified.");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
index 5b557ac..2dc7c71 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectCreator.java
@@ -360,7 +360,6 @@ public class NewProjectCreator {
String pkg =
mValues.mode == Mode.TEST ? mValues.packageName : mValues.testPackageName;
- parameters.put(PARAM_PROJECT, projectName);
parameters.put(PARAM_PACKAGE, pkg);
parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME);
parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder());
@@ -481,7 +480,8 @@ public class NewProjectCreator {
mainData.getProject(),
mainData.getDescription(),
mainData.getParameters(),
- mainData.getDictionary());
+ mainData.getDictionary(),
+ null);
if (mainProject != null) {
final IJavaProject javaProject = JavaCore.create(mainProject);
@@ -512,7 +512,8 @@ public class NewProjectCreator {
testData.getProject(),
testData.getDescription(),
parameters,
- testData.getDictionary());
+ testData.getDictionary(),
+ null);
if (testProject != null) {
final IJavaProject javaProject = JavaCore.create(testProject);
Display.getDefault().syncExec(new Runnable() {
@@ -556,7 +557,8 @@ public class NewProjectCreator {
IProject project,
IProjectDescription description,
Map<String, Object> parameters,
- Map<String, String> dictionary)
+ Map<String, String> dictionary,
+ Runnable projectPopulator)
throws CoreException, IOException, StreamException {
// get the project target
@@ -587,6 +589,10 @@ public class NewProjectCreator {
addDefaultDirectories(project, RES_DIRECTORY, RES_DENSITY_ENABLED_DIRECTORIES, monitor);
}
+ if (projectPopulator != null) {
+ projectPopulator.run();
+ }
+
// Setup class path: mark folders as source folders
IJavaProject javaProject = JavaCore.create(project);
setupSourceFolders(javaProject, sourceFolders, monitor);
@@ -662,6 +668,28 @@ public class NewProjectCreator {
return project;
}
+ public static IProject create(
+ IProgressMonitor monitor,
+ IProject project,
+ IAndroidTarget target,
+ Runnable projectPopulator)
+ throws CoreException, IOException, StreamException {
+ NewProjectCreator creator = new NewProjectCreator(null, null);
+
+ Map<String, String> dictionary = null;
+ Map<String, Object> parameters = new HashMap<String, Object>();
+ parameters.put(PARAM_SDK_TARGET, target);
+ parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES);
+ parameters.put(PARAM_IS_NEW_PROJECT, false);
+ parameters.put(PARAM_SAMPLE_LOCATION, null);
+
+ IWorkspace workspace = ResourcesPlugin.getWorkspace();
+ final IProjectDescription description = workspace.newProjectDescription(project.getName());
+
+ return creator.createEclipseProject(monitor, project, description, parameters,
+ dictionary, projectPopulator);
+ }
+
/**
* Adds default directories to the project.
*
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
index d4c342c..7592c58 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ProjectNamePage.java
@@ -66,7 +66,7 @@ import java.util.regex.Pattern;
* Initial page shown when creating projects which asks for the project name,
* the the location of the project, working sets, etc.
*/
-class ProjectNamePage extends WizardPage implements SelectionListener, ModifyListener {
+public class ProjectNamePage extends WizardPage implements SelectionListener, ModifyListener {
private final NewProjectWizardState mValues;
/** Flag used when setting button/text state manually to ignore listener updates */
private boolean mIgnore;
@@ -601,7 +601,7 @@ class ProjectNamePage extends WizardPage implements SelectionListener, ModifyLis
return null;
}
- static IStatus validateProjectName(String projectName) {
+ public static IStatus validateProjectName(String projectName) {
if (projectName == null || projectName.length() == 0) {
return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
"Project name must be specified");
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java
new file mode 100644
index 0000000..9a61b4f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/ActivityPage.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard.ACTIVITY_TEMPLATES;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_PADDING;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_WIDTH;
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.graphics.Font;
+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.Label;
+import org.eclipse.swt.widgets.List;
+
+import java.io.InputStream;
+
+class ActivityPage extends WizardPage implements SelectionListener {
+ private final NewProjectWizardState mValues;
+ private List mList;
+ private Button mCreateToggle;
+
+ private boolean mIgnore;
+ private boolean mShown;
+ private ImageControl mPreview;
+ private Image mPreviewImage;
+ private Label mHeading;
+ private Label mDescription;
+
+ /**
+ * Create the wizard.
+ */
+ ActivityPage(NewProjectWizardState values) {
+ super("activityPage"); //$NON-NLS-1$
+ mValues = values;
+
+ setTitle("Create Activity");
+ setDescription("Select whether to create an activity, and if so, what kind of activity.");
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ container.setLayout(new GridLayout(3, false));
+
+ mCreateToggle = new Button(container, SWT.CHECK);
+ mCreateToggle.setSelection(true);
+ mCreateToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ mCreateToggle.setText("Create Activity");
+ mCreateToggle.addSelectionListener(this);
+
+ mList = new List(container, SWT.BORDER | SWT.V_SCROLL);
+ mList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+ mList.setItems(ACTIVITY_TEMPLATES);
+ int index = -1;
+ for (int i = 0; i < ACTIVITY_TEMPLATES.length; i++) {
+ if (ACTIVITY_TEMPLATES[i].equals(mValues.activityValues.getTemplateName())) {
+ index = i;
+ break;
+ }
+ }
+ if (index == -1) {
+ mValues.activityValues.setTemplateName(ACTIVITY_TEMPLATES[0]);
+ index = 0;
+ }
+ mList.setSelection(index);
+ mList.addSelectionListener(this);
+
+ // Preview
+ mPreview = new ImageControl(container, SWT.NONE, null);
+ GridData gd_mImage = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1);
+ gd_mImage.widthHint = PREVIEW_WIDTH + 2 * PREVIEW_PADDING;
+ mPreview.setLayoutData(gd_mImage);
+ new Label(container, SWT.NONE);
+
+ mHeading = new Label(container, SWT.NONE);
+ mHeading.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ new Label(container, SWT.NONE);
+
+ mDescription = new Label(container, SWT.WRAP);
+ mDescription.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1));
+
+ Font font = JFaceResources.getFontRegistry().getBold(JFaceResources.BANNER_FONT);
+ if (font != null) {
+ mHeading.setFont(font);
+ }
+
+ setPreview(mValues.activityValues.getTemplateName());
+ }
+
+ private void setPreview(String templateName) {
+ Image oldImage = mPreviewImage;
+ mPreviewImage = null;
+
+ String title = "";
+ String description = "";
+ TemplateMetadata template = TemplateHandler.getTemplate(templateName);
+ if (template != null) {
+ String thumb = template.getThumbnailPath();
+ if (thumb != null && !thumb.isEmpty()) {
+ String filePath = TemplateHandler.getTemplatePath(templateName) + '/' + thumb;
+ InputStream input = AdtPlugin.readEmbeddedFileAsStream(filePath);
+ if (input != null) {
+ try {
+ mPreviewImage = new Image(getControl().getDisplay(), input);
+ input.close();
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+ title = template.getTitle();
+ description = template.getDescription();
+ }
+
+ mHeading.setText(title);
+ mDescription.setText(description);
+ mPreview.setImage(mPreviewImage);
+ mPreview.fitToWidth(PREVIEW_WIDTH);
+
+ if (oldImage != null) {
+ oldImage.dispose();
+ }
+
+ Composite parent = (Composite) getControl();
+ parent.layout(true, true);
+ parent.redraw();
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ if (mPreviewImage != null) {
+ mPreviewImage.dispose();
+ mPreviewImage = null;
+ }
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ if (visible) {
+ mShown = true;
+ try {
+ mIgnore = true;
+ mCreateToggle.setSelection(mValues.createActivity);
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ validatePage();
+ }
+
+
+ private void validatePage() {
+ IStatus status = null;
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mList.getSelectionCount() < 1) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Select an activity type");
+ }
+ }
+
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ if (!mValues.createAppSkeleton) {
+ return true;
+ }
+
+ // Ensure that the Finish button isn't enabled until
+ // the user has reached and completed this page
+ if (!mShown) {
+ return false;
+ }
+
+ return super.isPageComplete();
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mCreateToggle) {
+ mValues.createActivity = mCreateToggle.getSelection();
+ mList.setEnabled(mValues.createActivity);
+ } else if (source == mList) {
+ int index = mList.getSelectionIndex();
+ String[] items = mList.getItems();
+ if (index >= 0 && index < items.length) {
+ String templateName = items[index];
+ mValues.activityValues.setTemplateName(templateName);
+ setPreview(templateName);
+ }
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/AppSkeletonPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/AppSkeletonPage.java
new file mode 100644
index 0000000..ed45a7b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/AppSkeletonPage.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+
+class AppSkeletonPage extends WizardPage implements SelectionListener {
+ private final NewProjectWizardState mValues;
+
+ private Button mActionBarToggle;
+ private Button mAboutToggle;
+ private Button mSettingsToggle;
+ private Button mPhoneToggle;
+ private Button mTabletToggle;
+
+ private boolean mIgnore;
+ private Button mCreateIconToggle;
+
+ AppSkeletonPage(NewProjectWizardState values) {
+ super("appSkeletonPage");
+ mValues = values;
+ setTitle("Configure Application Skeleton");
+ setDescription("Select which platforms to target and what to include in the app");
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ GridLayout gl_container = new GridLayout(2, false);
+ gl_container.horizontalSpacing = 10;
+ container.setLayout(gl_container);
+ Label targetLabel = new Label(container, SWT.NONE);
+ targetLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ targetLabel.setText("Target:");
+ mPhoneToggle = new Button(container, SWT.CHECK);
+ mPhoneToggle.setSelection(true);
+ mPhoneToggle.setText("Phones");
+ mPhoneToggle.setEnabled(false);
+ mPhoneToggle.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+ mTabletToggle = new Button(container, SWT.CHECK);
+ mTabletToggle.setSelection(true);
+ mTabletToggle.setText("Tablets");
+ mTabletToggle.setEnabled(false);
+ mTabletToggle.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+ Label uiLabel = new Label(container, SWT.NONE);
+ uiLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ uiLabel.setText("User Interface:");
+ mActionBarToggle = new Button(container, SWT.CHECK);
+ mActionBarToggle.setSelection(true);
+ mActionBarToggle.setText("Action Bar");
+ mActionBarToggle.setEnabled(false);
+ mActionBarToggle.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+ mSettingsToggle = new Button(container, SWT.CHECK);
+ mSettingsToggle.setSelection(true);
+ mSettingsToggle.setText("Settings");
+ mSettingsToggle.setEnabled(false);
+ mSettingsToggle.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+ mAboutToggle = new Button(container, SWT.CHECK);
+ mAboutToggle.setSelection(true);
+ mAboutToggle.setText("About");
+ mAboutToggle.setEnabled(false);
+ mAboutToggle.addSelectionListener(this);
+
+ new Label(container, SWT.NONE);
+ mCreateIconToggle = new Button(container, SWT.CHECK);
+ mCreateIconToggle.setSelection(mValues.createIcon);
+ mCreateIconToggle.setText("Create custom launcher icon");
+ mCreateIconToggle.addSelectionListener(this);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ validatePage();
+ }
+
+ private void validatePage() {
+ IStatus status = null;
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ // Ensure that you're choosing at least one UI target
+ if (!mValues.phone && !mValues.tablet) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "You must choose at least one target (tablet or phone)");
+ }
+ }
+
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mPhoneToggle) {
+ mValues.phone = mPhoneToggle.getSelection();
+ } else if (source == mTabletToggle) {
+ mValues.tablet = mTabletToggle.getSelection();
+ } else if (source == mCreateIconToggle) {
+ mValues.createIcon = mCreateIconToggle.getSelection();
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmActivityToLayoutMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmActivityToLayoutMethod.java
new file mode 100644
index 0000000..a6f2a9e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmActivityToLayoutMethod.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectPage.ACTIVITY_NAME_SUFFIX;
+
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to convert an Activity class name into
+ * a suitable layout name.
+ */
+public class FmActivityToLayoutMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+
+ String activityName = args.get(0).toString();
+
+ // Strip off the end portion of the activity name. The user might be typing
+ // the activity name such that only a portion has been entered so far (e.g.
+ // "MainActivi") and we want to chop off that portion too such that we don't
+ // offer a layout name partially containing the activity suffix (e.g. "main_activi").
+ int suffixStart = activityName.lastIndexOf(ACTIVITY_NAME_SUFFIX.charAt(0));
+ if (suffixStart != -1 && activityName.regionMatches(suffixStart, ACTIVITY_NAME_SUFFIX, 0,
+ activityName.length() - suffixStart)) {
+ activityName = activityName.substring(0, suffixStart);
+ }
+ assert !activityName.endsWith(ACTIVITY_NAME_SUFFIX) : activityName;
+
+ // Convert CamelCase convention used in activity class names to underlined convention
+ // used in layout name:
+ String name = AdtUtils.camelCaseToUnderlines(activityName);
+
+ return new SimpleScalar(name);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmCamelCaseToUnderscoreMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmCamelCaseToUnderscoreMethod.java
new file mode 100644
index 0000000..f017aa7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmCamelCaseToUnderscoreMethod.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to convert an underscore name into a CamelCase name.
+ */
+public class FmCamelCaseToUnderscoreMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ return new SimpleScalar(AdtUtils.underlinesToCamelCase(args.get(0).toString()));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmLayoutToActivityMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmLayoutToActivityMethod.java
new file mode 100644
index 0000000..f3dd9cd
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmLayoutToActivityMethod.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.AdtUtils.extractClassName;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectPage.ACTIVITY_NAME_SUFFIX;
+
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to convert a layout name into an appropriate
+ * Activity class.
+ */
+public class FmLayoutToActivityMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+
+ String name = args.get(0).toString();
+ name = AdtUtils.underlinesToCamelCase(name);
+ String className = extractClassName(name);
+ if (className == null) {
+ className = "My";
+ }
+ String activityName = className + ACTIVITY_NAME_SUFFIX;
+
+ return new SimpleScalar(activityName);
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmSlashedPackageNameMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmSlashedPackageNameMethod.java
new file mode 100644
index 0000000..60a6531
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmSlashedPackageNameMethod.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to convert a package name (foo.bar) into
+ * a slashed path (foo/bar)
+ */
+public class FmSlashedPackageNameMethod implements TemplateMethodModel {
+
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+
+ return new SimpleScalar(args.get(0).toString().replace('.', '/'));
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmUnderscoreToCamelCaseMethod.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmUnderscoreToCamelCaseMethod.java
new file mode 100644
index 0000000..94a41da
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/FmUnderscoreToCamelCaseMethod.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import com.android.ide.eclipse.adt.AdtUtils;
+
+import freemarker.template.SimpleScalar;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+
+import java.util.List;
+
+/**
+ * Method invoked by FreeMarker to convert a CamelCase word into
+ * underscore_names.
+ */
+public class FmUnderscoreToCamelCaseMethod implements TemplateMethodModel {
+ @Override
+ public TemplateModel exec(List args) throws TemplateModelException {
+ if (args.size() != 1) {
+ throw new TemplateModelException("Wrong arguments");
+ }
+ return new SimpleScalar(AdtUtils.camelCaseToUnderlines(args.get(0).toString()));
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java
new file mode 100644
index 0000000..0c40fab
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+
+import static com.android.ide.eclipse.adt.AdtUtils.extractClassName;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplatePage.WIZARD_PAGE_WIDTH;
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
+
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.sdk.Sdk;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.ProjectNamePage;
+import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.IAndroidTarget;
+import com.google.common.collect.Maps;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.Combo;
+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 java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import lombok.ast.libs.org.parboiled.google.collect.Lists;
+
+/**
+ * First wizard page in the "New Project From Template" wizard
+ */
+public class NewProjectPage extends WizardPage
+ implements ModifyListener, SelectionListener, FocusListener {
+ private static final String SAMPLE_PACKAGE_PREFIX = "com.example."; //$NON-NLS-1$
+ /** Suffix added by default to activity names */
+ static final String ACTIVITY_NAME_SUFFIX = "Activity"; //$NON-NLS-1$
+ private static final int INITIAL_MIN_SDK = 8;
+
+ private final NewProjectWizardState mValues;
+ private Map<String, Integer> mMinNameToApi;
+
+ private Text mProjectText;
+ private Text mPackageText;
+ private Text mApplicationText;
+ private Combo mMinSdkCombo;
+ private Button mCompatToggle;
+
+ private boolean mIgnore;
+ private Combo mBuildSdkCombo;
+ private Button mChooseSdkButton;
+ private Button mSkeletonToggle;
+ private Label mHelpIcon;
+ private Label mTipLabel;
+
+ private ControlDecoration mApplicationDec;
+ private ControlDecoration mProjectDec;
+ private ControlDecoration mPackageDec;
+ private ControlDecoration mBuildTargetDec;
+ private ControlDecoration mMinSdkDec;
+
+ NewProjectPage(NewProjectWizardState values) {
+ super("newAndroidApp"); //$NON-NLS-1$
+ mValues = values;
+ setTitle("New Android Application");
+ setDescription("Creates a new Android Application");
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ GridLayout gl_container = new GridLayout(4, false);
+ gl_container.horizontalSpacing = 10;
+ container.setLayout(gl_container);
+
+ Label applicationLabel = new Label(container, SWT.NONE);
+ applicationLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
+ applicationLabel.setText("Application Name:");
+
+ mApplicationText = new Text(container, SWT.BORDER);
+ mApplicationText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mApplicationText.addModifyListener(this);
+ mApplicationText.addFocusListener(this);
+ mApplicationDec = createFieldDecoration(mApplicationText,
+ "The application name is shown in the Play store, as well as in the " +
+ "Manage Application list in settings.");
+
+ Label projectLabel = new Label(container, SWT.NONE);
+ projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
+ projectLabel.setText("Project Name:");
+ mProjectText = new Text(container, SWT.BORDER);
+ mProjectText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mProjectText.addModifyListener(this);
+ mProjectText.addFocusListener(this);
+ mProjectDec = createFieldDecoration(mProjectText,
+ "The project name is only used by Eclipse, but must be unique within the " +
+ "workspace. This can typically be the same as the application name.");
+
+ Label packageLabel = new Label(container, SWT.NONE);
+ packageLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
+ packageLabel.setText("Package Name:");
+
+ mPackageText = new Text(container, SWT.BORDER);
+ mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mPackageText.addModifyListener(this);
+ mPackageText.addFocusListener(this);
+ mPackageDec = createFieldDecoration(mPackageText,
+ "The package name must be a unique identifier for your application. " +
+ "It is typically not shown to users, but it *must* stay the same " +
+ "for the lifetime of your application; it is how multiple versions " +
+ "of the same application are considered the \"same app\". This is " +
+ "typically the reverse domain name of your organization plus one or " +
+ "more application identifiers, and it must be a valid Java package " +
+ "name.");
+ new Label(container, SWT.NONE);
+
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+
+ Label buildSdkLabel = new Label(container, SWT.NONE);
+ buildSdkLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
+ buildSdkLabel.setText("Build SDK:");
+
+ mBuildSdkCombo = new Combo(container, SWT.READ_ONLY);
+ mBuildSdkCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ IAndroidTarget[] targets = Sdk.getCurrent().getTargets();
+ mMinNameToApi = Maps.newHashMap();
+ List<String> labels = new ArrayList<String>(targets.length);
+ for (IAndroidTarget target : targets) {
+ String targetLabel = target.getFullName();
+ labels.add(targetLabel);
+ mMinNameToApi.put(targetLabel, target.getVersion().getApiLevel());
+
+ }
+ mBuildSdkCombo.setData(targets);
+ mBuildSdkCombo.setItems(labels.toArray(new String[labels.size()]));
+
+ // Pick most recent platform
+ List<String> codeNames = Lists.newArrayList();
+ int selectIndex = -1;
+ for (int i = 0, n = targets.length; i < n; i++) {
+ IAndroidTarget target = targets[i];
+ AndroidVersion version = target.getVersion();
+ int apiLevel = version.getApiLevel();
+ if (version.isPreview()) {
+ String codeName = version.getCodename();
+ String targetLabel = "API " + apiLevel + ":" + codeName;
+ codeNames.add(targetLabel);
+ mMinNameToApi.put(targetLabel, apiLevel);
+ } else if (target.isPlatform()
+ && (mValues.target == null ||
+ apiLevel > mValues.target.getVersion().getApiLevel())) {
+ mValues.target = target;
+ selectIndex = i;
+ }
+ }
+ if (selectIndex != -1) {
+ mBuildSdkCombo.select(selectIndex);
+ }
+
+ mBuildSdkCombo.addSelectionListener(this);
+ mBuildSdkCombo.addFocusListener(this);
+ mBuildTargetDec = createFieldDecoration(mBuildSdkCombo,
+ "Choose a target API to compile your code against. This is typically the most " +
+ "recent version, or the first version that supports all the APIs you want to " +
+ "directly access");
+
+
+ mChooseSdkButton = new Button(container, SWT.NONE);
+ mChooseSdkButton.setText("Choose...");
+ mChooseSdkButton.addSelectionListener(this);
+ mChooseSdkButton.setEnabled(false);
+
+ Label minSdkLabel = new Label(container, SWT.NONE);
+ minSdkLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
+ minSdkLabel.setText("Minimum Required SDK:");
+
+ mMinSdkCombo = new Combo(container, SWT.READ_ONLY);
+ mMinSdkCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+
+ labels = new ArrayList<String>(24);
+ for (String label : AdtUtils.getKnownVersions()) {
+ labels.add(label);
+ }
+ assert labels.size() >= 15; // *Known* versions to ADT, not installed/available versions
+ for (String codeName : codeNames) {
+ labels.add(codeName);
+ }
+ String[] versions = labels.toArray(new String[labels.size()]);
+ mMinSdkCombo.setItems(versions);
+ if (mValues.target != null && mValues.target.getVersion().isPreview()) {
+ mValues.minSdk = mValues.target.getVersion().getCodename();
+ mMinSdkCombo.setText(mValues.minSdk);
+ mValues.iconState.minSdk = mValues.target.getVersion().getApiLevel();
+ } else {
+ mMinSdkCombo.select(INITIAL_MIN_SDK - 1);
+ mValues.minSdk = Integer.toString(INITIAL_MIN_SDK);
+ mValues.iconState.minSdk = INITIAL_MIN_SDK;
+ }
+ mMinSdkCombo.addSelectionListener(this);
+ mMinSdkCombo.addFocusListener(this);
+ mMinSdkDec = createFieldDecoration(mMinSdkCombo,
+ "Choose the lowest version of Android that your application will support. Lower " +
+ "API levels target more devices, but means fewer features are available. By " +
+ "targeting API 8 and later, you reach approximately 93% of the market.");
+
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+
+ mCompatToggle = new Button(container, SWT.CHECK);
+ mCompatToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mCompatToggle.setSelection(true);
+ mCompatToggle.setEnabled(false);
+ mCompatToggle.setText("Include compatibility code");
+ mCompatToggle.addSelectionListener(this);
+ new Label(container, SWT.NONE);
+
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+ new Label(container, SWT.NONE);
+
+ mSkeletonToggle = new Button(container, SWT.CHECK);
+ mSkeletonToggle.setSelection(true);
+ mSkeletonToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
+ mSkeletonToggle.setText("Create Application Skeleton");
+ mSkeletonToggle.addSelectionListener(this);
+
+ Label label = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ label.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 4, 1));
+
+ mHelpIcon = new Label(container, SWT.NONE);
+ mHelpIcon.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1));
+ Image icon = IconFactory.getInstance().getIcon("quickfix");
+ mHelpIcon.setImage(icon);
+ mHelpIcon.setVisible(false);
+
+ mTipLabel = new Label(container, SWT.WRAP);
+ mTipLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
+
+ // Reserve space for 4 lines
+ mTipLabel.setText("\n\n\n\n"); //$NON-NLS-1$
+
+ // Reserve enough width to accommodate the various wizard pages up front
+ // (since they are created lazily, and we don't want the wizard to dynamically
+ // resize itself for small size adjustments as each successive page is slightly
+ // larger)
+ Label dummy = new Label(container, SWT.NONE);
+ GridData data = new GridData();
+ data.horizontalSpan = 4;
+ data.widthHint = WIZARD_PAGE_WIDTH;
+ dummy.setLayoutData(data);
+
+ }
+
+ private ControlDecoration createFieldDecoration(Control control, String description) {
+ ControlDecoration dec = new ControlDecoration(control, SWT.LEFT);
+ dec.setMarginWidth(2);
+ FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
+ getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
+ dec.setImage(errorFieldIndicator.getImage());
+ dec.setDescriptionText(description);
+ control.setToolTipText(description);
+
+ return dec;
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+
+ // DURING DEVELOPMENT ONLY
+ if (assertionsEnabled()) {
+ String uniqueProjectName = AdtUtils.getUniqueProjectName("Test", "");
+ mProjectText.setText(uniqueProjectName);
+ mPackageText.setText("test.pkg");
+ }
+
+ validatePage();
+ }
+
+ // ---- Implements ModifyListener ----
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mProjectText) {
+ mValues.projectName = mProjectText.getText();
+ mValues.projectModified = true;
+
+ try {
+ mIgnore = true;
+ if (!mValues.applicationModified) {
+ mValues.applicationName = mValues.projectName;
+ mApplicationText.setText(mValues.projectName);
+ }
+ updateActivityNames(mValues.projectName);
+ } finally {
+ mIgnore = false;
+ }
+ suggestPackage(mValues.projectName);
+ } else if (source == mPackageText) {
+ mValues.packageName = mPackageText.getText();
+ mValues.packageModified = true;
+ } else if (source == mApplicationText) {
+ mValues.applicationName = mApplicationText.getText();
+ mValues.applicationModified = true;
+
+ try {
+ mIgnore = true;
+ if (!mValues.projectModified) {
+ mValues.projectName = mValues.applicationName;
+ mProjectText.setText(mValues.applicationName);
+ }
+ updateActivityNames(mValues.applicationName);
+ } finally {
+ mIgnore = false;
+ }
+ suggestPackage(mValues.applicationName);
+ }
+
+ validatePage();
+ }
+
+ private void updateActivityNames(String name) {
+ try {
+ mIgnore = true;
+ if (!mValues.activityNameModified) {
+ mValues.activityName = extractClassName(name) + ACTIVITY_NAME_SUFFIX;
+ }
+ if (!mValues.activityTitleModified) {
+ mValues.activityTitle = name;
+ }
+ } finally {
+ mIgnore = false;
+ }
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mChooseSdkButton) {
+ // TODO: Open SDK chooser
+ assert false;
+ } else if (source == mMinSdkCombo) {
+ mValues.minSdk = getSelectedMinSdk();
+ // If higher than build target, adjust build target
+ // TODO: implement
+
+ Integer minSdk = mMinNameToApi.get(mValues.minSdk);
+ if (minSdk != null) {
+ mValues.iconState.minSdk = minSdk.intValue();
+ } else {
+ assert false : mValues.minSdk;
+ }
+
+ } else if (source == mBuildSdkCombo) {
+ mValues.target = getSelectedBuildTarget();
+
+ // If lower than min sdk target, adjust min sdk target
+ if (mValues.target.getVersion().isPreview()) {
+ mValues.minSdk = mValues.target.getVersion().getCodename();
+ try {
+ mIgnore = true;
+ mMinSdkCombo.setText(mValues.minSdk);
+ } finally {
+ mIgnore = false;
+ }
+ } else {
+ String minSdk = mValues.minSdk;
+ int buildApiLevel = mValues.target.getVersion().getApiLevel();
+ if (minSdk != null && !minSdk.isEmpty()
+ && Character.isDigit(minSdk.charAt(0))
+ && buildApiLevel < Integer.parseInt(minSdk)) {
+ mValues.minSdk = Integer.toString(buildApiLevel);
+ try {
+ mIgnore = true;
+ setSelectedMinSdk(buildApiLevel);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+ } else if (source == mSkeletonToggle) {
+ mValues.createAppSkeleton = mSkeletonToggle.getSelection();
+ mValues.createIcon = mValues.createAppSkeleton;
+ }
+
+ validatePage();
+ }
+
+ private String getSelectedMinSdk() {
+ // If you're using a preview build, such as android-JellyBean, you have
+ // to use the codename, e.g. JellyBean, as the minimum SDK as well.
+ IAndroidTarget buildTarget = getSelectedBuildTarget();
+ if (buildTarget != null && buildTarget.getVersion().isPreview()) {
+ return buildTarget.getVersion().getCodename();
+ }
+
+ // +1: First API level (at index 0) is 1
+ return Integer.toString(mMinSdkCombo.getSelectionIndex() + 1);
+ }
+
+ private void setSelectedMinSdk(int api) {
+ mMinSdkCombo.select(api - 1); // -1: First API level (at index 0) is 1
+ }
+
+ @Nullable
+ private IAndroidTarget getSelectedBuildTarget() {
+ IAndroidTarget[] targets = (IAndroidTarget[]) mBuildSdkCombo.getData();
+ int index = mBuildSdkCombo.getSelectionIndex();
+ if (index >= 0 && index < targets.length) {
+ return targets[index];
+ } else {
+ return null;
+ }
+ }
+
+ private void suggestPackage(String original) {
+ if (!mValues.packageModified) {
+ // Create default package name
+ StringBuilder sb = new StringBuilder();
+ sb.append(SAMPLE_PACKAGE_PREFIX);
+ appendPackage(sb, original);
+
+ mValues.packageName = sb.toString();
+ try {
+ mIgnore = true;
+ mPackageText.setText(mValues.packageName);
+ } finally {
+ mIgnore = false;
+ }
+ }
+ }
+
+ private static void appendPackage(StringBuilder sb, String string) {
+ for (int i = 0, n = string.length(); i < n; i++) {
+ char c = string.charAt(i);
+ if (i == 0 && Character.isJavaIdentifierStart(c)
+ || i != 0 && Character.isJavaIdentifierPart(c)) {
+ sb.append(Character.toLowerCase(c));
+ } else if ((c == '.' || c == ' ')
+ && (sb.length() > 0 && sb.charAt(sb.length() - 1) != '.')) {
+ sb.append('.');
+ } else if (c == '-') {
+ sb.append('_');
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ // ---- Implements FocusListener ----
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ Object source = e.getSource();
+ String tip = "";
+ if (source == mApplicationText) {
+ tip = mApplicationDec.getDescriptionText();
+ } else if (source == mProjectText) {
+ tip = mProjectDec.getDescriptionText();
+ } else if (source == mBuildSdkCombo) {
+ tip = mBuildTargetDec.getDescriptionText();
+ } else if (source == mMinSdkCombo) {
+ tip = mMinSdkDec.getDescriptionText();
+ } else if (source == mPackageText) {
+ tip = mPackageDec.getDescriptionText();
+ if (mPackageText.getText().startsWith(SAMPLE_PACKAGE_PREFIX)) {
+ mPackageText.setSelection(0, SAMPLE_PACKAGE_PREFIX.length());
+ }
+ }
+ mTipLabel.setText(tip);
+ mHelpIcon.setVisible(tip.length() > 0);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ mTipLabel.setText("");
+ mHelpIcon.setVisible(false);
+ }
+
+ // Validation
+
+ private void validatePage() {
+ IStatus appStatus = validateAppName();
+
+ IStatus status = appStatus;
+
+ IStatus projectStatus = validateProjectName();
+ if (projectStatus != null && (status == null || status.getSeverity() != IStatus.ERROR)) {
+ status = projectStatus;
+ }
+
+ IStatus packageStatus = validatePackageName();
+ if (packageStatus != null && (status == null || status.getSeverity() != IStatus.ERROR)) {
+ status = packageStatus;
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.target == null) {
+ status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "Select an Android build target version");
+ }
+ }
+
+ if (status == null || status.getSeverity() != IStatus.ERROR) {
+ if (mValues.minSdk == null || mValues.minSdk.isEmpty()) {
+ status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "Select a minimum SDK version");
+ }
+ }
+
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private IStatus validateAppName() {
+ String appName = mValues.applicationName;
+ IStatus status = null;
+ if (appName == null || appName.isEmpty()) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Enter an application name (shown in launcher)");
+ } else if (Character.isLowerCase(mValues.applicationName.charAt(0))) {
+ status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "The application name for most apps begins with an uppercase letter");
+ }
+
+ updateDecorator(mApplicationDec, status, true);
+
+ return status;
+ }
+
+ private IStatus validateProjectName() {
+ IStatus status = ProjectNamePage.validateProjectName(mValues.projectName);
+ updateDecorator(mProjectDec, status, true);
+
+ return status;
+ }
+
+ private IStatus validatePackageName() {
+
+ IStatus status;
+ if (mValues.packageName == null || mValues.packageName.startsWith(SAMPLE_PACKAGE_PREFIX)) {
+ status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
+ "Package name must be specified.");
+ } else {
+ status = ApplicationInfoPage.validatePackage(mValues.packageName);
+ }
+
+ updateDecorator(mPackageDec, status, true);
+
+ return status;
+ }
+
+ private void updateDecorator(ControlDecoration decorator, IStatus status, boolean hasInfo) {
+ if (hasInfo) {
+ int severity = status != null ? status.getSeverity() : IStatus.OK;
+ setDecoratorType(decorator, severity);
+ } else {
+ if (status == null || status.isOK()) {
+ decorator.hide();
+ } else {
+ decorator.show();
+ }
+ }
+ }
+
+ private void setDecoratorType(ControlDecoration decorator, int severity) {
+ String id;
+ if (severity == IStatus.ERROR) {
+ id = FieldDecorationRegistry.DEC_ERROR;
+ } else if (severity == IStatus.WARNING) {
+ id = FieldDecorationRegistry.DEC_WARNING;
+ } else {
+ id = FieldDecorationRegistry.DEC_INFORMATION;
+ }
+ FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
+ getFieldDecoration(id);
+ decorator.setImage(errorFieldIndicator.getImage());
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java
new file mode 100644
index 0000000..60f7a9e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
+
+import com.android.assetstudiolib.GraphicGenerator;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.assetstudio.AssetType;
+import com.android.ide.eclipse.adt.internal.assetstudio.ConfigureAssetSetPage;
+import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectCreator;
+import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+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.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.ui.INewWizard;
+import org.eclipse.ui.IWorkbench;
+
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.imageio.ImageIO;
+
+/**
+ * Wizard for creating new projects
+ */
+public class NewProjectWizard extends Wizard implements INewWizard {
+ private static final String ATTR_COPY_ICONS = "copyIcons"; //$NON-NLS-1$
+ static final String ATTR_TARGET_API = "targetApi"; //$NON-NLS-1$
+ static final String ATTR_MIN_API = "minApi"; //$NON-NLS-1$
+ static final String ATTR_MIN_API_LEVEL = "minApiLevel"; //$NON-NLS-1$
+ static final String ATTR_PACKAGE_NAME = "packageName"; //$NON-NLS-1$
+ static final String ATTR_APP_TITLE = "appTitle"; //$NON-NLS-1$
+ private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$
+
+ private IWorkbench mWorkbench;
+ private NewProjectPage mMainPage;
+ private AppSkeletonPage mAppSkeletonPage;
+ private NewTemplatePage mTemplatePage;
+ private ActivityPage mActivityPage;
+ private ConfigureAssetSetPage mIconPage;
+ private NewProjectWizardState mValues;
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ mWorkbench = workbench;
+
+ setWindowTitle("New Android App");
+ setHelpAvailable(false);
+ setImageDescriptor();
+
+ mValues = new NewProjectWizardState();
+ mMainPage = new NewProjectPage(mValues);
+ mAppSkeletonPage = new AppSkeletonPage(mValues);
+ mActivityPage = new ActivityPage(mValues);
+ }
+
+ /**
+ * Adds pages to this wizard.
+ */
+ @Override
+ public void addPages() {
+ addPage(mMainPage);
+ addPage(mAppSkeletonPage);
+ addPage(mActivityPage);
+ }
+
+ @Override
+ public IWizardPage getNextPage(IWizardPage page) {
+ // If you turn off creating an application, only one page
+ if (page == mMainPage) {
+ if (!mValues.createAppSkeleton) {
+ return null;
+ }
+ }
+
+ if (page == mActivityPage && mValues.createActivity) {
+ if (mTemplatePage == null) {
+ NewTemplateWizardState activityValues = mValues.activityValues;
+
+ // Initialize the *default* activity name based on what we've derived
+ // from the project name
+ activityValues.defaults.put("activityName", mValues.activityName);
+
+ // Hide those parameters that the template requires but that we don't want to
+ // ask the users about, since we will supply these values from the rest
+ // of the new project wizard.
+ Set<String> hidden = activityValues.hidden;
+ hidden.add(ATTR_PACKAGE_NAME);
+ hidden.add(ATTR_APP_TITLE);
+ hidden.add(ATTR_MIN_API);
+ hidden.add(ATTR_MIN_API_LEVEL);
+ hidden.add(ATTR_TARGET_API);
+
+ mTemplatePage = new NewTemplatePage(activityValues, false);
+ addPage(mTemplatePage);
+ }
+ return mTemplatePage;
+ }
+
+ if (page == mTemplatePage || !mValues.createActivity && page == mActivityPage) {
+ if (mValues.createIcon) {
+ if (mIconPage == null) {
+ // Bundle asset studio wizard to create the launcher icon
+ CreateAssetSetWizardState iconState = mValues.iconState;
+ iconState.type = AssetType.LAUNCHER;
+ iconState.outputName = "ic_launcher"; //$NON-NLS-1$
+ iconState.background = new RGB(0xff, 0xff, 0xff);
+ iconState.foreground = new RGB(0x33, 0xb6, 0xea);
+ iconState.shape = GraphicGenerator.Shape.CIRCLE;
+ iconState.trim = true;
+ iconState.padding = 10;
+ iconState.sourceType = CreateAssetSetWizardState.SourceType.CLIPART;
+ iconState.clipartName = "user.png"; //$NON-NLS-1$
+ mIconPage = new ConfigureAssetSetPage(iconState);
+ mIconPage.setTitle("Configure Launcher Icon");
+ addPage(mIconPage);
+ }
+ return mIconPage;
+ } else {
+ return null;
+ }
+ }
+
+ return super.getNextPage(page);
+ }
+
+ @Override
+ public boolean canFinish() {
+ if (!mValues.createAppSkeleton) {
+ return mMainPage.isPageComplete();
+ }
+
+ // Deal with lazy creation of some pages: these may not be in the page-list yet
+ // since they are constructed lazily, so consider that option here.
+ if (mValues.createIcon && (mIconPage == null || !mIconPage.isPageComplete())) {
+ return false;
+ }
+ if (mValues.createActivity && (mTemplatePage == null || !mTemplatePage.isPageComplete())) {
+ return false;
+ }
+
+ // Override super behavior (which just calls isPageComplete() on each of the pages)
+ // to special case the template and icon pages since we want to skip them if
+ // the appropriate flags are not set.
+ for (IWizardPage page : getPages()) {
+ if (page == mTemplatePage && !mValues.createActivity) {
+ continue;
+ }
+ if (page == mIconPage && !mValues.createIcon) {
+ continue;
+ }
+ if (!page.isPageComplete()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean performFinish() {
+ try {
+ IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+ String name = mValues.projectName;
+ final IProject newProject = root.getProject(name);
+
+ final TemplateHandler template = mValues.template;
+ // We'll be merging in an activity template, but don't create *~ backup files
+ // of the merged files (such as the manifest file) in that case.
+ template.setBackupMergedFiles(false);
+
+ Runnable projectPopulator = new Runnable() {
+ @Override
+ public void run() {
+ // Generate basic output skeleton
+ Map<String, Object> paramMap = new HashMap<String, Object>();
+ paramMap.put(ATTR_PACKAGE_NAME, mValues.packageName);
+ paramMap.put(ATTR_APP_TITLE, mValues.applicationName);
+ paramMap.put(ATTR_MIN_API, mValues.minSdk);
+ paramMap.put(ATTR_MIN_API_LEVEL, mValues.minSdkLevel);
+ paramMap.put(ATTR_TARGET_API, 15);
+ paramMap.put(ATTR_COPY_ICONS, !mValues.createIcon);
+
+ File outputPath = AdtUtils.getAbsolutePath(newProject).toFile();
+ template.render(outputPath, paramMap);
+
+ if (mValues.createAppSkeleton) {
+ if (mValues.createIcon) {
+ generateIcons(newProject);
+ }
+
+ if (mValues.createActivity) {
+ generateActivity(template, paramMap, outputPath);
+ }
+ }
+ }
+ };
+
+ IProgressMonitor monitor = new NullProgressMonitor();
+ NewProjectCreator.create(monitor, newProject, mValues.target, projectPopulator);
+
+ try {
+ newProject.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ // Open the primary file/files
+ final List<String> filesToOpen = template.getFilesToOpen();
+ NewTemplateWizard.openFiles(newProject, filesToOpen, mWorkbench);
+
+ return true;
+ } catch (Exception ioe) {
+ AdtPlugin.log(ioe, null);
+ return false;
+ }
+ }
+
+ /**
+ * Generate custom icons into the project based on the asset studio wizard state
+ */
+ private void generateIcons(final IProject newProject) {
+ // Generate the custom icons
+ assert mValues.createIcon;
+ Map<String, Map<String, BufferedImage>> categories =
+ mIconPage.generateImages(false);
+ for (Map<String, BufferedImage> previews : categories.values()) {
+ for (Map.Entry<String, BufferedImage> entry : previews.entrySet()) {
+ String relativePath = entry.getKey();
+ IPath dest = new Path(relativePath);
+ IFile file = newProject.getFile(dest);
+
+ // In case template already created icons (should remove that)
+ // remove them first
+ if (file.exists()) {
+ try {
+ file.delete(true, new NullProgressMonitor());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ NewXmlFileWizard.createWsParentDirectory(file.getParent());
+ BufferedImage image = entry.getValue();
+
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ ImageIO.write(image, "PNG", stream); //$NON-NLS-1$
+ byte[] bytes = stream.toByteArray();
+ InputStream is = new ByteArrayInputStream(bytes);
+ file.create(is, true /*force*/, null /*progress*/);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ try {
+ file.getParent().refreshLocal(1, new NullProgressMonitor());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+ }
+
+ /**
+ * Generate the activity: Pre-populate information about the project the
+ * activity needs but that we don't need to ask about when creating a new
+ * project
+ */
+ private void generateActivity(final TemplateHandler template,
+ Map<String, Object> paramMap, File outputPath) {
+ assert mValues.createActivity;
+ NewTemplateWizardState activityValues = mValues.activityValues;
+ Map<String, Object> parameters = activityValues.parameters;
+ parameters.put(ATTR_PACKAGE_NAME, paramMap.get(ATTR_PACKAGE_NAME));
+ parameters.put(ATTR_APP_TITLE, paramMap.get(ATTR_APP_TITLE));
+ parameters.put(ATTR_MIN_API, paramMap.get(ATTR_MIN_API));
+ parameters.put(ATTR_MIN_API_LEVEL, paramMap.get(ATTR_MIN_API_LEVEL));
+
+ parameters.put(ATTR_TARGET_API, paramMap.get(ATTR_TARGET_API));
+
+ TemplateHandler activityTemplate = activityValues.getTemplateHandler();
+ activityTemplate.setBackupMergedFiles(false);
+ activityTemplate.render(outputPath, parameters);
+ List<String> filesToOpen = activityTemplate.getFilesToOpen();
+ template.getFilesToOpen().addAll(filesToOpen);
+ }
+
+ /**
+ * 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/adt/internal/wizards/templates/NewProjectWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizardState.java
new file mode 100644
index 0000000..48a781f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizardState.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import com.android.ide.eclipse.adt.internal.assetstudio.CreateAssetSetWizardState;
+import com.android.sdklib.IAndroidTarget;
+
+import java.io.File;
+
+/**
+ * Value object which holds the current state of the wizard pages for the
+ * {@link NewProjectWizard}
+ */
+public class NewProjectWizardState {
+ private static final String TEMPLATE_NAME = "NewAndroidApplication"; //$NON-NLS-1$
+
+ /** Creates a new {@link NewProjectWizardState} */
+ public NewProjectWizardState() {
+ File inputPath = new File(TemplateHandler.getTemplatePath(TEMPLATE_NAME));
+ template = TemplateHandler.createFromPath(inputPath);
+ }
+
+ /** The template handler instantiating the project */
+ public final TemplateHandler template;
+
+ /** The name of the project */
+ public String projectName;
+
+ /** The derived name of the activity, if any */
+ public String activityName;
+
+ /** The derived title of the activity, if any */
+ public String activityTitle;
+
+ /** The application name */
+ public String applicationName;
+
+ /** The package name */
+ public String packageName;
+
+ /** Whether the project name has been edited by the user */
+ public boolean projectModified;
+
+ /** Whether the package name has been edited by the user */
+ public boolean packageModified;
+
+ /** Whether the activity name has been edited by the user */
+ public boolean activityNameModified;
+
+ /** Whether the activity title has been edited by the user */
+ public boolean activityTitleModified;
+
+ /** Whether the application name has been edited by the user */
+ public boolean applicationModified;
+
+ /** The compilation target to use for this project */
+ public IAndroidTarget target;
+
+ /** The minimum SDK API level to use */
+ public String minSdk;
+
+ /** The minimum API level, as a string (if the API is a preview release with a codename) */
+ public int minSdkLevel;
+
+ /** Whether to create an application skeleton */
+ public boolean createAppSkeleton = true;
+
+ /** Whether to create an activity (if so, the activity state is stored in
+ * {@link #activityValues}) */
+ public boolean createActivity = true;
+
+ /** Whether to target phones */
+ public boolean phone = true;
+
+ /** Whether to target tablets */
+ public boolean tablet = true;
+
+ /** Whether a custom icon should be created instead of just reusing the default (if so,
+ * the icon wizard state is stored in {@link #iconState}) */
+ public boolean createIcon = true;
+
+ // Delegated wizards
+
+ /** State for the asset studio wizard, used to create custom icons */
+ public CreateAssetSetWizardState iconState = new CreateAssetSetWizardState();
+
+ /** State for the template wizard, used to embed an activity template */
+ public NewTemplateWizardState activityValues = new NewTemplateWizardState();
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java
new file mode 100644
index 0000000..a6fdf48
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplatePage.java
@@ -0,0 +1,638 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_PADDING;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.PREVIEW_WIDTH;
+
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.ImageControl;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper;
+import com.android.ide.eclipse.adt.internal.project.ProjectChooserHelper.ProjectCombo;
+import com.android.tools.lint.detector.api.LintUtils;
+import com.google.common.collect.Lists;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.jface.fieldassist.FieldDecoration;
+import org.eclipse.jface.fieldassist.FieldDecorationRegistry;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+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.Combo;
+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.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * First wizard page in the "New Project From Template" wizard (which is parameterized
+ * via template.xml files)
+ */
+public class NewTemplatePage extends WizardPage
+ implements ModifyListener, SelectionListener, FocusListener {
+ /** The default width to use for the wizard page */
+ static final int WIZARD_PAGE_WIDTH = 600;
+
+ private final NewTemplateWizardState mValues;
+ private final boolean mChooseProject;
+ private boolean mIgnore;
+ private boolean mShown;
+ private Control mFirst;
+ // TODO: Move decorators to the Parameter objects?
+ private Map<String, ControlDecoration> mDecorations = new HashMap<String, ControlDecoration>();
+ private Label mHelpIcon;
+ private Label mTipLabel;
+ private ImageControl mPreview;
+ private Image mPreviewImage;
+ private ProjectCombo mProjectButton;
+ private List<Parameter> mParameters;
+ private StringEvaluator mEvaluator;
+
+ NewTemplatePage(NewTemplateWizardState values, boolean chooseProject) {
+ super("newTemplatePage"); //$NON-NLS-1$
+ mValues = values;
+ mChooseProject = chooseProject;
+ }
+
+ @Override
+ public void createControl(Composite parent2) {
+ Composite parent = new Composite(parent2, SWT.NULL);
+ setControl(parent);
+ GridLayout parentLayout = new GridLayout(3, false);
+ parentLayout.verticalSpacing = 0;
+ parentLayout.marginWidth = 0;
+ parentLayout.marginHeight = 0;
+ parentLayout.horizontalSpacing = 0;
+ parent.setLayout(parentLayout);
+
+ // Reserve enough width (since the panel is created lazily later)
+ Label label = new Label(parent, SWT.NONE);
+ GridData data = new GridData();
+ data.widthHint = WIZARD_PAGE_WIDTH;
+ label.setLayoutData(data);
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ private void onEnter() {
+ Composite parent = (Composite) getControl();
+
+ Control[] children = parent.getChildren();
+ if (children.length > 0) {
+ for (Control c : parent.getChildren()) {
+ c.dispose();
+ }
+ for (ControlDecoration decoration : mDecorations.values()) {
+ decoration.dispose();
+ }
+ mDecorations.clear();
+ }
+
+ Composite container = new Composite(parent, SWT.NULL);
+ container.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ GridLayout gl_container = new GridLayout(3, false);
+ gl_container.horizontalSpacing = 10;
+ container.setLayout(gl_container);
+
+ if (mChooseProject) {
+ // Project: [button]
+ String tooltip = "The Android Project where the new resource will be created.";
+ Label projectLabel = new Label(container, SWT.NONE);
+ projectLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ projectLabel.setText("Project:");
+ projectLabel.setToolTipText(tooltip);
+
+ ProjectChooserHelper helper =
+ new ProjectChooserHelper(getShell(), null /* filter */);
+ mProjectButton = new ProjectCombo(helper, container, mValues.project);
+ mProjectButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+ mProjectButton.setToolTipText(tooltip);
+ mProjectButton.addSelectionListener(this);
+
+ //Label projectSeparator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ //projectSeparator.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1));
+ }
+
+ // Add parameters
+ mFirst = null;
+ TemplateMetadata template = mValues.getTemplateHandler().getTemplate();
+ String thumb = null;
+ if (template != null) {
+ thumb = template.getThumbnailPath();
+ String title = template.getTitle();
+ if (!title.isEmpty()) {
+ setTitle(title);
+ }
+ String description = template.getDescription();
+ if (!description.isEmpty()) {
+ setDescription(description);
+ }
+
+ Map<String, String> defaults = mValues.defaults;
+ Set<String> seen = null;
+ if (LintUtils.assertionsEnabled()) {
+ seen = new HashSet<String>();
+ }
+
+ List<Parameter> parameters = template.getParameters();
+ mParameters = new ArrayList<Parameter>(parameters.size());
+ for (Parameter parameter : parameters) {
+ Parameter.Type type = parameter.type;
+
+ if (type == Parameter.Type.SEPARATOR) {
+ Label separator = new Label(container, SWT.SEPARATOR | SWT.HORIZONTAL);
+ separator.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1));
+ continue;
+ }
+
+ String id = parameter.id;
+ assert id != null && !id.isEmpty() : ATTR_ID;
+ mParameters.add(parameter);
+ String value = defaults.get(id);
+ if (value == null) {
+ value = parameter.initial;
+ }
+
+ String name = parameter.name;
+ String help = parameter.help;
+
+ // Required
+ assert name != null && !name.isEmpty() : ATTR_NAME;
+ // Ensure id's are unique:
+ assert seen != null && seen.add(id) : id;
+
+ // Skip attributes that were already provided by the surrounding
+ // context. For example, when adding into an existing project,
+ // provide the minimum SDK automatically from the project.
+ if (mValues.hidden != null && mValues.hidden.contains(id)) {
+ continue;
+ }
+
+ if (type == Parameter.Type.STRING) {
+ // TODO: Look at the constraints to add validators here
+ // TODO: If I type.equals("layout") add resource validator for layout
+ // names
+ // TODO: If I type.equals("class") make class validator
+
+ // TODO: Handle package and id better later
+ Label label = new Label(container, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ label.setText(name);
+
+ Text text = new Text(container, SWT.BORDER);
+ text.setData(parameter);
+ parameter.control = text;
+ text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+
+ if (value != null && !value.isEmpty()){
+ text.setText(value);
+ mValues.parameters.put(id, value);
+ }
+
+ text.addModifyListener(this);
+ text.addFocusListener(this);
+
+ if (mFirst == null) {
+ mFirst = text;
+ }
+
+ if (help != null && !help.isEmpty()) {
+ text.setToolTipText(help);
+ ControlDecoration decoration = createFieldDecoration(id, text, help);
+ }
+ } else if (type == Parameter.Type.BOOLEAN) {
+ Label label = new Label(container, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+
+ Button checkBox = new Button(container, SWT.CHECK);
+ checkBox.setText(name);
+ checkBox.setData(parameter);
+ parameter.control = checkBox;
+ checkBox.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+
+ if (value != null && !value.isEmpty()){
+ Boolean selected = Boolean.valueOf(value);
+ checkBox.setSelection(selected);
+ mValues.parameters.put(id, value);
+ }
+
+ checkBox.addSelectionListener(this);
+ checkBox.addFocusListener(this);
+
+ if (mFirst == null) {
+ mFirst = checkBox;
+ }
+
+ if (help != null && !help.isEmpty()) {
+ checkBox.setToolTipText(help);
+ ControlDecoration decoration = createFieldDecoration(id, checkBox, help);
+ }
+
+ } else if (type == Parameter.Type.ENUM) {
+ Label label = new Label(container, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ label.setText(name);
+
+ Combo combo = new Combo(container, SWT.READ_ONLY);
+ combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
+
+ List<Element> options = DomUtilities.getChildren(parameter.element);
+ assert options.size() > 0;
+ int selected = 0;
+ List<String> ids = Lists.newArrayList();
+ List<String> labels = Lists.newArrayList();
+ for (int i = 0, n = options.size(); i < n; i++) {
+ Element option = options.get(i);
+ String optionId = option.getAttribute(ATTR_ID);
+ assert optionId != null && !optionId.isEmpty() : ATTR_ID;
+ String isDefault = option.getAttribute(ATTR_DEFAULT);
+ if (isDefault != null && !isDefault.isEmpty() &&
+ Boolean.valueOf(isDefault)) {
+ selected = i;
+ }
+ NodeList childNodes = option.getChildNodes();
+ assert childNodes.getLength() == 1 &&
+ childNodes.item(0).getNodeType() == Node.TEXT_NODE;
+ String optionLabel = childNodes.item(0).getNodeValue().trim();
+ ids.add(optionId);
+ labels.add(optionLabel);
+ }
+ combo.setData(parameter);
+ parameter.control = combo;
+ combo.setData(ATTR_ID, ids.toArray(new String[ids.size()]));
+ assert labels.size() > 0;
+ combo.setItems(labels.toArray(new String[labels.size()]));
+ combo.select(selected);
+ mValues.parameters.put(id, ids.get(selected));
+
+ combo.addSelectionListener(this);
+ combo.addFocusListener(this);
+
+ if (mFirst == null) {
+ mFirst = combo;
+ }
+
+ if (help != null && !help.isEmpty()) {
+ combo.setToolTipText(help);
+ ControlDecoration decoration = createFieldDecoration(id, combo, help);
+ }
+ } else {
+ assert false : type;
+ }
+ }
+ }
+
+ // Preview
+ mPreview = new ImageControl(parent, SWT.NONE, null);
+ GridData gd_mImage = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1);
+ gd_mImage.widthHint = PREVIEW_WIDTH + 2 * PREVIEW_PADDING;
+ mPreview.setLayoutData(gd_mImage);
+
+ Label separator = new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData separatorData = new GridData(SWT.FILL, SWT.TOP, true, false, 3, 1);
+ separatorData.heightHint = 16;
+ separator.setLayoutData(separatorData);
+
+ // Generic help
+ mHelpIcon = new Label(parent, SWT.NONE);
+ mHelpIcon.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false, 1, 1));
+ Image icon = IconFactory.getInstance().getIcon("quickfix");
+ mHelpIcon.setImage(icon);
+ mHelpIcon.setVisible(false);
+ mTipLabel = new Label(parent, SWT.WRAP);
+ mTipLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
+
+ if (thumb != null && !thumb.isEmpty()) {
+ setPreview(thumb);
+ }
+
+ parent.layout(true, true);
+ // TODO: This is a workaround for the fact that (at least on OSX) you end up
+ // with some visual artifacts from the control decorations in the upper left corner
+ // (outside the parent widget itself) from the initial control decoration placement
+ // prior to layout. Therefore, perform a redraw. A better solution would be to
+ // delay creation of the control decorations until layout has been performed.
+ // Let's do that soon.
+ parent.getParent().redraw();
+ }
+
+ private void setPreview(String thumb) {
+ if (thumb == null) {
+ return;
+ }
+
+ Image oldImage = mPreviewImage;
+ mPreviewImage = null;
+
+ byte[] data = mValues.getTemplateHandler().readTemplateResource(thumb);
+ if (data != null) {
+ try {
+ mPreviewImage = new Image(getControl().getDisplay(),
+ new ByteArrayInputStream(data));
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ mPreview.setImage(mPreviewImage);
+ mPreview.fitToWidth(PREVIEW_WIDTH);
+
+ if (oldImage != null) {
+ oldImage.dispose();
+ }
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ if (mPreviewImage != null) {
+ mPreviewImage.dispose();
+ mPreviewImage = null;
+ }
+ }
+
+ private ControlDecoration createFieldDecoration(String id, Control control,
+ String description) {
+ ControlDecoration decoration = new ControlDecoration(control, SWT.LEFT);
+ decoration.setMarginWidth(2);
+ FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
+ getFieldDecoration(FieldDecorationRegistry.DEC_INFORMATION);
+ decoration.setImage(errorFieldIndicator.getImage());
+ decoration.setDescriptionText(description);
+ control.setToolTipText(description);
+ mDecorations.put(id, decoration);
+
+ return decoration;
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ // Force user to reach this page before hitting Finish
+ return mShown;
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ onEnter();
+ }
+
+ super.setVisible(visible);
+
+ if (mFirst != null) {
+ mFirst.setFocus();
+ }
+
+ if (visible) {
+ mShown = true;
+ }
+
+ validatePage();
+ }
+
+ /** Returns the parameter associated with the given control */
+ @Nullable
+ private Parameter getParameter(Control control) {
+ return (Parameter) control.getData();
+ }
+
+ // ---- Validation ----
+
+ private void validatePage() {
+ IStatus status = null;
+
+ // -- validate project
+ if (mChooseProject && mValues.project == null) {
+ status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID,
+ "Please select an Android project.");
+ }
+
+ for (Parameter parameter : mParameters) {
+ IInputValidator validator = parameter.getValidator(mValues.project);
+ if (validator != null) {
+ ControlDecoration decoration = mDecorations.get(parameter.id);
+ String value = parameter.value == null ? "" : parameter.value.toString();
+ String error = validator.isValid(value);
+ if (error != null) {
+ status = new Status(IStatus.WARNING, AdtPlugin.PLUGIN_ID, error);
+ if (decoration != null) {
+ updateDecorator(decoration, status, parameter.help);
+ }
+ } else if (decoration != null) {
+ updateDecorator(decoration, null, parameter.help);
+ }
+ }
+ }
+
+ setPageComplete(status == null || status.getSeverity() != IStatus.ERROR);
+ if (status != null) {
+ setMessage(status.getMessage(),
+ status.getSeverity() == IStatus.ERROR
+ ? IMessageProvider.ERROR : IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+
+ private void updateDecorator(ControlDecoration decorator, IStatus status, String help) {
+ if (help != null && !help.isEmpty()) {
+ decorator.setDescriptionText(status != null ? status.getMessage() : help);
+
+ int severity = status != null ? status.getSeverity() : IStatus.OK;
+ String id;
+ if (severity == IStatus.ERROR) {
+ id = FieldDecorationRegistry.DEC_ERROR;
+ } else if (severity == IStatus.WARNING) {
+ id = FieldDecorationRegistry.DEC_WARNING;
+ } else {
+ id = FieldDecorationRegistry.DEC_INFORMATION;
+ }
+ FieldDecoration errorFieldIndicator = FieldDecorationRegistry.getDefault().
+ getFieldDecoration(id);
+ decorator.setImage(errorFieldIndicator.getImage());
+ } else {
+ if (status == null || status.isOK()) {
+ decorator.hide();
+ } else {
+ decorator.show();
+ }
+ }
+ }
+
+ // ---- Implements ModifyListener ----
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source instanceof Text) {
+ Text text = (Text) source;
+ editParameter(text, text.getText().trim());
+ }
+
+ validatePage();
+ }
+
+ // ---- Implements SelectionListener ----
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (mIgnore) {
+ return;
+ }
+
+ Object source = e.getSource();
+ if (source == mProjectButton) {
+ mValues.project = mProjectButton.getSelectedProject();
+ } else if (source instanceof Combo) {
+ Combo combo = (Combo) source;
+ String[] optionIds = (String[]) combo.getData(ATTR_ID);
+ int index = combo.getSelectionIndex();
+ if (index != -1 && index < optionIds.length) {
+ String optionId = optionIds[index];
+ editParameter(combo, optionId);
+ TemplateMetadata template = mValues.getTemplateHandler().getTemplate();
+ setPreview(template.getThumbnailPath());
+ }
+ } else if (source instanceof Button) {
+ Button button = (Button) source;
+ editParameter(button, button.getSelection());
+
+ TemplateMetadata template = mValues.getTemplateHandler().getTemplate();
+ setPreview(template.getThumbnailPath());
+ }
+
+ validatePage();
+ }
+
+ private void editParameter(Control control, Object value) {
+ Parameter parameter = getParameter(control);
+ if (parameter != null) {
+ String id = parameter.id;
+ parameter.value = value;
+ parameter.edited = value != null && !value.toString().isEmpty();
+ mValues.parameters.put(id, value);
+
+ // Update dependent variables, if any
+ for (Parameter p : mParameters) {
+ if (p == parameter || p.suggest == null || p.edited) {
+ continue;
+ }
+ p.suggest.indexOf(id);
+ if (!p.suggest.contains(id)) {
+ continue;
+ }
+
+ try {
+ if (mEvaluator == null) {
+ mEvaluator = new StringEvaluator();
+ }
+ String updated = mEvaluator.evaluate(p.suggest, mParameters);
+ if (updated != null && !updated.equals(p.value)) {
+ p.value = updated;
+ mValues.parameters.put(p.id, updated);
+
+ // Update form widgets
+ boolean prevIgnore = mIgnore;
+ try {
+ mIgnore = true;
+ if (p.control instanceof Text) {
+ ((Text) p.control).setText(updated);
+ } else if (p.control instanceof Button) {
+ // TODO: Handle
+ } else if (p.control instanceof Combo) {
+ // TODO: Handle
+ } else if (p.control != null) {
+ assert false : p.control;
+ }
+ } finally {
+ mIgnore = prevIgnore;
+ }
+ }
+ } catch (Throwable t) {
+ // Pass: Ignore updating if something wrong happens
+ t.printStackTrace(); // during development only
+ }
+ }
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ // ---- Implements FocusListener ----
+
+ @Override
+ public void focusGained(FocusEvent e) {
+ Object source = e.getSource();
+ String tip = "";
+
+ if (source instanceof Control) {
+ Control control = (Control) source;
+ Parameter parameter = getParameter(control);
+ if (parameter != null) {
+ ControlDecoration decoration = mDecorations.get(parameter.id);
+ if (decoration != null) {
+ tip = decoration.getDescriptionText();
+ }
+ }
+ }
+
+ mTipLabel.setText(tip);
+ mHelpIcon.setVisible(tip.length() > 0);
+ }
+
+ @Override
+ public void focusLost(FocusEvent e) {
+ mTipLabel.setText("");
+ mHelpIcon.setVisible(false);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java
new file mode 100644
index 0000000..b7ad998
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizard.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_MIN_API_LEVEL;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_PACKAGE_NAME;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewProjectWizard.ATTR_TARGET_API;
+import static org.eclipse.core.resources.IResource.DEPTH_INFINITE;
+
+import com.android.annotations.NonNull;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
+import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestInfo;
+
+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.NullProgressMonitor;
+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.PartInitException;
+import org.eclipse.ui.wizards.newresource.BasicNewResourceWizard;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Template wizard which creates parameterized templates
+ */
+public class NewTemplateWizard extends Wizard implements INewWizard {
+ /** Template name and location under /templates in the plugin */
+ static final String BLANK_ACTIVITY = "BlankActivity"; //$NON-NLS-1$
+ /** Template name and location under /templates in the plugin */
+ static final String MASTER_DETAIL_FLOW = "MasterDetailFlow"; //$NON-NLS-1$
+ /** Template name and location under /templates in the plugin */
+ static final String CUSTOM_VIEW = "CustomView"; //$NON-NLS-1$
+ /** Available activity-templates (included in a list in the new project template) */
+ static final String[] ACTIVITY_TEMPLATES =
+ new String[] { BLANK_ACTIVITY, MASTER_DETAIL_FLOW };
+ private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$
+
+ protected IWorkbench mWorkbench;
+ protected NewTemplatePage mMainPage;
+ protected NewTemplateWizardState mValues;
+ private final String mTemplateName;
+
+ NewTemplateWizard(String templateName) {
+ mTemplateName = templateName;
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ mWorkbench = workbench;
+
+ setHelpAvailable(false);
+ setImageDescriptor();
+
+ mValues = new NewTemplateWizardState();
+ mValues.setTemplateName(mTemplateName);
+ hideBuiltinParameters();
+
+ List<IProject> projects = AdtUtils.getSelectedProjects(selection);
+ if (projects.size() == 1) {
+ mValues.project = projects.get(0);
+ }
+
+ mMainPage = new NewTemplatePage(mValues, true);
+ }
+
+ /**
+ * Hide those parameters that the template requires but that we don't want
+ * to ask the users about, since we can derive it from the target project
+ * the template is written into.
+ */
+ protected void hideBuiltinParameters() {
+ Set<String> hidden = mValues.hidden;
+ hidden.add(ATTR_PACKAGE_NAME);
+ hidden.add(ATTR_MIN_API);
+ hidden.add(ATTR_MIN_API_LEVEL);
+ hidden.add(ATTR_TARGET_API);
+ }
+
+ @Override
+ public void addPages() {
+ addPage(mMainPage);
+ }
+
+ @Override
+ public boolean performFinish() {
+ try {
+ Map<String, Object> parameters = mValues.parameters;
+ IProject project = mValues.project;
+
+ ManifestInfo manifest = ManifestInfo.get(project);
+ parameters.put(ATTR_PACKAGE_NAME, manifest.getPackage());
+ parameters.put(ATTR_MIN_API, manifest.getMinSdkVersion());
+ parameters.put(ATTR_MIN_API_LEVEL, manifest.getMinSdkName());
+ parameters.put(ATTR_TARGET_API, manifest.getTargetSdkVersion());
+
+ File outputPath = AdtUtils.getAbsolutePath(project).toFile();
+ TemplateHandler handler = mValues.getTemplateHandler();
+ handler.render(outputPath, parameters);
+
+ try {
+ project.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor());
+ } catch (CoreException e) {
+ AdtPlugin.log(e, null);
+ }
+
+ List<String> filesToOpen = handler.getFilesToOpen();
+ NewTemplateWizard.openFiles(project, filesToOpen, mWorkbench);
+
+ return true;
+ } catch (Exception ioe) {
+ AdtPlugin.log(ioe, null);
+ return false;
+ }
+ }
+
+ /**
+ * Returns an image descriptor for the wizard logo.
+ */
+ private void setImageDescriptor() {
+ ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE);
+ setDefaultPageImageDescriptor(desc);
+ }
+
+ /**
+ * Opens the given set of files (as relative paths within a given project
+ *
+ * @param project the project containing the paths
+ * @param relativePaths the paths to files to open
+ * @param mWorkbench the workbench to open the files in
+ */
+ public static void openFiles(
+ @NonNull final IProject project,
+ @NonNull final List<String> relativePaths,
+ @NonNull final IWorkbench mWorkbench) {
+ if (!relativePaths.isEmpty()) {
+ // This has to be delayed in order for focus handling to work correctly
+ AdtPlugin.getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ for (String path : relativePaths) {
+ IResource resource = project.findMember(path);
+ if (resource != null) {
+ if (resource instanceof IFile) {
+ try {
+ AdtPlugin.openFile((IFile) resource, null, false);
+ } catch (PartInitException e) {
+ AdtPlugin.log(e, "Failed to open %1$s", //$NON-NLS-1$
+ resource.getFullPath().toString());
+ }
+ }
+ boolean isLast = relativePaths.size() == 1 ||
+ path.equals(relativePaths.get(relativePaths.size() - 1));
+ if (isLast) {
+ BasicNewResourceWizard.selectAndReveal(resource,
+ mWorkbench.getActiveWorkbenchWindow());
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Specific New Master Detail Flow wizard
+ */
+ public static class MasterDetailWizard extends NewTemplateWizard {
+ /** Creates a new {@link MasterDetailWizard} */
+ public MasterDetailWizard() {
+ super(MASTER_DETAIL_FLOW);
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ super.init(workbench, selection);
+ setWindowTitle("New Master Detail Flow");
+ super.mMainPage.setTitle("New Master Detail Flow");
+ super.mMainPage.setDescription("Creates a new Master Detail Flow");
+ }
+ }
+
+ /**
+ * Specific New Blank Activity wizard
+ */
+ public static class NewActivityWizard extends NewTemplateWizard {
+ /** Creates a new {@link NewActivityWizard} */
+ public NewActivityWizard() {
+ super(BLANK_ACTIVITY);
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ super.init(workbench, selection);
+ setWindowTitle("New Blank Activity");
+ super.mMainPage.setTitle("New Blank Activity");
+ super.mMainPage.setDescription("Creates a new blank activity");
+ }
+ }
+
+ /**
+ * Specific New Custom View wizard
+ */
+ public static class NewCustomViewWizard extends NewTemplateWizard {
+ /** Creates a new {@link NewCustomViewWizard} */
+ public NewCustomViewWizard() {
+ super(CUSTOM_VIEW);
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ super.init(workbench, selection);
+ setWindowTitle("New Custom View");
+ super.mMainPage.setTitle("New Custom View");
+ super.mMainPage.setDescription("Creates a new custom view");
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java
new file mode 100644
index 0000000..dc75a71
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.NewTemplateWizard.BLANK_ACTIVITY;
+
+import com.android.annotations.NonNull;
+
+import org.eclipse.core.resources.IProject;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Value object which holds the current state of the wizard pages for the
+ * {@link NewTemplateWizard}
+ */
+public class NewTemplateWizardState {
+ /** Name of the template being created */
+ private String mTemplateName = BLANK_ACTIVITY;
+
+ /** Template handler responsible for instantiating templates and reading resources */
+ private TemplateHandler mTemplateHandler;
+
+ /** Configured parameters, by id */
+ public final Map<String, Object> parameters = new HashMap<String, Object>();
+
+ /** Configured defaults for the parameters, by id */
+ public final Map<String, String> defaults = new HashMap<String, String>();
+
+ /** Ids for parameters which should be hidden (because the client wizard already
+ * has information for these parameters) */
+ public final Set<String> hidden = new HashSet<String>();
+
+ /**
+ * The chosen project (which may be null if the wizard page is being
+ * embedded in the new project wizard)
+ */
+ public IProject project;
+
+ /** Name of the template being created */
+ private File mTemplateLocation;
+
+ /**
+ * Create a new state object for use by the {@link NewTemplatePage}
+ */
+ public NewTemplateWizardState() {
+ }
+
+ @NonNull
+ String getTemplateName() {
+ return mTemplateName;
+ }
+
+ /**
+ * Sets the new template name to use
+ *
+ * @param templateName the name of the template to use
+ */
+ void setTemplateName(@NonNull String templateName) {
+ if (!templateName.equals(mTemplateName)) {
+ mTemplateName = templateName;
+ mTemplateLocation = null;
+ mTemplateHandler = null;
+ }
+ }
+
+ @NonNull
+ TemplateHandler getTemplateHandler() {
+ if (mTemplateHandler == null) {
+ File inputPath;
+ if (mTemplateLocation != null) {
+ inputPath = mTemplateLocation;
+ } else {
+ inputPath = new File(TemplateHandler.getTemplatePath(mTemplateName));
+ }
+ mTemplateHandler = TemplateHandler.createFromPath(inputPath);
+ }
+
+ return mTemplateHandler;
+ }
+
+ // For template development/testing only
+ void setTemplateLocation(File file) {
+ if (!file.equals(mTemplateLocation)) {
+ mTemplateLocation = file;
+ mTemplateName = null;
+ mTemplateHandler = null;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java
new file mode 100644
index 0000000..0b8b952
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/Parameter.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_CONSTRAINTS;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DEFAULT;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_HELP;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_ID;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_SUGGEST;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.resources.ResourceNameValidator;
+import com.android.ide.eclipse.adt.internal.wizards.newproject.ApplicationInfoPage;
+import com.android.resources.ResourceFolderType;
+import com.android.resources.ResourceType;
+import com.google.common.base.Splitter;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.dialogs.IInputValidator;
+import org.eclipse.jface.fieldassist.ControlDecoration;
+import org.eclipse.swt.widgets.Control;
+import org.w3c.dom.Element;
+
+import java.util.EnumSet;
+import java.util.Locale;
+
+/**
+ * A template parameter editable and edited by the user.
+ * <p>
+ * Note that this class encapsulates not just the metadata provided by the
+ * template, but the actual editing operation of that template in the wizard: it
+ * also captures current values, a reference to the editing widget (such that
+ * related widgets can be updated when one value depends on another etc)
+ */
+class Parameter {
+ enum Type {
+ STRING,
+ BOOLEAN,
+ ENUM,
+ SEPARATOR;
+ // TODO: Numbers?
+
+ public static Type get(String name) {
+ try {
+ return Type.valueOf(name.toUpperCase(Locale.US));
+ } catch (IllegalArgumentException e) {
+ AdtPlugin.printErrorToConsole("Unexpected template type '" + name + "'");
+ AdtPlugin.printErrorToConsole("Expected one of :");
+ for (Type s : Type.values()) {
+ AdtPlugin.printErrorToConsole(" " + s.name().toLowerCase(Locale.US));
+ }
+ }
+
+ return STRING;
+ }
+ }
+
+ /**
+ * Constraints that can be applied to a parameter which helps the UI add a
+ * validator etc for user input. These are typically combined into a set
+ * of constraints via an EnumSet.
+ */
+ enum Constraint {
+ /**
+ * This value must be unique. This constraint usually only makes sense
+ * when other constraints are specified, such as {@link #LAYOUT}, which
+ * means that the parameter should designate a name that does not
+ * represent an existing layout resource name
+ */
+ UNIQUE,
+
+ /**
+ * This value must already exist. This constraint usually only makes sense
+ * when other constraints are specified, such as {@link #LAYOUT}, which
+ * means that the parameter should designate a name that already exists as
+ * a resource name.
+ */
+ EXISTS,
+
+ /** The associated value must not be empty */
+ NONEMPTY,
+
+ /** The associated value should represent an API level */
+ APILEVEL,
+
+ /** The associated value should represent a valid class name */
+ CLASS,
+
+ /** The associated value should represent a valid package name */
+ PACKAGE,
+
+ /** The associated value should represent a valid layout resource name */
+ LAYOUT,
+
+ /** The associated value should represent a valid drawable resource name */
+ DRAWABLE,
+
+ /** The associated value should represent a valid id resource name */
+ ID,
+
+ /** The associated value should represent a valid string resource name */
+ STRING;
+
+ public static Constraint get(String name) {
+ try {
+ return Constraint.valueOf(name.toUpperCase(Locale.US));
+ } catch (IllegalArgumentException e) {
+ AdtPlugin.printErrorToConsole("Unexpected template constraint '" + name + "'");
+ if (name.indexOf(',') != -1) {
+ AdtPlugin.printErrorToConsole("Use | to separate constraints");
+ } else {
+ AdtPlugin.printErrorToConsole("Expected one of :");
+ for (Constraint s : Constraint.values()) {
+ AdtPlugin.printErrorToConsole(" " + s.name().toLowerCase(Locale.US));
+ }
+ }
+ }
+
+ return NONEMPTY;
+ }
+ }
+
+ /** The type of parameter */
+ @NonNull
+ public final Type type;
+
+ /** The unique id of the parameter (not displayed to the user) */
+ @Nullable
+ public final String id;
+
+ /** The display name for this parameter */
+ @Nullable
+ public final String name;
+
+ /**
+ * The initial value for this parameter (see also {@link #suggest} for more
+ * dynamic defaults
+ */
+ @Nullable
+ public final String initial;
+
+ /**
+ * A template expression using other template parameters for producing a
+ * default value based on other edited parameters, if possible.
+ */
+ @Nullable
+ public final String suggest;
+
+ /** Help for the parameter, if any */
+ @Nullable
+ public final String help;
+
+ /** The currently edited value */
+ @Nullable
+ public Object value;
+
+ /** The control showing this value */
+ @Nullable
+ public Control control;
+
+ /** The decoration associated with the control */
+ @Nullable
+ public ControlDecoration decoration;
+
+ /** Whether the parameter has been edited */
+ public boolean edited;
+
+ /** The element defining this parameter */
+ @NonNull
+ public final Element element;
+
+ /** The constraints applicable for this parameter */
+ @NonNull
+ public final EnumSet<Constraint> constraints;
+
+ /** The validator, if any, for this field */
+ private IInputValidator mValidator;
+
+ /** True if this field has no validator */
+ private boolean mNoValidator;
+
+ /** Project associated with this validator */
+ private IProject mValidatorProject;
+
+ Parameter(@NonNull Element parameter) {
+ element = parameter;
+
+ String typeName = parameter.getAttribute(TemplateHandler.ATTR_TYPE);
+ assert typeName != null && !typeName.isEmpty() : TemplateHandler.ATTR_TYPE;
+ type = Type.get(typeName);
+
+ id = parameter.getAttribute(ATTR_ID);
+ initial = parameter.getAttribute(ATTR_DEFAULT);
+ suggest = parameter.getAttribute(ATTR_SUGGEST);
+ name = parameter.getAttribute(ATTR_NAME);
+ help = parameter.getAttribute(ATTR_HELP);
+ String constraintString = parameter.getAttribute(ATTR_CONSTRAINTS);
+ if (constraintString != null && !constraintString.isEmpty()) {
+ EnumSet<Constraint> constraintSet = null;
+ for (String s : Splitter.on('|').omitEmptyStrings().split(constraintString)) {
+ Constraint constraint = Constraint.get(s);
+ if (constraintSet == null) {
+ constraintSet = EnumSet.of(constraint);
+ } else {
+ constraintSet = EnumSet.copyOf(constraintSet);
+ constraintSet.add(constraint);
+ }
+ }
+ constraints = constraintSet;
+ } else {
+ constraints = EnumSet.noneOf(Constraint.class);
+ }
+
+ value = initial;
+ }
+
+ @Nullable
+ public IInputValidator getValidator(@Nullable IProject project) {
+ if (mNoValidator) {
+ return null;
+ }
+
+ if (project != mValidatorProject) {
+ // Force update of validators if the project changes, since the validators
+ // are often tied to project metadata (for example, the resource name validators
+ // which look for name conflicts)
+ mValidator = null;
+ mValidatorProject = project;
+ }
+
+ if (mValidator == null) {
+ if (constraints.contains(Constraint.LAYOUT)) {
+ if (project != null && constraints.contains(Constraint.UNIQUE)) {
+ mValidator = ResourceNameValidator.create(false, project, ResourceType.LAYOUT);
+ } else {
+ mValidator = ResourceNameValidator.create(false, ResourceFolderType.LAYOUT);
+ }
+ return mValidator;
+ } else if (constraints.contains(Constraint.STRING)) {
+ if (project != null && constraints.contains(Constraint.UNIQUE)) {
+ mValidator = ResourceNameValidator.create(false, project, ResourceType.STRING);
+ } else {
+ mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES);
+ }
+ return mValidator;
+ } else if (constraints.contains(Constraint.ID)) {
+ if (project != null && constraints.contains(Constraint.UNIQUE)) {
+ mValidator = ResourceNameValidator.create(false, project, ResourceType.ID);
+ } else {
+ mValidator = ResourceNameValidator.create(false, ResourceFolderType.VALUES);
+ }
+ return mValidator;
+ } else if (constraints.contains(Constraint.DRAWABLE)) {
+ if (project != null && constraints.contains(Constraint.UNIQUE)) {
+ mValidator = ResourceNameValidator.create(false, project,
+ ResourceType.DRAWABLE);
+ } else {
+ mValidator = ResourceNameValidator.create(false, ResourceFolderType.DRAWABLE);
+ }
+ return mValidator;
+ } else if (constraints.contains(Constraint.CLASS)) {
+ mValidator = new IInputValidator() {
+ @Override
+ public String isValid(String newText) {
+ IStatus status = ApplicationInfoPage.validateActivity(newText.trim());
+ if (status != null && !status.isOK()) {
+ return status.getMessage();
+ }
+
+ return null;
+ }
+ };
+ return mValidator;
+ } else if (constraints.contains(Constraint.PACKAGE)) {
+ mValidator = new IInputValidator() {
+ @Override
+ public String isValid(String newText) {
+ IStatus status = ApplicationInfoPage.validatePackage(newText.trim());
+ if (status != null && !status.isOK()) {
+ return status.getMessage();
+ }
+
+ return null;
+ }
+ };
+ return mValidator;
+ } else if (constraints.contains(Constraint.NONEMPTY)) {
+ mValidator = new IInputValidator() {
+ @Override
+ public String isValid(String newText) {
+ if (newText.trim().isEmpty()) {
+ return String.format("Enter a value for %1$s", name);
+ }
+
+ return null;
+ }
+ };
+ return mValidator;
+ }
+
+ // TODO: Handle EXISTS, APILEVEL (which is currently handled manually in the
+ // new project wizard, and never actually input by the user in a templated
+ // wizard)
+
+ mNoValidator = true;
+ }
+
+ return mValidator;
+ }
+} \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/StringEvaluator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/StringEvaluator.java
new file mode 100644
index 0000000..c1c8073
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/StringEvaluator.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import freemarker.cache.TemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A template handler which can evaluate simple strings. Used to evaluate
+ * parameter constraints during UI wizard value editing.
+ * <p>
+ * Unlike the more general {@link TemplateHandler} which is used to instantiate
+ * full template files (from resources, merging into existing files etc) this
+ * evaluator supports only simple strings, referencing only values from the
+ * provided map (and builtin functions).
+ */
+class StringEvaluator implements TemplateLoader {
+ private Map<String, Object> mParameters;
+ private Configuration mFreemarker;
+ private String mCurrentExpression;
+
+ StringEvaluator() {
+ mParameters = TemplateHandler.createBuiltinMap();
+
+ mFreemarker = new Configuration();
+ mFreemarker.setObjectWrapper(new DefaultObjectWrapper());
+ mFreemarker.setTemplateLoader(this);
+ }
+
+ /** Evaluates the given expression, with the given set of parameters */
+ @Nullable
+ String evaluate(@NonNull String expression, @NonNull List<Parameter> parameters) {
+ // Render the instruction list template.
+ for (Parameter parameter : parameters) {
+ mParameters.put(parameter.id, parameter.value);
+ }
+ try {
+ mCurrentExpression = expression;
+ Template inputsTemplate = mFreemarker.getTemplate(expression);
+ StringWriter out = new StringWriter();
+ inputsTemplate.process(mParameters, out);
+ out.flush();
+ return out.toString();
+ } catch (Exception e) {
+ if (assertionsEnabled()) {
+ AdtPlugin.log(e, null);
+ }
+ return null;
+ }
+ }
+
+ // ---- Implements TemplateLoader ----
+
+ @Override
+ public Object findTemplateSource(String name) throws IOException {
+ return mCurrentExpression;
+ }
+
+ @Override
+ public long getLastModified(Object templateSource) {
+ return 0;
+ }
+
+ @Override
+ public Reader getReader(Object templateSource, String encoding) throws IOException {
+ return new StringReader(mCurrentExpression);
+ }
+
+ @Override
+ public void closeTemplateSource(Object templateSource) throws IOException {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
new file mode 100644
index 0000000..dc0c898
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java
@@ -0,0 +1,956 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_FTL;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_JAR;
+import static com.android.ide.eclipse.adt.AdtConstants.DOT_XML;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtUtils;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+import com.android.manifmerger.ManifestMerger;
+import com.android.resources.ResourceFolderType;
+import com.android.sdklib.SdkConstants;
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Files;
+
+import freemarker.cache.TemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.swt.SWT;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import lombok.ast.libs.org.parboiled.google.collect.Lists;
+
+/**
+ * Handler which manages instantiating FreeMarker templates, copying resources
+ * and merging into existing files
+ */
+class TemplateHandler {
+ /**
+ * Special marker indicating that this path refers to the special shared
+ * resource directory rather than being somewhere inside the root/ directory
+ * where all template specific resources are found
+ */
+ private static final String VALUE_TEMPLATE_DIR = "$TEMPLATEDIR"; //$NON-NLS-1$
+
+ /**
+ * Directory within the template which contains the resources referenced
+ * from the template.xml file
+ */
+ private static final String DATA_ROOT = "root"; //$NON-NLS-1$
+
+ /**
+ * Shared resource directory containing common resources shared among
+ * multiple templates
+ */
+ private static final String RESOURCE_ROOT = "res"; //$NON-NLS-1$
+
+ /** Relative path within the ADT plugin where the templates are found */
+ static final String TEMPLATE_PREFIX = "/templates/"; //$NON-NLS-1$
+
+ /** Reserved filename which describes each template */
+ static final String TEMPLATE_XML = "template.xml"; //$NON-NLS-1$
+
+ // Various tags and attributes used in the template metadata files - template.xml,
+ // globals.xml.ftl, recipe.xml.ftl, etc.
+
+ static final String TAG_MERGE = "merge"; //$NON-NLS-1$
+ static final String TAG_EXECUTE = "execute"; //$NON-NLS-1$
+ static final String TAG_GLOBALS = "globals"; //$NON-NLS-1$
+ static final String TAG_GLOBAL = "global"; //$NON-NLS-1$
+ static final String TAG_PARAMETER = "parameter"; //$NON-NLS-1$
+ static final String TAG_COPY = "copy"; //$NON-NLS-1$
+ static final String TAG_INSTANTIATE = "instantiate"; //$NON-NLS-1$
+ static final String TAG_OPEN = "open"; //$NON-NLS-1$
+ static final String TAG_THUMB = "thumb"; //$NON-NLS-1$
+ static final String TAG_THUMBS = "thumbs"; //$NON-NLS-1$
+ static final String ATTR_VALUE = "value"; //$NON-NLS-1$
+ static final String ATTR_DEFAULT = "default"; //$NON-NLS-1$
+ static final String ATTR_SUGGEST = "suggest"; //$NON-NLS-1$
+ static final String ATTR_ID = "id"; //$NON-NLS-1$
+ static final String ATTR_NAME = "name"; //$NON-NLS-1$
+ static final String ATTR_DESCRIPTION = "description";//$NON-NLS-1$
+ static final String ATTR_TYPE = "type"; //$NON-NLS-1$
+ static final String ATTR_HELP = "help"; //$NON-NLS-1$
+ static final String ATTR_FILE = "file"; //$NON-NLS-1$
+ static final String ATTR_TO = "to"; //$NON-NLS-1$
+ static final String ATTR_FROM = "from"; //$NON-NLS-1$
+ static final String ATTR_CONSTRAINTS = "constraints";//$NON-NLS-1$
+
+ /** Default padding to apply in wizards around the thumbnail preview images */
+ static final int PREVIEW_PADDING = 10;
+
+ /** Default width to scale thumbnail preview images in wizards to */
+ static final int PREVIEW_WIDTH = 200;
+
+ /**
+ * List of files to open after the wizard has been created (these are
+ * identified by {@link #TAG_OPEN} elements in the recipe file
+ */
+ private final List<String> mOpen = Lists.newArrayList();
+
+ /** Path to the directory containing the templates */
+ private final File mRootPath;
+
+ /** The template loader which is responsible for finding (and sharing) template files */
+ private final MyTemplateLoader mLoader;
+
+ /** Agree to all file-overwrites from now on? */
+ private boolean mYesToAll = false;
+
+ /** Is writing the template cancelled? */
+ private boolean mNoToAll = false;
+
+ /**
+ * Should files that we merge contents into be backed up? If yes, will
+ * create emacs-style tilde-file backups (filename.xml~)
+ */
+ private boolean mBackupMergedFiles = true;
+
+ /**
+ * Template metadata
+ */
+ private TemplateMetadata mTemplate;
+
+ /** Creates a new {@link TemplateHandler} for the given root path */
+ static TemplateHandler createFromPath(File rootPath) {
+ return new TemplateHandler(rootPath);
+ }
+
+ private TemplateHandler(File rootPath) {
+ mRootPath = rootPath;
+ mLoader = new MyTemplateLoader();
+ mLoader.setPrefix(mRootPath.getPath());
+ }
+
+ public void setBackupMergedFiles(boolean backupMergedFiles) {
+ mBackupMergedFiles = backupMergedFiles;
+ }
+
+ public void render(final File outputPath, Map<String, Object> args) {
+ if (!outputPath.exists()) {
+ outputPath.mkdirs();
+ }
+
+ // Render the instruction list template.
+ Map<String, Object> paramMap = createParameterMap(args);
+ Configuration freemarker = new Configuration();
+ freemarker.setObjectWrapper(new DefaultObjectWrapper());
+ freemarker.setTemplateLoader(mLoader);
+
+ processVariables(freemarker, TEMPLATE_XML, paramMap, outputPath);
+ }
+
+ Map<String, Object> createParameterMap(Map<String, Object> args) {
+ final Map<String, Object> paramMap = createBuiltinMap();
+
+ // Wizard parameters supplied by user, specific to this template
+ paramMap.putAll(args);
+
+ return paramMap;
+ }
+
+ /** Data model for the templates */
+ static Map<String, Object> createBuiltinMap() {
+ // Create the data model.
+ final Map<String, Object> paramMap = new HashMap<String, Object>();
+
+ // Builtin conversion methods
+ paramMap.put("slashedPackageName", new FmSlashedPackageNameMethod()); //$NON-NLS-1$
+ paramMap.put("camelCaseToUnderscore", new FmCamelCaseToUnderscoreMethod()); //$NON-NLS-1$
+ paramMap.put("underscoreToCamelCase", new FmUnderscoreToCamelCaseMethod()); //$NON-NLS-1$
+ paramMap.put("activityToLayout", new FmActivityToLayoutMethod()); //$NON-NLS-1$
+ paramMap.put("layoutToActivity", new FmLayoutToActivityMethod()); //$NON-NLS-1$
+
+ // This should be handled better: perhaps declared "required packages" as part of the
+ // inputs? (It would be better if we could conditionally disable template based
+ // on availability)
+ Map<String, String> builtin = new HashMap<String, String>();
+ builtin.put("templatesRes", VALUE_TEMPLATE_DIR); //$NON-NLS-1$
+ paramMap.put("android", builtin); //$NON-NLS-1$
+
+ return paramMap;
+ }
+
+ @Nullable
+ public TemplateMetadata getTemplate() {
+ if (mTemplate == null) {
+ String xml = readTemplateTextResource(TEMPLATE_XML);
+ if (xml != null) {
+ Document doc = DomUtilities.parseDocument(xml, true);
+ if (doc != null && doc.getDocumentElement() != null) {
+ mTemplate = new TemplateMetadata(doc);
+ }
+ }
+ }
+
+ return mTemplate;
+ }
+
+ @Nullable
+ public static TemplateMetadata getTemplate(String templateName) {
+ String relative = getTemplatePath(templateName) + '/' +TEMPLATE_XML;
+ String xml = AdtPlugin.readEmbeddedTextFile(relative);
+ Document doc = DomUtilities.parseDocument(xml, true);
+ if (doc != null && doc.getDocumentElement() != null) {
+ return new TemplateMetadata(doc);
+ }
+
+ return null;
+ }
+
+ @NonNull
+ public static String getTemplatePath(String templateName) {
+ return TEMPLATE_PREFIX + templateName;
+ }
+
+ @NonNull
+ public String getResourcePath(String templateName) {
+ return new File(mRootPath.getPath(), templateName).getPath();
+ }
+
+ /**
+ * Load a text resource for the given relative path within the template
+ *
+ * @param relativePath relative path within the template
+ * @return the string contents of the template text file
+ */
+ @Nullable
+ public String readTemplateTextResource(@NonNull String relativePath) {
+ if (mRootPath.getPath().startsWith(TEMPLATE_PREFIX)) {
+ return AdtPlugin.readEmbeddedTextFile(getResourcePath(relativePath));
+ } else {
+ try {
+ return Files.toString(new File(mRootPath, relativePath), Charsets.UTF_8);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ return null;
+ }
+ }
+ }
+
+ @Nullable
+ public String readTemplateTextResource(@NonNull File file) {
+ if (mRootPath.getPath().startsWith(TEMPLATE_PREFIX)) {
+ return AdtPlugin.readEmbeddedTextFile(file.getPath());
+ } else {
+ try {
+ return Files.toString(file, Charsets.UTF_8);
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Reads the contents of a resource
+ *
+ * @param relativePath the path relative to the template directory
+ * @return the binary data read from the file
+ */
+ @Nullable
+ public byte[] readTemplateResource(@NonNull String relativePath) {
+ if (mRootPath.getPath().startsWith(TEMPLATE_PREFIX)) {
+ return AdtPlugin.readEmbeddedFile(getResourcePath(relativePath));
+ } else {
+ try {
+ return Files.toByteArray(new File(mRootPath, relativePath));
+ } catch (IOException e) {
+ AdtPlugin.log(e, null);
+ return null;
+ }
+ }
+ }
+
+ /** Read the given FreeMarker file and process the variable definitions */
+ private void processVariables(final Configuration freemarker,
+ String file, final Map<String, Object> paramMap, final File outputPath) {
+ try {
+ String xml;
+ if (file.endsWith(DOT_XML)) {
+ // Just read the file
+ xml = readTemplateTextResource(file);
+ } else {
+ mLoader.setTemplateFile(new File(mRootPath, file));
+ Template inputsTemplate = freemarker.getTemplate(file);
+ StringWriter out = new StringWriter();
+ inputsTemplate.process(paramMap, out);
+ out.flush();
+ xml = out.toString();
+ }
+
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+ saxParser.parse(new ByteArrayInputStream(xml.getBytes()), new DefaultHandler() {
+ @Override
+ public void startElement(String uri, String localName, String name,
+ Attributes attributes)
+ throws SAXException {
+ if (TAG_PARAMETER.equals(name)) {
+ String id = attributes.getValue(ATTR_ID);
+ if (!paramMap.containsKey(id)) {
+ String value = attributes.getValue(ATTR_DEFAULT);
+ paramMap.put(id, value);
+ }
+ } else if (TAG_GLOBAL.equals(name)) {
+ String id = attributes.getValue(ATTR_ID);
+ if (!paramMap.containsKey(id)) {
+ String value = attributes.getValue(ATTR_VALUE);
+ paramMap.put(id, value);
+ }
+ } else if (TAG_GLOBALS.equals(name)) {
+ // Handle evaluation of variables
+ String path = attributes.getValue(ATTR_FILE);
+ if (path != null) {
+ processVariables(freemarker, path, paramMap, outputPath);
+ } // else: <globals> root element
+ } else if (TAG_EXECUTE.equals(name)) {
+ String path = attributes.getValue(ATTR_FILE);
+ if (path != null) {
+ execute(freemarker, path, paramMap, outputPath);
+ }
+ } else if (!name.equals("template") && !name.equals("category")
+ && !name.equals("option")) {
+ System.err.println("WARNING: Unknown template directive " + name);
+ }
+ }
+ });
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ private boolean canOverwrite(File file) {
+ if (file.exists() && !file.isDirectory()) {
+ // Warn that the file already exists and ask the user what to do
+ if (!mYesToAll) {
+ MessageDialog dialog = new MessageDialog(null, "File Already Exists", null,
+ String.format(
+ "%1$s already exists.\nWould you like to replace it?",
+ file.getPath()),
+ MessageDialog.QUESTION, new String[] {
+ // Yes will be moved to the end because it's the default
+ "Yes", "No", "Cancel", "Yes to All"
+ }, 0);
+ int result = dialog.open();
+ switch (result) {
+ case 0:
+ // Yes
+ break;
+ case 3:
+ // Yes to all
+ mYesToAll = true;
+ break;
+ case 1:
+ // No
+ return false;
+ case SWT.DEFAULT:
+ case 2:
+ // Cancel
+ mNoToAll = true;
+ return false;
+ }
+ }
+
+ if (mBackupMergedFiles) {
+ return makeBackup(file);
+ } else {
+ return file.delete();
+ }
+ }
+
+ return true;
+ }
+
+ /** Executes the given recipe file: copying, merging, instantiating, opening files etc */
+ private void execute(
+ final Configuration freemarker,
+ String file,
+ final Map<String, Object> paramMap,
+ final File outputPath) {
+ try {
+ mLoader.setTemplateFile(new File(mRootPath, file));
+ Template freemarkerTemplate = freemarker.getTemplate(file);
+
+ StringWriter out = new StringWriter();
+ freemarkerTemplate.process(paramMap, out);
+ out.flush();
+ String xml = out.toString();
+
+ // Parse and execute the resulting instruction list.
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+ SAXParser saxParser = factory.newSAXParser();
+
+ saxParser.parse(new ByteArrayInputStream(xml.getBytes()),
+ new DefaultHandler() {
+ @Override
+ public void startElement(String uri, String localName, String name,
+ Attributes attributes)
+ throws SAXException {
+ if (mNoToAll) {
+ return;
+ }
+
+ try {
+ boolean instantiate = TAG_INSTANTIATE.equals(name);
+ if (TAG_COPY.equals(name) || instantiate) {
+ String fromPath = attributes.getValue(ATTR_FROM);
+ String toPath = attributes.getValue(ATTR_TO);
+ if (toPath == null || toPath.isEmpty()) {
+ toPath = attributes.getValue(ATTR_FROM);
+ toPath = AdtUtils.stripSuffix(toPath, DOT_FTL);
+ }
+ File to = new File(outputPath, toPath);
+ if (instantiate) {
+ instantiate(freemarker, paramMap, fromPath, to);
+ } else {
+ copyBundledResource(fromPath, to);
+ }
+ } else if (TAG_MERGE.equals(name)) {
+ String fromPath = attributes.getValue(ATTR_FROM);
+ String toPath = attributes.getValue(ATTR_TO);
+ if (toPath == null || toPath.isEmpty()) {
+ toPath = attributes.getValue(ATTR_FROM);
+ toPath = AdtUtils.stripSuffix(toPath, DOT_FTL);
+ }
+ // Resources in template.xml are located within root/
+ File to = new File(outputPath, toPath);
+ merge(freemarker, paramMap, fromPath, to);
+ } else if (name.equals(TAG_OPEN)) {
+ // The relative path here is within the output directory:
+ String relativePath = attributes.getValue(ATTR_FILE);
+ if (relativePath != null && !relativePath.isEmpty()) {
+ mOpen.add(relativePath);
+ }
+ } else if (!name.equals("recipe")) { //$NON-NLS-1$
+ System.err.println("WARNING: Unknown template directive " + name);
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+
+ private File getFullPath(String fromPath) {
+ if (fromPath.startsWith(VALUE_TEMPLATE_DIR)) {
+ return new File(mRootPath.getParentFile(), RESOURCE_ROOT
+ + fromPath.substring(VALUE_TEMPLATE_DIR.length()));
+ }
+ return new File(mRootPath, DATA_ROOT + File.separator + fromPath);
+ }
+
+ private void merge(
+ @NonNull final Configuration freemarker,
+ @NonNull final Map<String, Object> paramMap,
+ @NonNull String relativeFrom,
+ @NonNull File to) throws IOException, TemplateException {
+ if (!to.exists()) {
+ // The target file doesn't exist: don't merge, just copy
+ boolean instantiate = relativeFrom.endsWith(DOT_FTL);
+ if (instantiate) {
+ instantiate(freemarker, paramMap, relativeFrom, to);
+ } else {
+ copyBundledResource(relativeFrom, to);
+ }
+ return;
+ }
+
+ if (!to.getPath().endsWith(DOT_XML)) {
+ throw new RuntimeException("Only XML files can be merged at this point: " + to);
+ }
+
+ String xml = null;
+ File from = getFullPath(relativeFrom);
+ if (relativeFrom.endsWith(DOT_FTL)) {
+ // Perform template substitution of the template prior to merging
+ mLoader.setTemplateFile(from);
+ Template template = freemarker.getTemplate(from.getName());
+ Writer out = new StringWriter();
+ template.process(paramMap, out);
+ out.flush();
+ xml = out.toString();
+ } else {
+ xml = readTemplateTextResource(from);
+ }
+
+ String currentXml = Files.toString(to, Charsets.UTF_8);
+ Document currentManifest = DomUtilities.parseStructuredDocument(currentXml);
+ Document fragment = DomUtilities.parseStructuredDocument(xml);
+
+ XmlFormatStyle formatStyle = XmlFormatStyle.MANIFEST;
+ boolean modified;
+ boolean ok;
+ if (to.getName().equals(SdkConstants.FN_ANDROID_MANIFEST_XML)) {
+ modified = ok = mergeManifest(currentManifest, fragment);
+ } else {
+ // Merge plain XML files
+ ResourceFolderType folderType =
+ ResourceFolderType.getFolderType(to.getParentFile().getName());
+ if (folderType != null) {
+ formatStyle = XmlFormatStyle.getForFolderType(folderType);
+ } else {
+ formatStyle = XmlFormatStyle.FILE;
+ }
+
+ modified = mergeResourceFile(currentManifest, fragment, folderType, paramMap);
+ ok = true;
+ }
+
+ // Finally write out the merged file (formatting etc)
+ if (ok) {
+ if (modified) {
+ XmlPrettyPrinter printer = new XmlPrettyPrinter(
+ XmlFormatPreferences.create(), formatStyle, null);
+ StringBuilder sb = new StringBuilder(2 );
+ printer.prettyPrint(-1, currentManifest, null, null, sb, false /*openTagOnly*/);
+ String contents = sb.toString();
+ writeString(to, contents, false);
+ }
+ } else {
+ // Just insert into file along with comment, using the "standard" conflict
+ // syntax that many tools and editors recognize.
+ String sep = AdtUtils.getLineSeparator();
+ String contents =
+ "<<<<<<< Original" + sep
+ + currentXml + sep
+ + "=======" + sep
+ + xml
+ + ">>>>>>> Added" + sep;
+ writeString(to, contents, false);
+ }
+ }
+
+ /**
+ * Writes the given contents into the given file (unless that file already
+ * contains the given contents), and if the file exists ask user whether
+ * the file should be overwritten (unless the user has already answered "Yes to All"
+ * or "Cancel" (no to all).
+ */
+ private void writeString(File destination, String contents, boolean confirmOverwrite)
+ throws IOException {
+ // First make sure that the files aren't identical, in which case we can do
+ // nothing (and not involve user)
+ if (!(destination.exists()
+ && isIdentical(contents.getBytes(Charsets.UTF_8), destination))) {
+ // And if the file does exist (and is now known to be different),
+ // ask user whether it should be replaced (canOverwrite will also
+ // return true if the file doesn't exist)
+ if (confirmOverwrite) {
+ if (!canOverwrite(destination)) {
+ return;
+ }
+ } else {
+ if (destination.exists()) {
+ if (mBackupMergedFiles) {
+ makeBackup(destination);
+ } else {
+ destination.delete();
+ }
+ }
+ }
+ Files.write(contents, destination, Charsets.UTF_8);
+ }
+ }
+
+ /**
+ * Writes the given contents into the given file (unless that file already
+ * contains the given contents), and if the file exists ask user whether
+ * the file should be overwritten (unless the user has already answered "Yes to All"
+ * or "Cancel" (no to all).
+ */
+ private void writeBytes(File destination, byte[] contents, boolean confirmOverwrite)
+ throws IOException {
+ // First make sure that the files aren't identical, in which case we can do
+ // nothing (and not involve user)
+ if (!(destination.exists() && isIdentical(contents, destination))) {
+ // And if the file does exist (and is now known to be different),
+ // ask user whether it should be replaced (canOverwrite will also
+ // return true if the file doesn't exist)
+ if (confirmOverwrite) {
+ if (!canOverwrite(destination)) {
+ return;
+ }
+ } else {
+ if (destination.exists()) {
+ if (mBackupMergedFiles) {
+ makeBackup(destination);
+ } else {
+ destination.delete();
+ }
+ }
+ }
+ Files.write(contents, destination);
+ }
+ }
+
+ /** Merges the given resource file contents into the given resource file
+ * @param paramMap */
+ private boolean mergeResourceFile(Document currentManifest, Document fragment,
+ ResourceFolderType folderType, Map<String, Object> paramMap) {
+ boolean modified = false;
+
+ // For layouts for example, I want to *append* inside the root all the
+ // contents of the new file.
+ // But for resources for example, I want to combine elements which specify
+ // the same name or id attribute.
+ // For elements like manifest files we need to insert stuff at the right
+ // location in a nested way (activities in the application element etc)
+ // but that doesn't happen for the other file types.
+ Element root = fragment.getDocumentElement();
+ NodeList children = root.getChildNodes();
+ List<Node> nodes = new ArrayList<Node>(children.getLength());
+ for (int i = children.getLength() - 1; i >= 0; i--) {
+ Node child = children.item(i);
+ nodes.add(child);
+ root.removeChild(child);
+ }
+
+ root = currentManifest.getDocumentElement();
+
+ if (folderType == ResourceFolderType.VALUES) {
+ // Try to merge items of the same name
+ Map<String, Node> old = new HashMap<String, Node>();
+ NodeList newSiblings = root.getChildNodes();
+ for (int i = newSiblings.getLength() - 1; i >= 0; i--) {
+ Node child = newSiblings.item(i);
+ if (child.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) child;
+ String name = getResourceId(element);
+ if (name != null) {
+ old.put(name, element);
+ }
+ }
+ }
+
+ for (Node node : nodes) {
+ if (node.getNodeType() == Node.ELEMENT_NODE) {
+ Element element = (Element) node;
+ String name = getResourceId(element);
+ Node replace = name != null ? old.get(name) : null;
+ if (replace != null) {
+ // There is an existing item with the same id: just replace it
+ // ACTUALLY -- let's NOT change it.
+ // Let's say you've used the activity wizard once, and it
+ // emits some configuration parameter as a resource that
+ // it depends on, say "padding". Then the user goes and
+ // tweaks the padding to some other number.
+ // Now running the wizard a *second* time for some new activity,
+ // we should NOT go and set the value back to the template's
+ // default!
+ //root.replaceChild(node, replace);
+
+ // ... ON THE OTHER HAND... What if it's a parameter class
+ // (where the template rewrites a common attribute). Here it's
+ // really confusing if the new parameter is not set. This is
+ // really an error in the template, since we shouldn't have conflicts
+ // like that, but we need to do something to help track this down.
+ AdtPlugin.log(null,
+ "Warning: Ignoring name conflict in resource file for name %1$s",
+ name);
+ } else {
+ root.appendChild(node);
+ modified = true;
+ }
+ }
+ }
+ } else {
+ // In other file types, such as layouts, just append all the new content
+ // at the end.
+ for (Node node : nodes) {
+ root.appendChild(node);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ /** Merges the given manifest fragment into the given manifest file */
+ private boolean mergeManifest(Document currentManifest, Document fragment) {
+ ManifestMerger merger = new ManifestMerger(AdtPlugin.getDefault());
+ return currentManifest != null && fragment != null
+ && merger.process(currentManifest, fragment);
+ }
+
+ /**
+ * Makes a backup of the given file, if it exists, by renaming it to name~
+ * (and removing an old name~ file if it exists)
+ */
+ private static boolean makeBackup(File file) {
+ if (!file.exists()) {
+ return true;
+ }
+ if (file.isDirectory()) {
+ return false;
+ }
+
+ File backupFile = new File(file.getParentFile(), file.getName() + '~');
+ if (backupFile.exists()) {
+ backupFile.delete();
+ }
+ return file.renameTo(backupFile);
+ }
+
+ private static String getResourceId(Element element) {
+ String name = element.getAttribute(ATTR_NAME);
+ if (name == null) {
+ name = element.getAttribute(ATTR_ID);
+ }
+
+ return name;
+ }
+
+ /** Instantiates the given template file into the given output file */
+ private void instantiate(
+ @NonNull final Configuration freemarker,
+ @NonNull final Map<String, Object> paramMap,
+ @NonNull String relativeFrom,
+ @NonNull File to) throws IOException, TemplateException {
+ File parentFile = to.getParentFile();
+ if (!parentFile.exists()) {
+ parentFile.mkdirs();
+ }
+
+ // For now, treat extension-less files as directories... this isn't quite right
+ // so I should refine this! Maybe with a unique attribute in the template file?
+ boolean isDirectory = relativeFrom.indexOf('.') == -1;
+ if (isDirectory) {
+ // It's a directory
+ copyBundledResource(relativeFrom, to);
+ } else {
+ File from = getFullPath(relativeFrom);
+ mLoader.setTemplateFile(from);
+ Template template = freemarker.getTemplate(from.getName());
+ Writer out = new StringWriter(1024);
+ template.process(paramMap, out);
+ out.flush();
+ String contents = out.toString();
+
+ if (relativeFrom.endsWith(DOT_XML)) {
+ XmlFormatStyle formatStyle = XmlFormatStyle.getForFile(new Path(to.getPath()));
+ XmlFormatPreferences prefs = XmlFormatPreferences.create();
+ contents = XmlPrettyPrinter.prettyPrint(contents, prefs, formatStyle, null);
+ }
+
+ writeString(to, contents, true);
+ }
+ }
+
+ /**
+ * Returns the list of files to open when the template has been created
+ *
+ * @return the list of files to open
+ */
+ @NonNull
+ public List<String> getFilesToOpen() {
+ return mOpen;
+ }
+
+ /** Copy a bundled resource (part of the plugin .jar file) into the given file system path */
+ private final void copyBundledResource(String relativeFrom, File output) throws IOException {
+ File from = getFullPath(relativeFrom);
+
+ // Local file copy? (Only used for the template-development wizard)
+ if (!mRootPath.getPath().startsWith(TEMPLATE_PREFIX)) {
+ copy(from, output);
+ return;
+ }
+
+ String resourcePath = from.getPath();
+ CodeSource source = TemplateHandler.class.getProtectionDomain().getCodeSource();
+ if (source != null) {
+ URL location = source.getLocation();
+ try {
+ URI locationUri = location.toURI();
+ File locationFile = new File(locationUri);
+ if (!locationUri.getPath().endsWith(DOT_JAR)) {
+ // Plain file; e.g. when running out of Eclipse plugin in
+ // Eclipse; it uses the bin/ folder instead of running out of a jar
+ File sourceFile = new File(locationFile, resourcePath);
+ copy(sourceFile, output);
+ return;
+ }
+
+ // Copy out of jar file
+ JarFile jarFile = new JarFile(locationFile);
+ int chopIndex = resourcePath.length() + 1;
+ for (final Enumeration<JarEntry> e = jarFile.entries(); e.hasMoreElements();) {
+ final JarEntry entry = e.nextElement();
+ if (entry.getName().startsWith(resourcePath)) {
+ final String filename = entry.getName().substring(chopIndex);
+ assert entry.getName().charAt(resourcePath.length()) == '/';
+ final File file = new File(output, filename);
+ if (!entry.isDirectory()) {
+ // Copy stream
+ InputStream in = jarFile.getInputStream(entry);
+ try {
+ byte[] data = ByteStreams.toByteArray(in);
+ writeBytes(output, data, true);
+ } finally {
+ in.close();
+ }
+ } else {
+ // Create directory
+ if (!file.exists() && !file.mkdirs()) {
+ throw new IOException("Could not create directory " + file);
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ AdtPlugin.log(e, null);
+ }
+ }
+ }
+
+ /** Returns true if the given file contains the given bytes */
+ private static boolean isIdentical(@Nullable byte[] data, @NonNull File dest)
+ throws IOException {
+ assert dest.isFile();
+ byte[] existing = Files.toByteArray(dest);
+ return Arrays.equals(existing, data);
+ }
+
+ /**
+ * Copies the given source file into the given destination file (where the
+ * source is allowed to be a directory, in which case the whole directory is
+ * copied recursively)
+ */
+ private void copy(File src, File dest) throws IOException {
+ if (src.isDirectory()){
+ if (!dest.exists() && !dest.mkdirs()) {
+ throw new IOException("Could not create directory " + dest);
+ }
+ File[] children = src.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ copy(child, new File(dest, child.getName()));
+ }
+ }
+ } else {
+ if (dest.exists() && isIdentical(Files.toByteArray(src), dest)) {
+ return;
+ }
+ if (!canOverwrite(dest)) {
+ return;
+ }
+
+ File parent = dest.getParentFile();
+ if (parent != null && !parent.exists()) {
+ parent.mkdirs();
+ }
+ Files.copy(src, dest);
+ }
+ }
+
+ /**
+ * A custom {@link TemplateLoader} which locates and provides templates
+ * within the plugin .jar file
+ */
+ private static final class MyTemplateLoader implements TemplateLoader {
+ private String mPrefix;
+
+ public void setPrefix(String prefix) {
+ mPrefix = prefix;
+ }
+
+ public void setTemplateFile(File file) {
+ setTemplateParent(file.getParentFile());
+ }
+
+ public void setTemplateParent(File parent) {
+ mPrefix = parent.getPath();
+ }
+
+ @Override
+ public Reader getReader(Object templateSource, String encoding) throws IOException {
+ URL url = (URL) templateSource;
+ return new InputStreamReader(url.openStream(), encoding);
+ }
+
+ @Override
+ public long getLastModified(Object templateSource) {
+ return 0;
+ }
+
+ @Override
+ public Object findTemplateSource(String name) throws IOException {
+ String path = mPrefix != null ? mPrefix + '/' + name : name;
+ URL resource = TemplateHandler.class.getResource(path);
+
+ // Support for local files during template development
+ if (resource == null && mPrefix != null && !mPrefix.startsWith(TEMPLATE_PREFIX)) {
+ File file = new File(path);
+ if (file.exists()) {
+ return file.toURI().toURL();
+ }
+ }
+
+ return resource;
+ }
+
+ @Override
+ public void closeTemplateSource(Object templateSource) throws IOException {
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java
new file mode 100644
index 0000000..eac818a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateMetadata.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_DESCRIPTION;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.ATTR_NAME;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_PARAMETER;
+import static com.android.ide.eclipse.adt.internal.wizards.templates.TemplateHandler.TAG_THUMB;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.eclipse.adt.AdtPlugin;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** An ADT template along with metadata */
+class TemplateMetadata {
+ private final Document mDocument;
+ private final List<Parameter> mParameters;
+ private final Map<String, Parameter> mParameterMap;
+
+ TemplateMetadata(@NonNull Document document) {
+ mDocument = document;
+
+ NodeList parameters = mDocument.getElementsByTagName(TAG_PARAMETER);
+ mParameters = new ArrayList<Parameter>(parameters.getLength());
+ mParameterMap = new HashMap<String, Parameter>(parameters.getLength());
+ for (int index = 0, max = parameters.getLength(); index < max; index++) {
+ Element element = (Element) parameters.item(index);
+ Parameter parameter = new Parameter(element);
+ mParameters.add(parameter);
+ if (parameter.id != null) {
+ mParameterMap.put(parameter.id, parameter);
+ }
+ }
+ }
+
+ @Nullable
+ String getTitle() {
+ String name = mDocument.getDocumentElement().getAttribute(ATTR_NAME);
+ if (name != null && !name.isEmpty()) {
+ return name;
+ }
+
+ return null;
+ }
+
+ @Nullable
+ String getDescription() {
+ String description = mDocument.getDocumentElement().getAttribute(ATTR_DESCRIPTION);
+ if (description != null && !description.isEmpty()) {
+ return description;
+ }
+
+ return null;
+ }
+
+ @Nullable
+ String getThumbnailPath() {
+ // Apply selector logic. Pick the thumb first thumb that satisfies the largest number
+ // of conditions.
+ NodeList thumbs = mDocument.getElementsByTagName(TAG_THUMB);
+ if (thumbs.getLength() == 0) {
+ return null;
+ }
+
+
+ int bestMatchCount = 0;
+ Element bestMatch = null;
+
+ for (int i = 0, n = thumbs.getLength(); i < n; i++) {
+ Element thumb = (Element) thumbs.item(i);
+
+ NamedNodeMap attributes = thumb.getAttributes();
+ if (bestMatch == null && attributes.getLength() == 0) {
+ bestMatch = thumb;
+ } else if (attributes.getLength() <= bestMatchCount) {
+ // Already have a match with this number of attributes, no point checking
+ continue;
+ } else {
+ boolean match = true;
+ for (int j = 0, max = attributes.getLength(); j < max; j++) {
+ Attr attribute = (Attr) attributes.item(j);
+ Parameter parameter = mParameterMap.get(attribute.getName());
+ if (parameter == null) {
+ AdtPlugin.log(null, "Unexpected parameter in template thumbnail: %1$s",
+ attribute.getName());
+ continue;
+ }
+ String thumbNailValue = attribute.getValue();
+ String editedValue = parameter.value != null ? parameter.value.toString() : "";
+ if (!thumbNailValue.equals(editedValue)) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ bestMatch = thumb;
+ bestMatchCount = attributes.getLength();
+ }
+ }
+ }
+
+ if (bestMatch != null) {
+ NodeList children = bestMatch.getChildNodes();
+ for (int i = 0, n = children.getLength(); i < n; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() == Node.TEXT_NODE) {
+ return child.getNodeValue().trim();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns the list of available parameters */
+ @NonNull
+ List<Parameter> getParameters() {
+ return mParameters;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestPage.java
new file mode 100644
index 0000000..90aa28a
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestPage.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+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.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.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+
+/** For template developers: Test local template directory */
+public class TemplateTestPage extends WizardPage
+ implements SelectionListener, ModifyListener {
+ private Text mLocation;
+ private Button mButton;
+ private static String sLocation; // Persist between repeated invocations
+ private Button mProjectToggle;
+ private File mTemplate;
+
+ TemplateTestPage() {
+ super("testWizardPage"); //$NON-NLS-1$
+ setTitle("Wizard Tester");
+ setDescription("Test a new template");
+ }
+
+ @SuppressWarnings("unused") // SWT constructors have side effects and aren't unused
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+ setControl(container);
+ container.setLayout(new GridLayout(3, false));
+
+ Label label = new Label(container, SWT.NONE);
+ label.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ label.setText("Template Location:");
+
+ mLocation = new Text(container, SWT.BORDER);
+ GridData gd_mLocation = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1);
+ gd_mLocation.widthHint = 400;
+ mLocation.setLayoutData(gd_mLocation);
+ if (sLocation != null) {
+ mLocation.setText(sLocation);
+ }
+ mLocation.addModifyListener(this);
+
+ mButton = new Button(container, SWT.FLAT);
+ mButton.setText("...");
+
+ mProjectToggle = new Button(container, SWT.CHECK);
+ mProjectToggle.setEnabled(false);
+ mProjectToggle.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+ mProjectToggle.setText("Full project template");
+ new Label(container, SWT.NONE);
+ mButton.addSelectionListener(this);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ validatePage();
+ }
+
+ private boolean validatePage() {
+ String error = null;
+
+ String path = mLocation.getText().trim();
+ if (path == null || path.length() == 0) {
+ error = "Select a template directory";
+ mTemplate = null;
+ } else {
+ mTemplate = new File(path);
+ if (!mTemplate.exists()) {
+ error = String.format("%1$s does not exist", path);
+ } else {
+ // Preserve across wizard sessions
+ sLocation = path;
+
+ if (mTemplate.isDirectory()) {
+ if (!new File(mTemplate, TemplateHandler.TEMPLATE_XML).exists()) {
+ error = String.format("Not a template: missing template.xml file in %1$s ",
+ path);
+ }
+ } else {
+ if (mTemplate.getName().equals(TemplateHandler.TEMPLATE_XML)) {
+ mTemplate = mTemplate.getParentFile();
+ } else {
+ error = String.format("Select a directory containing a template");
+ }
+ }
+ }
+ }
+
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+
+ return error == null;
+ }
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ validatePage();
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (e.getSource() == mButton) {
+ DirectoryDialog dialog = new DirectoryDialog(mButton.getShell(), SWT.OPEN);
+ String path = mLocation.getText().trim();
+ if (path.length() > 0) {
+ dialog.setFilterPath(path);
+ }
+ String file = dialog.open();
+ if (file != null) {
+ mLocation.setText(file);
+ }
+ }
+
+ validatePage();
+ }
+
+
+
+ File getLocation() {
+ return mTemplate;
+ }
+
+ boolean isProjectTemplate() {
+ return mProjectToggle.getSelection();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestWizard.java
new file mode 100644
index 0000000..bca50c6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateTestWizard.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.wizards.templates;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jface.viewers.IStructuredSelection;
+import org.eclipse.jface.wizard.IWizardPage;
+import org.eclipse.ui.IWorkbench;
+
+import java.io.File;
+
+/**
+ * Template wizard which creates parameterized templates
+ */
+public class TemplateTestWizard extends NewTemplateWizard {
+ private TemplateTestPage mSelectionPage;
+ private IProject mProject;
+
+ /** Creates a new wizard for testing template definitions in a local directory */
+ public TemplateTestWizard() {
+ super("");
+ }
+
+ @Override
+ public void init(IWorkbench workbench, IStructuredSelection selection) {
+ super.init(workbench, selection);
+ if (mValues != null) {
+ mProject = mValues.project;
+ }
+
+ mMainPage = null;
+ mValues = null;
+
+ mSelectionPage = new TemplateTestPage();
+ }
+
+ @Override
+ public void addPages() {
+ addPage(mSelectionPage);
+ }
+
+ @Override
+ public IWizardPage getNextPage(IWizardPage page) {
+ if (page == mSelectionPage) {
+ File file = mSelectionPage.getLocation();
+ if (file != null && file.exists()) {
+ if (mValues == null) {
+ mValues = new NewTemplateWizardState();
+ mValues.setTemplateLocation(file);
+ mValues.project = mProject;
+ hideBuiltinParameters();
+
+ mMainPage = new NewTemplatePage(mValues, true);
+ addPage(mMainPage);
+ }
+
+ return mMainPage;
+ }
+ }
+
+ return super.getNextPage(page);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/globals.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/globals.xml.ftl
new file mode 100644
index 0000000..3a26abd
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/globals.xml.ftl
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="menuName" value="${layoutName}" />
+</globals>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/recipe.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/recipe.xml.ftl
new file mode 100644
index 0000000..2ce72db
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/recipe.xml.ftl
@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="AndroidManifest.xml.ftl" />
+
+ <copy from="res/drawable-hdpi" />
+ <copy from="res/drawable-mdpi" />
+ <copy from="res/drawable-xhdpi" />
+
+ <copy from="res/menu/main.xml"
+ to="res/menu/${menuName}.xml" />
+
+ <merge from="res/values/dimens.xml" />
+ <merge from="res/values-large/dimens.xml" />
+ <merge from="res/values/strings.xml.ftl" />
+
+ <!-- Decide whether or not to add the support library -->
+ <#if navType != "none">
+ <copy from="${android.templatesRes}/android-support-v4.jar.bin"
+ to="libs/android-support-v4.jar" />
+ </#if>
+
+ <!-- Decide what kind of layout to add (viewpager or not) -->
+ <#if navType?contains("pager")>
+ <instantiate from="res/layout/activity_pager.xml.ftl"
+ to="res/layout/${layoutName}.xml" />
+
+ <#elseif navType == "tabs" || navType == "dropdown">
+ <instantiate from="res/layout/activity_fragment_container.xml"
+ to="res/layout/${layoutName}.xml" />
+
+ <#else>
+ <instantiate from="res/layout/activity_simple.xml"
+ to="res/layout/${layoutName}.xml" />
+ </#if>
+
+ <!-- Decide which activity code to add -->
+ <#if navType == "none">
+ <instantiate from="src/app_package/SimpleActivity.java.ftl"
+ to="${srcOut}/${activityClass}.java" />
+
+ <#elseif navType == "tabs_pager" || navType == "pager_strip">
+ <instantiate from="src/app_package/TabsAndPagerActivity.java.ftl"
+ to="${srcOut}/${activityClass}.java" />
+
+ <#elseif navType == "tabs">
+ <instantiate from="src/app_package/TabsActivity.java.ftl"
+ to="${srcOut}/${activityClass}.java" />
+
+ <#elseif navType == "dropdown">
+ <instantiate from="src/app_package/DropdownActivity.java.ftl"
+ to="${srcOut}/${activityClass}.java" />
+
+ </#if>
+
+ <open file="res/layout/${layoutName}.xml" />
+</recipe>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/AndroidManifest.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..ffcce79
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/AndroidManifest.xml.ftl
@@ -0,0 +1,15 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
+
+ <application>
+
+ <activity android:name=".${activityClass}"
+ android:label="@string/activity_name">
+ <intent-filter android:label="@string/activity_name">
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ </application>
+
+</manifest>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-hdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..67de12d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-mdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..134d549
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-xhdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..d699c6b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_fragment_container.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_fragment_container.xml
new file mode 100644
index 0000000..3128b5f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_fragment_container.xml
@@ -0,0 +1,6 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".${activityClass}" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_pager.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_pager.xml.ftl
new file mode 100644
index 0000000..c8f1604
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_pager.xml.ftl
@@ -0,0 +1,22 @@
+<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".${activityClass}"<#if navType != "pager_strip"> /><#else>>
+
+ <!--
+ This title strip will display the currently visible page title, as well as the page
+ titles for adjacent pages.
+ -->
+ <android.support.v4.view.PagerTitleStrip android:id="@+id/pager_title_strip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:background="#33b5e5"
+ android:textColor="#fff"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp" />
+
+</android.support.v4.view.ViewPager>
+</#if>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_simple.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_simple.xml
new file mode 100644
index 0000000..aa34ee3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/layout/activity_simple.xml
@@ -0,0 +1,8 @@
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/hello_world"
+ android:padding="@dimen/padding_medium"
+ tools:context=".${activityClass}" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/menu/main.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/menu/main.xml
new file mode 100644
index 0000000..cfc10fd
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/menu/main.xml
@@ -0,0 +1,6 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/menu_settings"
+ android:title="@string/menu_settings"
+ android:orderInCategory="100"
+ android:showAsAction="never" />
+</menu>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values-large/dimens.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values-large/dimens.xml
new file mode 100644
index 0000000..d8cd7c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values-large/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <dimen name="padding_small">8dp</dimen>
+ <dimen name="padding_medium">16dp</dimen>
+ <dimen name="padding_large">16dp</dimen>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/dimens.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/dimens.xml
new file mode 100644
index 0000000..d95a70f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <dimen name="padding_small">8dp</dimen>
+ <dimen name="padding_medium">8dp</dimen>
+ <dimen name="padding_large">16dp</dimen>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/strings.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..753649d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/res/values/strings.xml.ftl
@@ -0,0 +1,13 @@
+<resources>
+ <string name="activity_name">${appTitle}</string>
+
+ <string name="menu_settings">Settings</string>
+
+ <string name="hello_world">Hello world!</string>
+
+ <#if navType != "none">
+ <string name="title_section1">Section 1</string>
+ <string name="title_section2">Section 2</string>
+ <string name="title_section3">Section 3</string>
+ </#if>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/DropdownActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
new file mode 100644
index 0000000..98c1a8f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/DropdownActivity.java.ftl
@@ -0,0 +1,98 @@
+package ${packageName};
+
+import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+
+public class ${activityClass} extends FragmentActivity implements ActionBar.OnNavigationListener {
+
+ private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ // Set up the action bar.
+ final ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowTitleEnabled(false);
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+
+ // Set up the dropdown list navigation in the action bar.
+ actionBar.setListNavigationCallbacks(
+ // Specify a SpinnerAdapter to populate the dropdown list.
+ new ArrayAdapter(
+ actionBar.getThemedContext(),
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ new String[]{
+ getString(R.string.title_section1),
+ getString(R.string.title_section2),
+ getString(R.string.title_section3),
+ }),
+ this);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
+ getActionBar().setSelectedNavigationItem(
+ savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
+ getActionBar().getSelectedNavigationIndex());
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onNavigationItemSelected(int position, long id) {
+ // When the given tab is selected, show the tab contents in the container
+ Fragment fragment = new DummySectionFragment();
+ Bundle args = new Bundle();
+ args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, position + 1);
+ fragment.setArguments(args);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.container, fragment)
+ .commit();
+ return true;
+ }
+
+ /**
+ * A dummy fragment representing a section of the app, but that simply displays dummy text.
+ */
+ public static class DummySectionFragment extends Fragment {
+ public DummySectionFragment() {
+ }
+
+ public static final String ARG_SECTION_NUMBER = "section_number";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ TextView textView = new TextView(getActivity());
+ textView.setGravity(Gravity.CENTER);
+ Bundle args = getArguments();
+ textView.setText(Integer.toString(args.getInt(ARG_SECTION_NUMBER)));
+ return textView;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/SimpleActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
new file mode 100644
index 0000000..1ebc0fa
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/SimpleActivity.java.ftl
@@ -0,0 +1,19 @@
+package ${packageName};
+
+import android.os.Bundle;
+import android.app.Activity;
+import android.view.Menu;
+
+public class ${activityClass} extends Activity {
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsActivity.java.ftl
new file mode 100644
index 0000000..ab11a7f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsActivity.java.ftl
@@ -0,0 +1,94 @@
+package ${packageName};
+
+import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class ${activityClass} extends FragmentActivity implements ActionBar.TabListener {
+
+ private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item";
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ // Set up the action bar.
+ final ActionBar actionBar = getActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ // For each of the sections in the app, add a tab to the action bar.
+ actionBar.addTab(actionBar.newTab().setText(R.string.title_section1).setTabListener(this));
+ actionBar.addTab(actionBar.newTab().setText(R.string.title_section2).setTabListener(this));
+ actionBar.addTab(actionBar.newTab().setText(R.string.title_section3).setTabListener(this));
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) {
+ getActionBar().setSelectedNavigationItem(
+ savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM));
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(STATE_SELECTED_NAVIGATION_ITEM,
+ getActionBar().getSelectedNavigationIndex());
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+
+ @Override
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ // When the given tab is selected, show the tab contents in the container
+ Fragment fragment = new DummySectionFragment();
+ Bundle args = new Bundle();
+ args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, tab.getPosition() + 1);
+ fragment.setArguments(args);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.container, fragment)
+ .commit();
+ }
+
+ @Override
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+
+ /**
+ * A dummy fragment representing a section of the app, but that simply displays dummy text.
+ */
+ public static class DummySectionFragment extends Fragment {
+ public DummySectionFragment() {
+ }
+
+ public static final String ARG_SECTION_NUMBER = "section_number";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ TextView textView = new TextView(getActivity());
+ textView.setGravity(Gravity.CENTER);
+ Bundle args = getArguments();
+ textView.setText(Integer.toString(args.getInt(ARG_SECTION_NUMBER)));
+ return textView;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
new file mode 100644
index 0000000..eb47519
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/root/src/app_package/TabsAndPagerActivity.java.ftl
@@ -0,0 +1,151 @@
+package ${packageName};
+
+import android.app.ActionBar;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class ${activityClass} extends FragmentActivity<#if navType?contains("tabs")> implements ActionBar.TabListener</#if> {
+
+ /**
+ * The {@link android.support.v4.view.PagerAdapter} that will provide fragments for each of the
+ * sections. We use a {@link android.support.v4.app.FragmentPagerAdapter} derivative, which will
+ * keep every loaded fragment in memory. If this becomes too memory intensive, it may be best
+ * to switch to a {@link android.support.v4.app.FragmentStatePagerAdapter}.
+ */
+ SectionsPagerAdapter mSectionsPagerAdapter;
+
+ /**
+ * The {@link ViewPager} that will host the section contents.
+ */
+ ViewPager mViewPager;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.${layoutName});
+
+ // Create the adapter that will return a fragment for each of the three primary sections
+ // of the app.
+ mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
+
+ <#if navType?contains("tabs")>
+ // Set up the action bar.
+ final ActionBar actionBar = getActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ </#if>
+
+ // Set up the ViewPager with the sections adapter.
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+ mViewPager.setAdapter(mSectionsPagerAdapter);
+
+ <#if navType?contains("tabs")>
+ // When swiping between different sections, select the corresponding tab.
+ // We can also use ActionBar.Tab#select() to do this if we have a reference to the
+ // Tab.
+ mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ actionBar.setSelectedNavigationItem(position);
+ }
+ });
+
+ // For each of the sections in the app, add a tab to the action bar.
+ for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
+ // Create a tab with text corresponding to the page title defined by the adapter.
+ // Also specify this Activity object, which implements the TabListener interface, as the
+ // listener for when this tab is selected.
+ actionBar.addTab(
+ actionBar.newTab()
+ .setText(mSectionsPagerAdapter.getPageTitle(i))
+ .setTabListener(this));
+ }
+ </#if>
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.${menuName}, menu);
+ return true;
+ }
+ <#if navType?contains("tabs")>
+ @Override
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+
+ @Override
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ // When the given tab is selected, switch to the corresponding page in the ViewPager.
+ mViewPager.setCurrentItem(tab.getPosition());
+ }
+
+ @Override
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
+ }
+ </#if>
+
+ /**
+ * A {@link FragmentPagerAdapter} that returns a fragment corresponding to one of the primary
+ * sections of the app.
+ */
+ public class SectionsPagerAdapter extends FragmentPagerAdapter {
+
+ public SectionsPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ @Override
+ public Fragment getItem(int i) {
+ Fragment fragment = new DummySectionFragment();
+ Bundle args = new Bundle();
+ args.putInt(DummySectionFragment.ARG_SECTION_NUMBER, i + 1);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public int getCount() {
+ return 3;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ switch (position) {
+ case 0: return getString(R.string.title_section1).toUpperCase();
+ case 1: return getString(R.string.title_section2).toUpperCase();
+ case 2: return getString(R.string.title_section3).toUpperCase();
+ }
+ return null;
+ }
+ }
+
+ /**
+ * A dummy fragment representing a section of the app, but that simply displays dummy text.
+ */
+ public static class DummySectionFragment extends Fragment {
+ public DummySectionFragment() {
+ }
+
+ public static final String ARG_SECTION_NUMBER = "section_number";
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ TextView textView = new TextView(getActivity());
+ textView.setGravity(Gravity.CENTER);
+ Bundle args = getArguments();
+ textView.setText(Integer.toString(args.getInt(ARG_SECTION_NUMBER)));
+ return textView;
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template.xml
new file mode 100644
index 0000000..302e2cc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<template
+ name="New Blank Activity"
+ description="Creates a new blank activity, with optional inner navigation.">
+
+ <category value="Activities" />
+
+ <parameter
+ id="activityClass"
+ name="Activity Name"
+ type="string"
+ constraints="class|nonempty"
+ suggest="${layoutToActivity(layoutName)}"
+ default="MainActivity"
+ help="The name of the activity class to create" />
+
+ <parameter
+ id="layoutName"
+ name="Layout Name"
+ type="string"
+ constraints="layout|unique"
+ suggest="${activityToLayout(activityClass)}"
+ default="main"
+ help="The name of the layout to create for the activity" />
+
+ <parameter
+ id="navType"
+ name="Navigation Type"
+ type="enum"
+ default="none"
+ help="The type of navigation to use for the activity" >
+ <option id="none" default="true">None</option>
+ <option id="tabs">Tabs</option>
+ <option id="tabs_pager">Tabs + Swipe</option>
+ <option id="pager_strip">Swipe Views + Title Strip</option>
+ <option id="dropdown">Dropdown</option>
+ </parameter>
+
+ <parameter
+ id="appTitle"
+ name="Title"
+ type="string"
+ constraints="nonempty"
+ default="My Application"
+ help="The name of the activity. For launcher activities, the application title." />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <!-- 128x128 thumbnails relative to template.xml -->
+ <thumbs>
+ <!-- default thumbnail is required -->
+ <thumb>template_blank_activity.png</thumb>
+ <!-- attributes act as selectors based on chosen parameters -->
+ <thumb navType="none">template_blank_activity.png</thumb>
+ <thumb navType="tabs">template_blank_activity_tabs.png</thumb>
+ <thumb navType="tabs_pager">template_blank_activity_tabs_pager.png</thumb>
+ <thumb navType="pager_strip">template_blank_activity_pager.png</thumb>
+ <thumb navType="dropdown">template_blank_activity_dropdown.png</thumb>
+ </thumbs>
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity.png
new file mode 100644
index 0000000..729dd1c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_dropdown.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_dropdown.png
new file mode 100644
index 0000000..09fa2cf
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_dropdown.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_pager.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_pager.png
new file mode 100644
index 0000000..7cd8e0e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_pager.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs.png
new file mode 100644
index 0000000..86a09d6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs_pager.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs_pager.png
new file mode 100644
index 0000000..0697a56
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/BlankActivity/template_blank_activity_tabs_pager.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/globals.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/globals.xml.ftl
new file mode 100644
index 0000000..d2eeb40
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/globals.xml.ftl
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="view_class" value="${camelCaseToUnderscore(viewClass)}" />
+</globals>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/recipe.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/recipe.xml.ftl
new file mode 100644
index 0000000..d152df0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/recipe.xml.ftl
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="res/values/attrs.xml.ftl"
+ to="res/values/attrs_${view_class}.xml" />
+ <instantiate from="res/layout/sample.xml.ftl"
+ to="res/layout/sample_${view_class}.xml" />
+
+ <instantiate from="src/app_package/CustomView.java.ftl"
+ to="${srcOut}/${viewClass}.java" />
+
+ <open file="${srcOut}/${viewClass}.java" />
+ <open file="res/layout/sample_${view_class}.xml" />
+</recipe>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/layout/sample.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/layout/sample.xml.ftl
new file mode 100755
index 0000000..bdd8c8b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/layout/sample.xml.ftl
@@ -0,0 +1,18 @@
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res/${packageName}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <${packageName}.${viewClass}
+ android:background="#ccc"
+ android:layout_width="300dp"
+ android:layout_height="300dp"
+ android:paddingLeft="20dp"
+ android:paddingBottom="40dp"
+ app:exampleDimension="24sp"
+ app:exampleColor="#33b5e5"
+ app:exampleString="Hello, ${viewClass}"
+ app:exampleDrawable="@android:drawable/ic_menu_add" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/values/attrs.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/values/attrs.xml.ftl
new file mode 100755
index 0000000..89059d2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/res/values/attrs.xml.ftl
@@ -0,0 +1,8 @@
+<resources>
+ <declare-styleable name="${viewClass}">
+ <attr name="exampleString" format="string" />
+ <attr name="exampleDimension" format="dimension" />
+ <attr name="exampleColor" format="color" />
+ <attr name="exampleDrawable" format="color|reference" />
+ </declare-styleable>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/src/app_package/CustomView.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/src/app_package/CustomView.java.ftl
new file mode 100644
index 0000000..e1c7e13
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/root/src/app_package/CustomView.java.ftl
@@ -0,0 +1,181 @@
+package ${packageName};
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.text.TextPaint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * TODO: document your custom view class.
+ */
+public class ${viewClass} extends View {
+ private String mExampleString; // TODO: use a default from R.string...
+ private int mExampleColor = Color.RED; // TODO: use a default from R.color...
+ private float mExampleDimension = 0; // TODO: use a default from R.dimen...
+ private Drawable mExampleDrawable;
+
+ private TextPaint mTextPaint;
+ private float mTextWidth;
+ private float mTextHeight;
+
+ public ${viewClass}(Context context) {
+ super(context);
+ init(null, 0);
+ }
+
+ public ${viewClass}(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(attrs, 0);
+ }
+
+ public ${viewClass}(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(attrs, defStyle);
+ }
+
+ private void init(AttributeSet attrs, int defStyle) {
+ // Load attributes
+ final TypedArray a = getContext().obtainStyledAttributes(
+ attrs, R.styleable.${viewClass}, defStyle, 0);
+
+ mExampleString = a.getString(
+ R.styleable.${viewClass}_exampleString);
+ mExampleColor = a.getColor(
+ R.styleable.${viewClass}_exampleColor,
+ mExampleColor);
+ // Use getDimensionPixelSize or getDimensionPixelOffset when dealing with
+ // values that should fall on pixel boundaries.
+ mExampleDimension = a.getDimension(
+ R.styleable.${viewClass}_exampleDimension,
+ mExampleDimension);
+
+ if (a.hasValue(R.styleable.${viewClass}_exampleDrawable)) {
+ mExampleDrawable = a.getDrawable(
+ R.styleable.${viewClass}_exampleDrawable);
+ mExampleDrawable.setCallback(this);
+ }
+
+ a.recycle();
+
+ // Set up a default TextPaint object
+ mTextPaint = new TextPaint();
+ mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setTextAlign(Paint.Align.LEFT);
+
+ // Update TextPaint and text measurements from attributes
+ invalidateTextPaintAndMeasurements();
+ }
+
+ private void invalidateTextPaintAndMeasurements() {
+ mTextPaint.setTextSize(mExampleDimension);
+ mTextPaint.setColor(mExampleColor);
+ mTextWidth = mTextPaint.measureText(mExampleString);
+
+ Paint.FontMetrics fontMetrics = mTextPaint.getFontMetrics();
+ mTextHeight = fontMetrics.bottom;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // TODO: consider storing these as member variables to reduce
+ // allocations per draw cycle.
+ int paddingLeft = getPaddingLeft();
+ int paddingTop = getPaddingTop();
+ int paddingRight = getPaddingRight();
+ int paddingBottom = getPaddingBottom();
+
+ int contentWidth = getWidth() - paddingLeft - paddingRight;
+ int contentHeight = getHeight() - paddingTop - paddingBottom;
+
+ // Draw the text.
+ canvas.drawText(mExampleString,
+ paddingLeft + (contentWidth - mTextWidth) / 2,
+ paddingTop + (contentHeight + mTextHeight) / 2,
+ mTextPaint);
+
+ // Draw the example drawable on top of the text.
+ if (mExampleDrawable != null) {
+ mExampleDrawable.setBounds(paddingLeft, paddingTop,
+ paddingLeft + contentWidth, paddingTop + contentHeight);
+ mExampleDrawable.draw(canvas);
+ }
+ }
+
+ /**
+ * Gets the example string attribute value.
+ * @return The example string attribute value.
+ */
+ public String getExampleString() {
+ return mExampleString;
+ }
+
+ /**
+ * Sets the view's example string attribute value. In the example view, this string
+ * is the text to draw.
+ * @param exampleString The example string attribute value to use.
+ */
+ public void setExampleString(String exampleString) {
+ mExampleString = exampleString;
+ invalidateTextPaintAndMeasurements();
+ }
+
+ /**
+ * Gets the example color attribute value.
+ * @return The example color attribute value.
+ */
+ public int getExampleColor() {
+ return mExampleColor;
+ }
+
+ /**
+ * Sets the view's example color attribute value. In the example view, this color
+ * is the font color.
+ * @param exampleColor The example color attribute value to use.
+ */
+ public void setExampleColor(int exampleColor) {
+ mExampleColor = exampleColor;
+ invalidateTextPaintAndMeasurements();
+ }
+
+ /**
+ * Gets the example dimension attribute value.
+ * @return The example dimension attribute value.
+ */
+ public float getExampleDimension() {
+ return mExampleDimension;
+ }
+
+ /**
+ * Sets the view's example dimension attribute value. In the example view, this dimension
+ * is the font size.
+ * @param exampleDimension The example dimension attribute value to use.
+ */
+ public void setExampleDimension(float exampleDimension) {
+ mExampleDimension = exampleDimension;
+ invalidateTextPaintAndMeasurements();
+ }
+
+ /**
+ * Gets the example drawable attribute value.
+ * @return The example drawable attribute value.
+ */
+ public Drawable getExampleDrawable() {
+ return mExampleDrawable;
+ }
+
+ /**
+ * Sets the view's example drawable attribute value. In the example view, this drawable is
+ * drawn above the text.
+ * @param exampleDrawable The example drawable attribute value to use.
+ */
+ public void setExampleDrawable(Drawable exampleDrawable) {
+ mExampleDrawable = exampleDrawable;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/template.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/template.xml
new file mode 100644
index 0000000..9511566
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/CustomView/template.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<template
+ name="New Custom View"
+ description="Creates a new custom view that extends android.view.View, and exposes custom attributes.">
+
+ <category value="UI Components" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="viewClass"
+ name="View Class"
+ type="string"
+ constraints="class|unique"
+ default="MyView"
+ help="By convention, should end in 'View'" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/globals.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/globals.xml.ftl
new file mode 100644
index 0000000..519c081
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/globals.xml.ftl
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+ <global id="CollectionName" value="${objectKind}List" />
+ <global id="collection_name" value="${objectKind?lower_case}_list" />
+ <global id="DetailName" value="${objectKind}Detail" />
+ <global id="detail_name" value="${objectKind?lower_case}_detail" />
+</globals>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/recipe.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/recipe.xml.ftl
new file mode 100644
index 0000000..a07635e
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/recipe.xml.ftl
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<recipe>
+ <merge from="AndroidManifest.xml.ftl" />
+
+ <copy from="${android.templatesRes}/android-support-v4.jar.bin"
+ to="libs/android-support-v4.jar" />
+
+ <merge from="res/values-large/refs.xml.ftl" />
+ <merge from="res/values-sw600dp/refs.xml.ftl" />
+ <merge from="res/values/strings.xml.ftl" />
+
+ <instantiate from="res/layout/activity_content_detail.xml.ftl"
+ to="res/layout/activity_${detail_name}.xml" />
+ <instantiate from="res/layout/activity_content_list.xml.ftl"
+ to="res/layout/activity_${collection_name}.xml" />
+ <instantiate from="res/layout/activity_content_twopane.xml.ftl"
+ to="res/layout/activity_${objectKind?lower_case}_twopane.xml" />
+ <instantiate from="res/layout/fragment_content_detail.xml.ftl"
+ to="res/layout/fragment_${detail_name}.xml" />
+
+ <instantiate from="src/app_package/ContentDetailActivity.java.ftl"
+ to="${srcOut}/${DetailName}Activity.java" />
+ <instantiate from="src/app_package/ContentDetailFragment.java.ftl"
+ to="${srcOut}/${DetailName}Fragment.java" />
+ <instantiate from="src/app_package/ContentListActivity.java.ftl"
+ to="${srcOut}/${CollectionName}Activity.java" />
+ <instantiate from="src/app_package/ContentListFragment.java.ftl"
+ to="${srcOut}/${CollectionName}Fragment.java" />
+ <instantiate from="src/app_package/dummy/DummyContent.java.ftl"
+ to="${srcOut}/dummy/DummyContent.java" />
+</recipe>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/AndroidManifest.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..1b9aa76
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/AndroidManifest.xml.ftl
@@ -0,0 +1,16 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <application>
+ <activity android:name=".${CollectionName}Activity"
+ android:label="@string/title_${collection_name}">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".${DetailName}Activity"
+ android:label="@string/title_${detail_name}" />
+ </application>
+
+</manifest>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
new file mode 100755
index 0000000..1d6d5dc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_detail.xml.ftl
@@ -0,0 +1,4 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/${detail_name}_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
new file mode 100644
index 0000000..788e763
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_list.xml.ftl
@@ -0,0 +1,7 @@
+<fragment xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="${packageName}.${CollectionName}Fragment"
+ android:id="@+id/${collection_name}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
new file mode 100644
index 0000000..c7a2c75
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/activity_content_twopane.xml.ftl
@@ -0,0 +1,21 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginRight="16dp"
+ android:divider="?android:attr/dividerHorizontal"
+ android:showDividers="middle">
+
+ <fragment android:name="${packageName}.${CollectionName}Fragment"
+ android:id="@+id/${collection_name}"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1" />
+
+ <FrameLayout android:id="@+id/${detail_name}_container"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="3" />
+
+</LinearLayout>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
new file mode 100644
index 0000000..9b7ca72
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/layout/fragment_content_detail.xml.ftl
@@ -0,0 +1,6 @@
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ style="?android:attr/textAppearanceLarge"
+ android:id="@+id/${detail_name}"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="16dp" />
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-large/refs.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-large/refs.xml.ftl
new file mode 100644
index 0000000..f3edd90
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-large/refs.xml.ftl
@@ -0,0 +1,3 @@
+<resources>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
new file mode 100644
index 0000000..f3edd90
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values-sw600dp/refs.xml.ftl
@@ -0,0 +1,3 @@
+<resources>
+ <item type="layout" name="activity_${collection_name}">@layout/activity_${objectKind?lower_case}_twopane</item>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values/strings.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..9b92c7d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/res/values/strings.xml.ftl
@@ -0,0 +1,5 @@
+<resources>
+ <string name="app_name">My ${objectKindPlural} Application</string>
+ <string name="title_${collection_name}">${objectKindPlural}</string>
+ <string name="title_${detail_name}">${objectKind} Detail</string>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
new file mode 100644
index 0000000..a7deaf6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailActivity.java.ftl
@@ -0,0 +1,39 @@
+package ${packageName};
+
+import android.os.Bundle;
+import android.content.Intent;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.NavUtils;
+import android.view.MenuItem;
+
+public class ${DetailName}Activity extends FragmentActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_${detail_name});
+
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ if (savedInstanceState == null) {
+ Bundle arguments = new Bundle();
+ arguments.putString(${DetailName}Fragment.ARG_ITEM_ID,
+ getIntent().getStringExtra(${DetailName}Fragment.ARG_ITEM_ID));
+ ${DetailName}Fragment fragment = new ${DetailName}Fragment();
+ fragment.setArguments(arguments);
+ getSupportFragmentManager().beginTransaction()
+ .add(R.id.${detail_name}_container, fragment)
+ .commit();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ NavUtils.navigateUpTo(this, new Intent(this, ${CollectionName}Activity.class));
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
new file mode 100644
index 0000000..a0acb1c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentDetailFragment.java.ftl
@@ -0,0 +1,38 @@
+package ${packageName};
+
+import ${packageName}.dummy.DummyContent;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+public class ${DetailName}Fragment extends Fragment {
+
+ public static final String ARG_ITEM_ID = "item_id";
+
+ DummyContent.DummyItem mItem;
+
+ public ${DetailName}Fragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getArguments().containsKey(ARG_ITEM_ID)) {
+ mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View rootView = inflater.inflate(R.layout.fragment_${detail_name}, container, false);
+ if (mItem != null) {
+ ((TextView) rootView.findViewById(R.id.${detail_name})).setText(mItem.content);
+ }
+ return rootView;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
new file mode 100644
index 0000000..4bc5216
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListActivity.java.ftl
@@ -0,0 +1,41 @@
+package ${packageName};
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
+
+public class ${CollectionName}Activity extends FragmentActivity
+ implements ${CollectionName}Fragment.Callbacks {
+
+ private boolean mTwoPane;
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_${collection_name});
+
+ if (findViewById(R.id.${detail_name}_container) != null) {
+ mTwoPane = true;
+ ((${CollectionName}Fragment) getSupportFragmentManager()
+ .findFragmentById(R.id.${collection_name}))
+ .setActivateOnItemClick(true);
+ }
+ }
+
+ @Override
+ public void onItemSelected(String id) {
+ if (mTwoPane) {
+ Bundle arguments = new Bundle();
+ arguments.putString(${DetailName}Fragment.ARG_ITEM_ID, id);
+ ${DetailName}Fragment fragment = new ${DetailName}Fragment();
+ fragment.setArguments(arguments);
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.${detail_name}_container, fragment)
+ .commit();
+
+ } else {
+ Intent detailIntent = new Intent(this, ${DetailName}Activity.class);
+ detailIntent.putExtra(${DetailName}Fragment.ARG_ITEM_ID, id);
+ startActivity(detailIntent);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
new file mode 100644
index 0000000..6b4b9a0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/ContentListFragment.java.ftl
@@ -0,0 +1,97 @@
+package ${packageName};
+
+import ${packageName}.dummy.DummyContent;
+
+import android.R;
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+public class ${CollectionName}Fragment extends ListFragment {
+
+ private static final String STATE_ACTIVATED_POSITION = "activated_position";
+
+ private Callbacks mCallbacks = sDummyCallbacks;
+ private int mActivatedPosition = ListView.INVALID_POSITION;
+
+ public interface Callbacks {
+
+ public void onItemSelected(String id);
+ }
+
+ private static Callbacks sDummyCallbacks = new Callbacks() {
+ @Override
+ public void onItemSelected(String id) {
+ }
+ };
+
+ public ${CollectionName}Fragment() {
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(getActivity(),
+ R.layout.simple_list_item_activated_1,
+ R.id.text1,
+ DummyContent.ITEMS));
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (savedInstanceState != null && savedInstanceState
+ .containsKey(STATE_ACTIVATED_POSITION)) {
+ setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
+ }
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (!(activity instanceof Callbacks)) {
+ throw new IllegalStateException("Activity must implement fragment's callbacks.");
+ }
+
+ mCallbacks = (Callbacks) activity;
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCallbacks = sDummyCallbacks;
+ }
+
+ @Override
+ public void onListItemClick(ListView listView, View view, int position, long id) {
+ super.onListItemClick(listView, view, position, id);
+ mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mActivatedPosition != ListView.INVALID_POSITION) {
+ outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
+ }
+ }
+
+ public void setActivateOnItemClick(boolean activateOnItemClick) {
+ getListView().setChoiceMode(activateOnItemClick
+ ? ListView.CHOICE_MODE_SINGLE
+ : ListView.CHOICE_MODE_NONE);
+ }
+
+ public void setActivatedPosition(int position) {
+ if (position == ListView.INVALID_POSITION) {
+ getListView().setItemChecked(mActivatedPosition, false);
+ } else {
+ getListView().setItemChecked(position, true);
+ }
+
+ mActivatedPosition = position;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
new file mode 100644
index 0000000..2b05416
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/root/src/app_package/dummy/DummyContent.java.ftl
@@ -0,0 +1,39 @@
+package ${packageName}.dummy;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DummyContent {
+
+ public static class DummyItem {
+
+ public String id;
+ public String content;
+
+ public DummyItem(String id, String content) {
+ this.id = id;
+ this.content = content;
+ }
+
+ @Override
+ public String toString() {
+ return content;
+ }
+ }
+
+ public static List<DummyItem> ITEMS = new ArrayList<DummyItem>();
+ public static Map<String, DummyItem> ITEM_MAP = new HashMap<String, DummyItem>();
+
+ static {
+ addItem(new DummyItem("1", "Item 1"));
+ addItem(new DummyItem("2", "Item 2"));
+ addItem(new DummyItem("3", "Item 3"));
+ }
+
+ private static void addItem(DummyItem item) {
+ ITEMS.add(item);
+ ITEM_MAP.put(item.id, item);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template.xml
new file mode 100644
index 0000000..0eed682
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<template
+ name="New Master/Detail Flow"
+ description="Creates a new master/detail flow, which is two columns on tablets, and one column on smaller screens. This creates a master fragment, detail fragment, and two activities.">
+
+ <thumbs>
+ <thumb>template_master_detail.png</thumb>
+ </thumbs>
+
+ <category value="Flows" />
+
+ <parameter
+ id="objectKind"
+ name="Object Kind"
+ type="string"
+ constraints="nonempty"
+ default="Item"
+ help="Other examples are 'Person', 'Book', etc." />
+
+ <parameter
+ id="objectKindPlural"
+ name="Object Kind Plural"
+ type="string"
+ constraints="nonempty"
+ default="Items"
+ help="Other examples are 'People', 'Books', etc." />
+
+ <parameter
+ id="appTitle"
+ name="Application title"
+ type="string"
+ constraints="nonempty"
+ default="My Application" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template_master_detail.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template_master_detail.png
new file mode 100644
index 0000000..f9d3f23
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/MasterDetailFlow/template_master_detail.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/globals.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/globals.xml.ftl
new file mode 100644
index 0000000..bfc27eb
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/globals.xml.ftl
@@ -0,0 +1,4 @@
+<?xml version="1.0"?>
+<globals>
+ <global id="srcOut" value="src/${slashedPackageName(packageName)}" />
+</globals>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/recipe.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/recipe.xml.ftl
new file mode 100644
index 0000000..b343a10
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/recipe.xml.ftl
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<recipe>
+ <instantiate from="AndroidManifest.xml.ftl" />
+
+<#if copyIcons>
+ <copy from="res/drawable-hdpi" />
+ <copy from="res/drawable-mdpi" />
+ <copy from="res/drawable-xhdpi" />
+</#if>
+ <copy from="res/values/dimens.xml" />
+ <copy from="res/values/styles.xml" />
+ <copy from="res/values-large/dimens.xml" />
+
+ <instantiate from="res/values/strings.xml.ftl" />
+</recipe>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/AndroidManifest.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/AndroidManifest.xml.ftl
new file mode 100644
index 0000000..c97c601
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/AndroidManifest.xml.ftl
@@ -0,0 +1,14 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="${packageName}"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <uses-sdk android:minSdkVersion="${minApi}" android:targetSdkVersion="${targetApi}" />
+
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ android:theme="@style/AppTheme">
+
+ </application>
+
+</manifest>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000..67de12d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..fba1ff0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000..134d549
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..72a445d
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_action_search.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000..d699c6b
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_action_search.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..002e7b0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values-large/dimens.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values-large/dimens.xml
new file mode 100644
index 0000000..d8cd7c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values-large/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <dimen name="padding_small">8dp</dimen>
+ <dimen name="padding_medium">16dp</dimen>
+ <dimen name="padding_large">16dp</dimen>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/dimens.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/dimens.xml
new file mode 100644
index 0000000..d95a70f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/dimens.xml
@@ -0,0 +1,5 @@
+<resources>
+ <dimen name="padding_small">8dp</dimen>
+ <dimen name="padding_medium">8dp</dimen>
+ <dimen name="padding_large">16dp</dimen>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/strings.xml.ftl b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/strings.xml.ftl
new file mode 100644
index 0000000..557e5c2
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/strings.xml.ftl
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">${appTitle}</string>
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/styles.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/styles.xml
new file mode 100644
index 0000000..5cd7c2f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/root/res/values/styles.xml
@@ -0,0 +1,3 @@
+<resources>
+ <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar" />
+</resources>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template.xml b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template.xml
new file mode 100644
index 0000000..84ba6c7
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<template
+ name="New Android Application"
+ description="Creates a new Android application with an activity.">
+
+ <thumbs>
+ <thumb>template_new_project.png</thumb>
+ </thumbs>
+
+ <category value="Applications" />
+
+ <parameter
+ id="packageName"
+ name="Package name"
+ type="string"
+ constraints="package"
+ default="com.mycompany.myapp" />
+
+ <parameter
+ id="appTitle"
+ name="Application title"
+ type="string"
+ constraints="nonempty"
+ default="My Application" />
+
+ <parameter
+ id="minApi"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <!--
+ Usually the same as minApi, but when minApi is a code name this will be the corresponding
+ API level
+ -->
+ <parameter
+ id="minApiLevel"
+ name="Minimum API level"
+ type="string"
+ constraints="apilevel"
+ default="7" />
+
+ <parameter
+ id="targetApi"
+ name="Target API level"
+ type="string"
+ constraints="apilevel"
+ default="15" />
+
+ <parameter
+ id="copyIcons"
+ name="Include launcher icons"
+ type="boolean"
+ default="true" />
+
+ <globals file="globals.xml.ftl" />
+ <execute file="recipe.xml.ftl" />
+
+</template>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template_new_project.png b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template_new_project.png
new file mode 100644
index 0000000..92e8556
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/NewAndroidApplication/template_new_project.png
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/layout.template b/eclipse/plugins/com.android.ide.eclipse.adt/templates/layout.template
index 5f4c383..398b79a 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/templates/layout.template
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/layout.template
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
+ tools:context=".ACTIVITY_NAME"
>
<TextView
android:layout_width="fill_parent"
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/templates/res/android-support-v4.jar.bin b/eclipse/plugins/com.android.ide.eclipse.adt/templates/res/android-support-v4.jar.bin
new file mode 100644
index 0000000..d006198
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/templates/res/android-support-v4.jar.bin
Binary files differ
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml
index c1d43ae..f5a809f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/plugin.xml
@@ -127,5 +127,11 @@
class="com.android.ide.eclipse.ddms.preferences.LogCatPreferencePage"
id="com.android.ide.eclipse.ddms.preferences.LogCatPreferencePage"
name="%page.name.LogCat"/>
+ <page
+ category="com.android.ide.eclipse.ddms.preferences.LogCatPreferencePage"
+ class="com.android.ide.eclipse.ddms.preferences.LogCatColorsPage"
+ id="com.android.ide.eclipse.ddms.preferences.LogCatColorsPage"
+ name="Colors">
+ </page>
</extension>
</plugin>
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatColorsPage.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatColorsPage.java
new file mode 100644
index 0000000..675a51c
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatColorsPage.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 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.ddms.preferences;
+
+import com.android.ddmuilib.logcat.LogCatPanel;
+import com.android.ide.eclipse.ddms.DdmsPlugin;
+
+import org.eclipse.jface.preference.ColorFieldEditor;
+import org.eclipse.jface.preference.FieldEditorPreferencePage;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchPreferencePage;
+
+public class LogCatColorsPage extends FieldEditorPreferencePage
+ implements IWorkbenchPreferencePage {
+ public LogCatColorsPage() {
+ super(GRID);
+ setPreferenceStore(DdmsPlugin.getDefault().getPreferenceStore());
+ }
+
+ @Override
+ public void init(IWorkbench workbench) {
+ }
+
+ @Override
+ protected void createFieldEditors() {
+ // colors preference for different log levels
+ ColorFieldEditor cfe = new ColorFieldEditor(LogCatPanel.VERBOSE_COLOR_PREFKEY,
+ "Verbose Log Message Color", getFieldEditorParent());
+ addField(cfe);
+
+ cfe = new ColorFieldEditor(LogCatPanel.DEBUG_COLOR_PREFKEY, "Debug Log Message Color",
+ getFieldEditorParent());
+ addField(cfe);
+
+ cfe = new ColorFieldEditor(LogCatPanel.INFO_COLOR_PREFKEY, "Info Log Message Color",
+ getFieldEditorParent());
+ addField(cfe);
+
+ cfe = new ColorFieldEditor(LogCatPanel.WARN_COLOR_PREFKEY, "Warning Log Message Color",
+ getFieldEditorParent());
+ addField(cfe);
+
+ cfe = new ColorFieldEditor(LogCatPanel.ERROR_COLOR_PREFKEY, "Error Log Message Color",
+ getFieldEditorParent());
+ addField(cfe);
+
+ cfe = new ColorFieldEditor(LogCatPanel.ASSERT_COLOR_PREFKEY, "Assert Log Message Color",
+ getFieldEditorParent());
+ addField(cfe);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
index 9971e18..a0c5450 100644
--- a/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.ddms/src/com/android/ide/eclipse/ddms/preferences/LogCatPreferencePage.java
@@ -30,6 +30,9 @@ import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FontFieldEditor;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IPerspectiveDescriptor;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
@@ -62,18 +65,27 @@ public class LogCatPreferencePage extends FieldEditorPreferencePage implements
Messages.LogCatPreferencePage_MaxMessages, getFieldEditorParent());
addField(mMaxMessages);
+ createHorizontalSeparator();
+
if (InstallDetails.isAdtInstalled()) {
createAdtSpecificFieldEditors();
}
}
+ private void createHorizontalSeparator() {
+ Label l = new Label(getFieldEditorParent(), SWT.SEPARATOR | SWT.HORIZONTAL);
+ GridData gd = new GridData(GridData.FILL_HORIZONTAL);
+ gd.horizontalSpan = 3;
+ l.setLayoutData(gd);
+ }
+
private void createAdtSpecificFieldEditors() {
mSwitchPerspective = new BooleanFieldEditor(PreferenceInitializer.ATTR_SWITCH_PERSPECTIVE,
Messages.LogCatPreferencePage_Switch_Perspective, getFieldEditorParent());
addField(mSwitchPerspective);
IPerspectiveDescriptor[] perspectiveDescriptors =
PlatformUI.getWorkbench().getPerspectiveRegistry().getPerspectives();
- String[][] perspectives;
+ String[][] perspectives = new String[0][0];
if (perspectiveDescriptors.length > 0) {
perspectives = new String[perspectiveDescriptors.length][2];
for (int i = 0; i < perspectiveDescriptors.length; i++) {
@@ -81,8 +93,6 @@ public class LogCatPreferencePage extends FieldEditorPreferencePage implements
perspectives[i][0] = perspective.getLabel();
perspectives[i][1] = perspective.getId();
}
- } else {
- perspectives = new String[0][0];
}
mWhichPerspective = new ComboFieldEditor(PreferenceInitializer.ATTR_PERSPECTIVE_ID,
Messages.LogCatPreferencePage_Switch_To, perspectives, getFieldEditorParent());
@@ -90,6 +100,8 @@ public class LogCatPreferencePage extends FieldEditorPreferencePage implements
.getBoolean(PreferenceInitializer.ATTR_SWITCH_PERSPECTIVE), getFieldEditorParent());
addField(mWhichPerspective);
+ createHorizontalSeparator();
+
mAutoMonitorLogcat = new BooleanFieldEditor(LogCatMonitor.AUTO_MONITOR_PREFKEY,
Messages.LogCatPreferencePage_AutoMonitorLogcat,
getFieldEditorParent());
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
index 3f7d20e..c6e60cb 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/AndroidContentAssistTest.java
@@ -25,13 +25,10 @@ import static com.android.sdklib.SdkConstants.FD_RES;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.animator.AnimationContentAssist;
-import com.android.ide.eclipse.adt.internal.editors.animator.AnimationEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.color.ColorContentAssist;
-import com.android.ide.eclipse.adt.internal.editors.color.ColorEditorDelegate;
+import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor;
import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableContentAssist;
-import com.android.ide.eclipse.adt.internal.editors.drawable.DrawableEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutContentAssist;
-import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestContentAssist;
import com.android.ide.eclipse.adt.internal.editors.manifest.ManifestEditor;
@@ -773,34 +770,34 @@ public class AndroidContentAssistTest extends AdtProjectTest {
private void checkLayoutCompletion(String name, String caretLocation) throws Exception {
IFile file = getLayoutFile(getProject(), name);
- IDE.setDefaultEditor(file, LayoutEditorDelegate.LEGACY_EDITOR_ID);
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
checkCompletion(name, file, caretLocation,
new LayoutContentAssist());
}
private void checkColorCompletion(String name, String caretLocation) throws Exception {
IFile file = getTestDataFile(getProject(), name, FD_RES + "/" + FD_RES_COLOR + "/" + name);
- IDE.setDefaultEditor(file, ColorEditorDelegate.LEGACY_EDITOR_ID);
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
checkCompletion(name, file, caretLocation, new ColorContentAssist());
}
private void checkAnimCompletion(String name, String caretLocation) throws Exception {
IFile file = getTestDataFile(getProject(), name, FD_RES + "/" + FD_RES_ANIM + "/" + name);
- IDE.setDefaultEditor(file, AnimationEditorDelegate.LEGACY_EDITOR_ID);
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
checkCompletion(name, file, caretLocation, new AnimationContentAssist());
}
private void checkAnimatorCompletion(String name, String caretLocation) throws Exception {
IFile file = getTestDataFile(getProject(), name, FD_RES + "/" + FD_RES_ANIMATOR + "/"
+ name);
- IDE.setDefaultEditor(file, AnimationEditorDelegate.LEGACY_EDITOR_ID);
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
checkCompletion(name, file, caretLocation, new AnimationContentAssist());
}
private void checkDrawableCompletion(String name, String caretLocation) throws Exception {
IFile file = getTestDataFile(getProject(), name, FD_RES + "/" + FD_RES_DRAWABLE + "/"
+ name);
- IDE.setDefaultEditor(file, DrawableEditorDelegate.LEGACY_EDITOR_ID);
+ IDE.setDefaultEditor(file, CommonXmlEditor.ID);
checkCompletion(name, file, caretLocation, new DrawableContentAssist());
}
@@ -851,6 +848,8 @@ public class AndroidContentAssistTest extends AdtProjectTest {
proposals = new ICompletionProposal[0];
}
+ editor.getEditorSite().getPage().closeAllEditors(false);
+
return proposals;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/HyperlinksTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/HyperlinksTest.java
index 0d42357..a54376d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/HyperlinksTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/HyperlinksTest.java
@@ -19,8 +19,6 @@ import static com.android.sdklib.SdkConstants.FD_SOURCES;
import com.android.ide.common.resources.ResourceFile;
import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
-import com.android.ide.eclipse.adt.internal.editors.Hyperlinks;
import com.android.ide.eclipse.adt.internal.editors.Hyperlinks.ResourceLink;
import com.android.ide.eclipse.adt.internal.editors.Hyperlinks.XmlResolver;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.AdtProjectTest;
@@ -180,6 +178,12 @@ public class HyperlinksTest extends AdtProjectTest {
"class=\"com.and^roid.eclipse.tests.TestFragment\"");
}
+ public void testNavigate15() throws Exception {
+ // Check navigating to a theme resource
+ checkXmlNavigation("navigation1.xml", "res/layout/navigation1.xml",
+ "?android:attr/alert^DialogStyle");
+ }
+
// Left to test:
// onClick handling
// class attributes
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
index a816cff..8ddfff5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/AdtProjectTest.java
@@ -411,7 +411,6 @@ public class AdtProjectTest extends SdkTestCase {
}
}
-
boolean showBeforeWindow = firstDelta >= beforeLines.length - lastDelta;
boolean showAfterWindow = firstDelta >= afterLines.length - lastDelta;
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt
index 17cf984..8f6cd32 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion55.txt
@@ -12,5 +12,5 @@ android:startOffset : Delay in milliseconds before the animation runs, once star
android:repeatCount : Defines how many times the animation should repeat. [integer, enum]
android:repeatMode : Defines the animation behavior when it reaches the end and the repeat count is greater than 0 or infinite. [enum]
android:zAdjustment : Allows for an adjustment of the Z ordering of the content being animated for the duration of the animation. [enum]
-android:background : Special background behind animation. [reference, color]
+android:background : Special background behind animation. [color, reference]
android:detachWallpaper : Special option for window animations: if this window is on top of a wallpaper, don't animate the wallpaper with it. [boolean]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt
index 837a6cc..5110e73 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/anim1-expected-completion56.txt
@@ -10,5 +10,5 @@ android:startOffset : Delay in milliseconds before the animation runs, once star
android:repeatCount : Defines how many times the animation should repeat. [integer, enum]
android:repeatMode : Defines the animation behavior when it reaches the end and the repeat count is greater than 0 or infinite. [enum]
android:zAdjustment : Allows for an adjustment of the Z ordering of the content being animated for the duration of the animation. [enum]
-android:background : Special background behind animation. [reference, color]
+android:background : Special background behind animation. [color, reference]
android:detachWallpaper : Special option for window animations: if this window is on top of a wallpaper, don't animate the wallpaper with it. [boolean]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt
index 1ba8a62..b298ac0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/animator1-expected-completion61.txt
@@ -11,7 +11,6 @@ Code completion in animator1.xml for android:interpolator="^@android:anim/bounce
@android:
@+id/
@anim/
-@animator/
@color/
@drawable/
@id/
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt
index c5aa1a0..063dc8c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/broken1-expected-completion20.txt
@@ -1,2 +1,2 @@
Code completion in broken1.xml for android:textColorHigh^:
-android:textColorHighlight : Color of the text selection highlight. [reference, color]
+android:textColorHighlight : Color of the text selection highlight. [color, reference]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt
index ac18c6d..281d7a0 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion1-expected-completion39.txt
@@ -3,15 +3,15 @@ style : A reference to a custom style [reference]
android:bufferType : Determines the minimum type that getText() will return. [enum]
android:text : Text to display. [string]
android:hint : Hint text to display when the text is empty. [string]
-android:textColor : Text color. [reference, color]
-android:textColorHighlight : Color of the text selection highlight. [reference, color]
-android:textColorHint : Color of the hint text. [reference, color]
+android:textColor : Text color. [color, reference]
+android:textColorHighlight : Color of the text selection highlight. [color, reference]
+android:textColorHint : Color of the hint text. [color, reference]
android:textAppearance : Base text color, typeface, size, and style. [reference]
android:textSize : Size of the text. [dimension]
android:textScaleX : Sets the horizontal scaling factor for the text. [float]
android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum]
android:textStyle : Style (bold, italic, bolditalic) for the text. [flag]
-android:textColorLink : Text color for links. [reference, color]
+android:textColorLink : Text color for links. [color, reference]
android:cursorVisible : Makes the cursor visible (the default) or invisible. [boolean]
android:maxLines : Makes the TextView be at most this many lines tall. [integer]
android:maxHeight : Makes the TextView be at most this many pixels tall. [dimension]
@@ -48,10 +48,10 @@ android:autoText : If set, specifies that this TextView has a textual input meth
android:editable : If set, specifies that this TextView has an input method. * Deprecated: Use inputType instead. [boolean]
android:freezesText : If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. [boolean]
android:ellipsize : If set, causes words that are longer than the view is wide to be ellipsized instead of broken in the middle. [enum]
-android:drawableTop : The drawable to be drawn above the text. [reference, color]
-android:drawableBottom : The drawable to be drawn below the text. [reference, color]
-android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color]
-android:drawableRight : The drawable to be drawn to the right of the text. [reference, color]
+android:drawableTop : The drawable to be drawn above the text. [color, reference]
+android:drawableBottom : The drawable to be drawn below the text. [color, reference]
+android:drawableLeft : The drawable to be drawn to the left of the text. [color, reference]
+android:drawableRight : The drawable to be drawn to the right of the text. [color, reference]
android:drawablePadding : The padding between the drawables and the text. [dimension]
android:lineSpacingExtra : Extra spacing between lines of text. [dimension]
android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float]
@@ -75,7 +75,7 @@ android:id : Supply an identifier name for this view, to later retrieve it with
android:tag : Supply a tag for this view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string]
android:scrollX : The initial horizontal scroll offset, in pixels. [dimension]
android:scrollY : The initial vertical scroll offset, in pixels. [dimension]
-android:background : A drawable to use as the background. [reference, color]
+android:background : A drawable to use as the background. [color, reference]
android:padding : Sets the padding, in pixels, of all four edges. [dimension]
android:paddingLeft : Sets the padding, in pixels, of the left edge; see padding. [dimension]
android:paddingTop : Sets the padding, in pixels, of the top edge; see padding. [dimension]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt
index 138e225..be3d8d5 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completion5-expected-completion40.txt
@@ -3,15 +3,15 @@ style : A reference to a custom style [reference]
android:bufferType : Determines the minimum type that getText() will return. [enum]
android:text : Text to display. [string]
android:hint : Hint text to display when the text is empty. [string]
-android:textColor : Text color. [reference, color]
-android:textColorHighlight : Color of the text selection highlight. [reference, color]
-android:textColorHint : Color of the hint text. [reference, color]
+android:textColor : Text color. [color, reference]
+android:textColorHighlight : Color of the text selection highlight. [color, reference]
+android:textColorHint : Color of the hint text. [color, reference]
android:textAppearance : Base text color, typeface, size, and style. [reference]
android:textSize : Size of the text. [dimension]
android:textScaleX : Sets the horizontal scaling factor for the text. [float]
android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum]
android:textStyle : Style (bold, italic, bolditalic) for the text. [flag]
-android:textColorLink : Text color for links. [reference, color]
+android:textColorLink : Text color for links. [color, reference]
android:cursorVisible : Makes the cursor visible (the default) or invisible. [boolean]
android:maxLines : Makes the TextView be at most this many lines tall. [integer]
android:maxHeight : Makes the TextView be at most this many pixels tall. [dimension]
@@ -48,10 +48,10 @@ android:autoText : If set, specifies that this TextView has a textual input meth
android:editable : If set, specifies that this TextView has an input method. * Deprecated: Use inputType instead. [boolean]
android:freezesText : If set, the text view will include its current complete text inside of its frozen icicle in addition to meta-data such as the current cursor position. [boolean]
android:ellipsize : If set, causes words that are longer than the view is wide to be ellipsized instead of broken in the middle. [enum]
-android:drawableTop : The drawable to be drawn above the text. [reference, color]
-android:drawableBottom : The drawable to be drawn below the text. [reference, color]
-android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color]
-android:drawableRight : The drawable to be drawn to the right of the text. [reference, color]
+android:drawableTop : The drawable to be drawn above the text. [color, reference]
+android:drawableBottom : The drawable to be drawn below the text. [color, reference]
+android:drawableLeft : The drawable to be drawn to the left of the text. [color, reference]
+android:drawableRight : The drawable to be drawn to the right of the text. [color, reference]
android:drawablePadding : The padding between the drawables and the text. [dimension]
android:lineSpacingExtra : Extra spacing between lines of text. [dimension]
android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float]
@@ -75,7 +75,7 @@ android:id : Supply an identifier name for this view, to later retrieve it with
android:tag : Supply a tag for this view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string]
android:scrollX : The initial horizontal scroll offset, in pixels. [dimension]
android:scrollY : The initial vertical scroll offset, in pixels. [dimension]
-android:background : A drawable to use as the background. [reference, color]
+android:background : A drawable to use as the background. [color, reference]
android:padding : Sets the padding, in pixels, of all four edges. [dimension]
android:paddingLeft : Sets the padding, in pixels, of the left edge; see padding. [dimension]
android:paddingTop : Sets the padding, in pixels, of the top edge; see padding. [dimension]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt
index 3592e04..7e27fa6 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/completionvalues1-expected-completion32.txt
@@ -14,7 +14,7 @@ android:animationResolution : Timeout between frames of animation in millisecond
android:autoLink : Controls whether links such as urls and email addresses are automatically found and converted to clickable links. [flag]
android:autoStart : When true, automatically start animating [boolean]
android:autoText : If set, specifies that this TextView has a textual input method and automatically corrects some common spelling errors. * Deprecated: Use inputType instead. [boolean]
-android:background : A drawable to use as the background. [reference, color]
+android:background : A drawable to use as the background. [color, reference]
android:baseline : The offset of the baseline within this view. [dimension]
android:baselineAlignBottom : If true, the image view will be baseline aligned with based on its bottom edge. [boolean]
android:baselineAligned : When set to false, prevents the layout from aligning its children's baselines. [boolean]
@@ -28,7 +28,7 @@ android:capitalize : If set, specifies that this TextView has a textual input me
android:checkMark : Drawable used for the check mark graphic. [reference]
android:checked : Indicates the initial checked state of this button. [boolean]
android:checkedButton : The id of the child radio button that should be checked by default within this radio group. [integer]
-android:childDivider : Drawable or color that is used as a divider for children. [reference, color]
+android:childDivider : Drawable or color that is used as a divider for children. [color, reference]
android:childIndicator : Indicator shown beside the child View. [reference]
android:childIndicatorLeft : The left bound for a child's indicator. [dimension]
android:childIndicatorRight : The right bound for a child's indicator. [dimension]
@@ -55,16 +55,16 @@ android:divider : Drawable to use as a vertical divider between buttons.
android:dividerHeight : Height of the divider. [dimension]
android:dividerPadding : Size of padding on either end of a divider. [dimension]
android:drawSelectorOnTop : When set to true, the selector will be drawn over the selected item. [boolean]
-android:drawableBottom : The drawable to be drawn below the text. [reference, color]
-android:drawableLeft : The drawable to be drawn to the left of the text. [reference, color]
+android:drawableBottom : The drawable to be drawn below the text. [color, reference]
+android:drawableLeft : The drawable to be drawn to the left of the text. [color, reference]
android:drawablePadding : The padding between the drawables and the text. [dimension]
-android:drawableRight : The drawable to be drawn to the right of the text. [reference, color]
-android:drawableTop : The drawable to be drawn above the text. [reference, color]
+android:drawableRight : The drawable to be drawn to the right of the text. [color, reference]
+android:drawableTop : The drawable to be drawn above the text. [color, reference]
android:drawingCacheQuality : Defines the quality of translucent drawing caches. [enum]
android:dropDownAnchor : View to anchor the auto-complete dropdown to. [reference]
android:dropDownHeight : Specifies the basic height of the dropdown. [dimension, enum]
android:dropDownHorizontalOffset : Horizontal offset from the spinner widget for positioning the dropdown in spinnerMode="dropdown". [dimension]
-android:dropDownSelector : List selector to use for spinnerMode="dropdown" display. [reference, color]
+android:dropDownSelector : List selector to use for spinnerMode="dropdown" display. [color, reference]
android:dropDownVerticalOffset : Vertical offset from the spinner widget for positioning the dropdown in spinnerMode="dropdown". [dimension]
android:dropDownWidth : Width of the dropdown in spinnerMode="dropdown". [dimension, enum]
android:duplicateParentState : When this attribute is set to true, the view gets its drawable state (focused, pressed, etc.) from its direct parent rather than from itself. [boolean]
@@ -92,9 +92,9 @@ android:flingable : @hide Whether the number picker supports fligning. [boolean
android:flipInterval : [integer]
android:focusable : Boolean that controls whether a view can take focus. [boolean]
android:focusableInTouchMode : Boolean that controls whether a view can take focus while in touch mode. [boolean]
-android:focusedMonthDateColor : The color for the dates of the selected month. [reference, color]
+android:focusedMonthDateColor : The color for the dates of the selected month. [color, reference]
android:footerDividersEnabled : When set to false, the ListView will not draw the divider before each footer view. [boolean]
-android:foreground : Defines the drawable to draw over the content. [reference, color]
+android:foreground : Defines the drawable to draw over the content. [color, reference]
android:foregroundGravity : Defines the gravity to apply to the foreground drawable. [flag]
android:foregroundInsidePadding : Defines whether the foreground drawable should be drawn inside the padding. [boolean]
android:format : Format string: if specified, the Chronometer will display this string, with the first "%s" replaced by the current timer value in "MM:SS" or "H:MM:SS" form. [string]
@@ -144,7 +144,7 @@ android:lineSpacingExtra : Extra spacing between lines of text. [dimension]
android:lineSpacingMultiplier : Extra spacing between lines of text, as a multiplier. [float]
android:lines : Makes the TextView be exactly this many lines tall. [integer]
android:linksClickable : If set to false, keeps the movement method from being set to the link movement method even if autoLink causes links to be found. [boolean]
-android:listSelector : Drawable used to indicate the currently selected item in the list. [reference, color]
+android:listSelector : Drawable used to indicate the currently selected item in the list. [color, reference]
android:longClickable : Defines whether this view reacts to long click events. [boolean]
android:loopViews : Defines whether the animator loops to the first view once it has reached the end of the list. [boolean]
android:marqueeRepeatLimit : The number of times to repeat the marquee animation. [integer, enum]
@@ -175,8 +175,8 @@ android:numeric : If set, specifies that this TextView has a numeric input metho
android:onClick : Name of the method in this View's context to invoke when the view is clicked. [string]
android:orientation : Should the layout be a column or a row? Use "horizontal" for a row, "vertical" for a column. [enum]
android:outAnimation : Identifier for the animation to use when a view is hidden. [reference]
-android:overScrollFooter : Drawable to draw below list content. [reference, color]
-android:overScrollHeader : Drawable to draw above list content. [reference, color]
+android:overScrollFooter : Drawable to draw below list content. [color, reference]
+android:overScrollHeader : Drawable to draw above list content. [color, reference]
android:overScrollMode : Defines over-scrolling behavior. [enum]
android:padding : Sets the padding, in pixels, of all four edges. [dimension]
android:paddingBottom : Sets the padding, in pixels, of the bottom edge; see padding. [dimension]
@@ -186,7 +186,7 @@ android:paddingTop : Sets the padding, in pixels, of the top edge; see padding.
android:password : Whether the characters of the field are displayed as password dots instead of themselves. * Deprecated: Use inputType instead. [boolean]
android:persistentDrawingCache : Defines the persistence of the drawing cache. [flag]
android:phoneNumber : If set, specifies that this TextView has a phone number input method. * Deprecated: Use inputType instead. [boolean]
-android:popupBackground : Background drawable to use for the dropdown in spinnerMode="dropdown". [reference, color]
+android:popupBackground : Background drawable to use for the dropdown in spinnerMode="dropdown". [color, reference]
android:popupPromptView : Reference to a layout to use for displaying a prompt in the dropdown for spinnerMode="dropdown". [reference]
android:privateImeOptions : An addition content type description to supply to the input method attached to the text view, which is private to the implementation of the input method. [string]
android:progress : Defines the default progress value, between 0 and max. [integer]
@@ -220,7 +220,7 @@ android:scrollingCache : When set to true, the list uses a drawing cache during
android:secondaryProgress : Defines the secondary progress value, between 0 and max. [integer]
android:selectAllOnFocus : If the text is selectable, select it all when the view takes focus instead of moving the cursor to the start or end. [boolean]
android:selectedDateVerticalBar : Drawable for the vertical bar shown at the beggining and at the end of a selected date. [reference]
-android:selectedWeekBackgroundColor : The background color for the selected week. [reference, color]
+android:selectedWeekBackgroundColor : The background color for the selected week. [color, reference]
android:selectionDivider : @hide The divider for making the selection area. [reference]
android:selectionDividerHeight : @hide The height of the selection divider. [dimension]
android:shadowColor : Place a shadow of the specified color behind the text. [color]
@@ -233,13 +233,13 @@ android:shownWeekCount : The number of weeks to be shown. [integer]
android:shrinkColumns : The zero-based index of the columns to shrink. [string]
android:singleLine : Constrains the text to a single horizontally scrolling line instead of letting it wrap onto multiple lines, and advances focus instead of inserting a newline when you press the enter key. * Deprecated: This attribute is deprecated and is replaced by the textMultiLine flag in the inputType attribute. Use caution when altering existing layouts, as the default value of singeLine is false (multi-line mode), but if you specify any value for inputType, the default is single-line mode. (If both singleLine and inputType attributes are found, the inputType flags will override the value of singleLine.). [boolean]
android:smoothScrollbar : When set to true, the list will use a more refined calculation method based on the pixels height of the items visible on screen. [boolean]
-android:solidColor : @hide Color for the solid color background if such for optimized rendering. [reference, color]
+android:solidColor : @hide Color for the solid color background if such for optimized rendering. [color, reference]
android:soundEffectsEnabled : Boolean that controls whether a view should have sound effects enabled for events such as clicking and touching. [boolean]
android:spacing : [dimension]
android:spinnerMode : Display mode for spinner options. [enum]
android:spinnersShown : Whether the spinners are shown. [boolean]
android:splitMotionEvents : Sets whether this ViewGroup should split MotionEvents to separate child views during touch event dispatch. [boolean]
-android:src : Sets a drawable as the content of this ImageView. [reference, color]
+android:src : Sets a drawable as the content of this ImageView. [color, reference]
android:stackFromBottom : Used by ListView and GridView to stack their content from the bottom. [boolean]
android:startYear : The first year (inclusive), for example "1940". [integer]
android:stepSize : The step size of the rating. [float]
@@ -253,10 +253,10 @@ android:tabStripRight : Drawable used to draw the right part of the strip undern
android:tag : Supply a tag for the top-level view containing a String, to be retrieved later with View.getTag() or searched for with View.findViewWithTag() . [string]
android:text : Text to display. [string]
android:textAppearance : Base text color, typeface, size, and style. [reference]
-android:textColor : Text color. [reference, color]
-android:textColorHighlight : Color of the text selection highlight. [reference, color]
-android:textColorHint : Color of the hint text. [reference, color]
-android:textColorLink : Text color for links. [reference, color]
+android:textColor : Text color. [color, reference]
+android:textColorHighlight : Color of the text selection highlight. [color, reference]
+android:textColorHint : Color of the hint text. [color, reference]
+android:textColorLink : Text color for links. [color, reference]
android:textCursorDrawable : Reference to a drawable that will be drawn under the insertion cursor. [reference]
android:textEditNoPasteWindowLayout : Variation of textEditPasteWindowLayout displayed when the clipboard is empty. [reference]
android:textEditPasteWindowLayout : The layout of the view that is displayed on top of the cursor to paste inside a TextEdit field. [reference]
@@ -283,13 +283,13 @@ android:translationX : translation in x of the view. [dimension]
android:translationY : translation in y of the view. [dimension]
android:typeface : Typeface (normal, sans, serif, monospace) for the text. [enum]
android:uncertainGestureColor : Color used to draw the user's strokes until we are sure it's a gesture. [color]
-android:unfocusedMonthDateColor : The color for the dates of an unfocused month. [reference, color]
+android:unfocusedMonthDateColor : The color for the dates of an unfocused month. [color, reference]
android:unselectedAlpha : Sets the alpha on the items that are not selected. [float]
android:verticalScrollbarPosition : Determines which side the vertical scroll bar should be placed on. [enum]
android:verticalSpacing : Defines the default vertical spacing between rows. [dimension]
android:visibility : Controls the initial visibility of the view. [enum]
android:weekDayTextAppearance : The text appearance for the week day abbreviation of the calendar header. [reference]
-android:weekNumberColor : The color for the week numbers. [reference, color]
-android:weekSeparatorLineColor : The color for the sepatator line between weeks. [reference, color]
+android:weekNumberColor : The color for the week numbers. [color, reference]
+android:weekSeparatorLineColor : The color for the sepatator line between weeks. [color, reference]
android:weightSum : Defines the maximum weight sum. [float]
android:width : Makes the TextView be exactly this many pixels wide. [dimension]
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt
index edf4892..c7f495b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion47.txt
@@ -1,5 +1,4 @@
Code completion in drawable1.xml for ^<layer-list:
-<animated-rotate />
<animation-list /> : Drawable used to render several animated frames.
<bitmap /> : Drawable used to draw bitmaps.
<clip />
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt
index 90eab10..d05bc76 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/drawable1-expected-completion50.txt
@@ -1,5 +1,4 @@
Code completion in drawable1.xml for <item >^</item>:
-<animated-rotate />
<animation-list /> : Drawable used to render several animated frames.
<bitmap /> : Drawable used to draw bitmaps.
<clip />
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion69.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion69.txt
index 382011e..627ff01 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion69.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/manifest-expected-completion69.txt
@@ -1,19 +1,19 @@
Code completion in manifest.xml for <uses-sdk android:minSdkVersion="^11" />:
-AOSP
-14 : Android 4.0.1
-13 : Android 3.2
-12 : Android 3.1
-11 : Android 3.0
-10
-9 : Android 2.3
-8 : Android 2.2
-7
-6
-5 : Android 2.0
-4 : Android 1.6
-3 : Android 1.5
-2 : Android 1.1
-1
+15 : API 15: Android 4.0.3 (IceCreamSandwich)
+14 : API 14: Android 4.0 (IceCreamSandwich)
+13 : API 13: Android 3.2 (Honeycomb)
+12 : API 12: Android 3.1 (Honeycomb)
+11 : API 11: Android 3.0 (Honeycomb)
+10 : API 10: Android 2.3.3 (Gingerbread)
+9 : API 9: Android 2.3 (Gingerbread)
+8 : API 8: Android 2.2 (Froyo)
+7 : API 7: Android 2.1 (Eclair)
+6 : API 6: Android 2.0.1 (Eclair)
+5 : API 5: Android 2.0 (Eclair)
+4 : API 4: Android 1.6 (Donut)
+3 : API 3: Android 1.5 (Cupcake)
+2 : API 2: Android 1.1
+1 : API 1: Android 1.0
@string/
@android:
@+id/
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate15.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate15.txt
new file mode 100644
index 0000000..e36c5f3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1-expected-navigate15.txt
@@ -0,0 +1,7 @@
+Go To Declaration in navigation1.xml for ?android:attr/alert^DialogStyle:
+Open Declaration in values/attrs.xml : [?android:attr/alertDialogStyle]
+ data/res/values/attrs.xml
+
+
+After open, the selected text is:
+ <attr name="alertDialogStyle" format="reference" />^
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml
index 9c175fc..e7ac4bc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/navigation1.xml
@@ -13,4 +13,5 @@
<EditText
android:text="@android:string/ok"
</EditText>
+ <EditText android:text="?android:attr/alertDialogStyle" />
</LinearLayout>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
index 9eeb2ea..12ac36f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/eclipse/adt/AdtUtilsTest.java
@@ -115,6 +115,24 @@ public class AdtUtilsTest extends TestCase {
assertNull(null, AdtUtils.capitalize(null));
}
+ public void testCamelCaseToUnderlines() {
+ assertEquals("", AdtUtils.camelCaseToUnderlines(""));
+ assertEquals("foo", AdtUtils.camelCaseToUnderlines("foo"));
+ assertEquals("foo", AdtUtils.camelCaseToUnderlines("Foo"));
+ assertEquals("foo_bar", AdtUtils.camelCaseToUnderlines("FooBar"));
+ assertEquals("test_xml", AdtUtils.camelCaseToUnderlines("testXML"));
+ assertEquals("test_foo", AdtUtils.camelCaseToUnderlines("testFoo"));
+ }
+
+ public void testUnderlinesToCamelCase() {
+ assertEquals("", AdtUtils.underlinesToCamelCase(""));
+ assertEquals("", AdtUtils.underlinesToCamelCase("_"));
+ assertEquals("Foo", AdtUtils.underlinesToCamelCase("foo"));
+ assertEquals("FooBar", AdtUtils.underlinesToCamelCase("foo_bar"));
+ assertEquals("FooBar", AdtUtils.underlinesToCamelCase("foo__bar"));
+ assertEquals("Foo", AdtUtils.underlinesToCamelCase("foo_"));
+ }
+
public void testStripSuffix() {
assertEquals("Foo", AdtUtils.stripSuffix("Foo", ""));
assertEquals("Fo", AdtUtils.stripSuffix("Foo", "o"));
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index 2ec32eb..dbc2cb8 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -127,6 +127,7 @@ ADT_DEST="sdk/eclipse/plugins/com.android.ide.eclipse.adt/libs"
ADT_LIBS="ant-glob assetstudio ide_common layoutlib_api lint_api lint_checks ninepatch propertysheet rule_api sdkuilib swtmenubar manifmerger"
ADT_PREBUILTS="\
prebuilts/misc/common/kxml2/kxml2-2.3.0.jar \
+ prebuilts/tools/common/freemarker/freemarker-2.3.19.jar \
prebuilts/tools/common/asm-tools/asm-4.0.jar \
prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
prebuilts/tools/common/lombok-ast/lombok-ast-0.2.jar"