diff options
45 files changed, 2011 insertions, 372 deletions
diff --git a/anttasks/src/com/android/ant/AidlExecTask.java b/anttasks/src/com/android/ant/AidlExecTask.java index 1315dd7..5a39436 100644 --- a/anttasks/src/com/android/ant/AidlExecTask.java +++ b/anttasks/src/com/android/ant/AidlExecTask.java @@ -31,11 +31,15 @@ import java.util.List; /** * Task to execute aidl. * <p> - * It expects 3 attributes:<br> + * It expects 5 attributes:<br> * 'executable' ({@link Path} with a single path) for the location of the aidl executable<br> * 'framework' ({@link Path} with a single path) for the "preprocessed" file containing all the * parcelables exported by the framework<br> * 'genFolder' ({@link Path} with a single path) for the location of the gen folder. + * 'aidlOutFolder' ({@link Path} with a single path) for the location of the bin/aidl folder to + * copy the aidl files. + * 'libraryBinAidlFolderPathRefid' the name of the reference to a path object that contains + * libraries aidl output folder. * * It also expects one or more inner elements called "source" which are identical to {@link Path} * elements. @@ -44,7 +48,7 @@ public class AidlExecTask extends MultiFilesTask { private String mExecutable; private String mFramework; - private Path mLibraryBinFolderPath; + private Path mLibraryBinAidlFolderPath; private String mGenFolder; private final ArrayList<Path> mPaths = new ArrayList<Path>(); private String mAidlOutFolder; @@ -75,8 +79,8 @@ public class AidlExecTask extends MultiFilesTask { } // add all the library aidl folders to access parcelables that are in libraries - if (mLibraryBinFolderPath != null) { - for (String importFolder : mLibraryBinFolderPath.list()) { + if (mLibraryBinAidlFolderPath != null) { + for (String importFolder : mLibraryBinAidlFolderPath.list()) { task.createArg().setValue("-I" + importFolder); } } @@ -147,10 +151,10 @@ public class AidlExecTask extends MultiFilesTask { mFramework = TaskHelper.checkSinglePath("framework", value); } - public void setLibraryBinFolderPathRefid(String libraryBinFolderPathRefid) { - Object libBinRef = getProject().getReference(libraryBinFolderPathRefid); - if (libBinRef instanceof Path) { - mLibraryBinFolderPath = (Path) libBinRef; + public void setLibraryBinAidlFolderPathRefid(String libraryBinAidlFolderPathRefid) { + Object libBinAidlRef = getProject().getReference(libraryBinAidlFolderPathRefid); + if (libBinAidlRef instanceof Path) { + mLibraryBinAidlFolderPath = (Path) libBinAidlRef; } } @@ -158,7 +162,7 @@ public class AidlExecTask extends MultiFilesTask { mGenFolder = TaskHelper.checkSinglePath("genFolder", value); } - public void setaidlOutFolder(Path value) { + public void setAidlOutFolder(Path value) { mAidlOutFolder = TaskHelper.checkSinglePath("aidlOutFolder", value); } diff --git a/anttasks/src/com/android/ant/ComputeDependencyTask.java b/anttasks/src/com/android/ant/ComputeDependencyTask.java index 62572c8..512f425 100644 --- a/anttasks/src/com/android/ant/ComputeDependencyTask.java +++ b/anttasks/src/com/android/ant/ComputeDependencyTask.java @@ -58,7 +58,7 @@ public class ComputeDependencyTask extends GetLibraryListTask { private String mLibraryPackagesOut; private String mJarLibraryPathOut; private String mLibraryNativeFolderPathOut; - private String mLibraryBinFolderPathOut; + private String mLibraryBinAidlFolderPathOut; private int mTargetApi = -1; private boolean mVerbose = false; @@ -78,8 +78,8 @@ public class ComputeDependencyTask extends GetLibraryListTask { mJarLibraryPathOut = jarLibraryPathOut; } - public void setLibraryBinFolderPathOut(String libraryBinFolderPathOut) { - mLibraryBinFolderPathOut = libraryBinFolderPathOut; + public void setLibraryBinAidlFolderPathOut(String libraryBinAidlFolderPathOut) { + mLibraryBinAidlFolderPathOut = libraryBinAidlFolderPathOut; } public void setLibraryNativeFolderPathOut(String libraryNativeFolderPathOut) { @@ -115,7 +115,7 @@ public class ComputeDependencyTask extends GetLibraryListTask { if (mLibraryNativeFolderPathOut == null) { throw new BuildException("Missing attribute libraryNativeFolderPathOut"); } - if (mLibraryBinFolderPathOut == null) { + if (mLibraryBinAidlFolderPathOut == null) { throw new BuildException("Missing attribute libraryBinFolderPathOut"); } if (mTargetApi == -1) { @@ -131,7 +131,7 @@ public class ComputeDependencyTask extends GetLibraryListTask { final Path manifestFilePath = new Path(antProject); final Path resFolderPath = new Path(antProject); final Path nativeFolderPath = new Path(antProject); - final Path binFolderPath = new Path(antProject); + final Path binAidlFolderPath = new Path(antProject); final StringBuilder packageStrBuilder = new StringBuilder(); LibraryProcessorFor3rdPartyJars processor = new LibraryProcessorFor3rdPartyJars() { @@ -143,24 +143,30 @@ public class ComputeDependencyTask extends GetLibraryListTask { // get the AndroidManifest.xml path. // FIXME: support renamed location. PathElement element = manifestFilePath.createPathElement(); - element.setPath(libRootPath + "/" + SdkConstants.FN_ANDROID_MANIFEST_XML); + element.setPath(libRootPath + '/' + SdkConstants.FN_ANDROID_MANIFEST_XML); // get the res path. $PROJECT/res as well as the crunch cache. // FIXME: support renamed folders. element = resFolderPath.createPathElement(); - element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT + - "/" + SdkConstants.FD_RES); + element.setPath(libRootPath + '/' + SdkConstants.FD_OUTPUT + + '/' + SdkConstants.FD_RES); element = resFolderPath.createPathElement(); - element.setPath(libRootPath + "/" + SdkConstants.FD_RESOURCES); + element.setPath(libRootPath + '/' + SdkConstants.FD_RESOURCES); // get the folder for the native libraries. Always $PROJECT/libs // FIXME: support renamed folder and/or move libs to bin/libs/ element = nativeFolderPath.createPathElement(); - element.setPath(libRootPath + "/" + SdkConstants.FD_NATIVE_LIBS); + element.setPath(libRootPath + '/' + SdkConstants.FD_NATIVE_LIBS); + + // get the bin/aidl folder. $PROJECT/bin/aidl for now + // FIXME: support renamed folder. + element = binAidlFolderPath.createPathElement(); + element.setPath(libRootPath + '/' + SdkConstants.FD_OUTPUT + + '/' + SdkConstants.FD_AIDL); // get the bin folder. $PROJECT/bin for now // FIXME: support renamed folder. - element = binFolderPath.createPathElement(); + element = binAidlFolderPath.createPathElement(); element.setPath(libRootPath + "/" + SdkConstants.FD_OUTPUT + "/" + SdkConstants.FD_AIDL); @@ -211,8 +217,8 @@ public class ComputeDependencyTask extends GetLibraryListTask { System.out.println("API<=15: Adding annotations.jar to the classpath."); jars.add(new File(sdkDir, SdkConstants.FD_TOOLS + - "/" + SdkConstants.FD_SUPPORT + - "/" + SdkConstants.FN_ANNOTATIONS_JAR)); + '/' + SdkConstants.FD_SUPPORT + + '/' + SdkConstants.FN_ANNOTATIONS_JAR)); } @@ -220,7 +226,7 @@ public class ComputeDependencyTask extends GetLibraryListTask { // (the task themselves can handle a ref to an empty Path) antProject.addReference(mLibraryNativeFolderPathOut, nativeFolderPath); antProject.addReference(mLibraryManifestFilePathOut, manifestFilePath); - antProject.addReference(mLibraryBinFolderPathOut, binFolderPath); + antProject.addReference(mLibraryBinAidlFolderPathOut, binAidlFolderPath); // the rest is done only if there's a library. if (hasLibraries) { diff --git a/build/tools.atree b/build/tools.atree index 414f143..afdeb75 100644 --- a/build/tools.atree +++ b/build/tools.atree @@ -66,6 +66,7 @@ bin/lint tools/lint sdk/templates/build.template tools/lib/build.template sdk/files/proguard-project.txt tools/lib/proguard-project.txt sdk/files/proguard-android.txt tools/proguard/proguard-android.txt +sdk/files/proguard-android-optimize.txt tools/proguard/proguard-android-optimize.txt # Ant Build Rules sdk/files/ant tools/ant diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/FindDialog.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/FindDialog.java index 6370be4..fe3f438 100644 --- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/FindDialog.java +++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/FindDialog.java @@ -44,14 +44,29 @@ public class FindDialog extends Dialog { private final IFindTarget mTarget; private Text mSearchText; private String mPreviousSearchText; + private final int mDefaultButtonId; - private final static int FIND_NEXT_ID = IDialogConstants.CLIENT_ID; - private final static int FIND_PREVIOUS_ID = IDialogConstants.CLIENT_ID + 1; + /** Id of the "Find Next" button */ + public static final int FIND_NEXT_ID = IDialogConstants.CLIENT_ID; + + /** Id of the "Find Previous button */ + public static final int FIND_PREVIOUS_ID = IDialogConstants.CLIENT_ID + 1; public FindDialog(Shell shell, IFindTarget target) { + this(shell, target, FIND_PREVIOUS_ID); + } + + /** + * Construct a find dialog. + * @param shell shell to use + * @param target delegate to be invoked on user action + * @param defaultButtonId one of {@code #FIND_NEXT_ID} or {@code #FIND_PREVIOUS_ID}. + */ + public FindDialog(Shell shell, IFindTarget target, int defaultButtonId) { super(shell); mTarget = target; + mDefaultButtonId = defaultButtonId; setShellStyle((getShellStyle() & ~SWT.APPLICATION_MODAL) | SWT.MODELESS); setBlockOnOpen(true); @@ -91,8 +106,11 @@ public class FindDialog extends Dialog { @Override protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false); - mFindNext = createButton(parent, FIND_NEXT_ID, "Find Next", false); - mFindPrevious = createButton(parent, FIND_PREVIOUS_ID, "Find Previous", /* default */ true); + + mFindNext = createButton(parent, FIND_NEXT_ID, "Find Next", + mDefaultButtonId == FIND_NEXT_ID); + mFindPrevious = createButton(parent, FIND_PREVIOUS_ID, "Find Previous", + mDefaultButtonId != FIND_NEXT_ID); mFindNext.setEnabled(false); mFindPrevious.setEnabled(false); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml index 1f402b9..c2e65ea 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml +++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml @@ -145,6 +145,24 @@ <run class="com.android.ide.eclipse.adt.internal.project.ExportNature" /> </runtime> </extension> + <extension + point="org.eclipse.ui.importWizards"> + <category + id="com.android.ide.eclipse.wizards.category" + name="Android" /> + <wizard + canFinishEarly="false" + category="com.android.ide.eclipse.wizards.category" + class="com.android.ide.eclipse.adt.internal.wizards.newproject.ImportProjectWizard" + finalPerspective="org.eclipse.jdt.ui.JavaPerspective" + hasPages="true" + icon="icons/new_adt_project.png" + id="com.android.ide.eclipse.adt.project.ImportProjectWizard" + name="Existing Android code into workspace" + preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" + project="true"> + </wizard> + </extension> <extension point="org.eclipse.ui.newWizards"> <category id="com.android.ide.eclipse.wizards.category" @@ -172,7 +190,7 @@ hasPages="true" icon="icons/new_adt_project.png" id="com.android.ide.eclipse.adt.project.NewProjectWizard.Old" - name="Empty Android Project" + name="Android Blank Project" preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" project="true" /> <wizard @@ -202,6 +220,18 @@ <wizard canFinishEarly="false" category="com.android.ide.eclipse.wizards.category" + class="com.android.ide.eclipse.adt.internal.wizards.newproject.ImportProjectWizard" + finalPerspective="org.eclipse.jdt.ui.JavaPerspective" + hasPages="true" + icon="icons/new_adt_project.png" + id="com.android.ide.eclipse.adt.project.ImportProjectWizard.NewPrj" + name="Android Project from Existing Code" + preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" + project="true"> + </wizard> + <wizard + canFinishEarly="false" + category="com.android.ide.eclipse.wizards.category" class="com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard" finalPerspective="org.eclipse.jdt.ui.JavaPerspective" hasPages="true" @@ -238,23 +268,12 @@ <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" + class="com.android.ide.eclipse.adt.internal.wizards.templates.NewActivityWizard" 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" + id="com.android.ide.eclipse.editors.wizards.NewActivityWizard" + name="Android Activity" preferredPerspectives="org.eclipse.jdt.ui.JavaPerspective" project="false" /> <wizard diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java index 04373e1..a1571e2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/common/layout/LinearLayoutRule.java @@ -304,8 +304,9 @@ public class LinearLayoutRule extends BaseLayoutRule { for (IDragElement element : elements) { // This tries to determine if an INode corresponds to an // IDragElement, by comparing their bounds. - if (bc.equals(element.getBounds())) { + if (element.isSame(it)) { isDragged = true; + break; } } 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 2f3e0b1..6368783 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 @@ -27,12 +27,14 @@ import com.android.ide.eclipse.adt.internal.lint.EclipseLintRunner; import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; import com.android.ide.eclipse.adt.internal.sdk.Sdk.TargetChangeListener; import com.android.sdklib.IAndroidTarget; 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.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.ResourcesPlugin; @@ -69,9 +71,12 @@ import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.events.IHyperlinkListener; +import org.eclipse.ui.forms.widgets.FormText; import org.eclipse.ui.ide.IDEActionFactory; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.internal.browser.WorkbenchBrowserSupport; +import org.eclipse.ui.part.MultiPageEditorPart; +import org.eclipse.ui.part.WorkbenchPart; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.wst.sse.core.StructuredModelManager; import org.eclipse.wst.sse.core.internal.provisional.IModelManager; @@ -953,7 +958,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * the hooks should not perform edits on the model without acquiring * a lock first. */ - protected void runEditHooks() { + public void runEditHooks() { if (!mIgnoreXmlUpdate) { // Check for errors, if enabled if (AdtPrefs.getPrefs().isLintOnSave()) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java new file mode 100644 index 0000000..7a41b5b --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DelegatingAction.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.editors.layout.gle2; + +import com.android.annotations.NonNull; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.action.IMenuCreator; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.swt.events.HelpListener; +import org.eclipse.swt.widgets.Event; + +/** + * Implementation of {@link IAction} which delegates to a different + * {@link IAction} which allows a subclass to wrap and customize some of the + * behavior of a different action + */ +public class DelegatingAction implements IAction { + private final IAction mAction; + + /** + * Construct a new delegate of the given action + * + * @param action the action to be delegated + */ + public DelegatingAction(@NonNull IAction action) { + mAction = action; + } + + @Override + public void addPropertyChangeListener(IPropertyChangeListener listener) { + mAction.addPropertyChangeListener(listener); + } + + @Override + public int getAccelerator() { + return mAction.getAccelerator(); + } + + @Override + public String getActionDefinitionId() { + return mAction.getActionDefinitionId(); + } + + @Override + public String getDescription() { + return mAction.getDescription(); + } + + @Override + public ImageDescriptor getDisabledImageDescriptor() { + return mAction.getDisabledImageDescriptor(); + } + + @Override + public HelpListener getHelpListener() { + return mAction.getHelpListener(); + } + + @Override + public ImageDescriptor getHoverImageDescriptor() { + return mAction.getHoverImageDescriptor(); + } + + @Override + public String getId() { + return mAction.getId(); + } + + @Override + public ImageDescriptor getImageDescriptor() { + return mAction.getImageDescriptor(); + } + + @Override + public IMenuCreator getMenuCreator() { + return mAction.getMenuCreator(); + } + + @Override + public int getStyle() { + return mAction.getStyle(); + } + + @Override + public String getText() { + return mAction.getText(); + } + + @Override + public String getToolTipText() { + return mAction.getToolTipText(); + } + + @Override + public boolean isChecked() { + return mAction.isChecked(); + } + + @Override + public boolean isEnabled() { + return mAction.isEnabled(); + } + + @Override + public boolean isHandled() { + return mAction.isHandled(); + } + + @Override + public void removePropertyChangeListener(IPropertyChangeListener listener) { + mAction.removePropertyChangeListener(listener); + } + + @Override + public void run() { + mAction.run(); + } + + @Override + public void runWithEvent(Event event) { + mAction.runWithEvent(event); + } + + @Override + public void setActionDefinitionId(String id) { + mAction.setActionDefinitionId(id); + } + + @Override + public void setChecked(boolean checked) { + mAction.setChecked(checked); + } + + @Override + public void setDescription(String text) { + mAction.setDescription(text); + } + + @Override + public void setDisabledImageDescriptor(ImageDescriptor newImage) { + mAction.setDisabledImageDescriptor(newImage); + } + + @Override + public void setEnabled(boolean enabled) { + mAction.setEnabled(enabled); + } + + @Override + public void setHelpListener(HelpListener listener) { + mAction.setHelpListener(listener); + } + + @Override + public void setHoverImageDescriptor(ImageDescriptor newImage) { + mAction.setHoverImageDescriptor(newImage); + } + + @Override + public void setId(String id) { + mAction.setId(id); + } + + @Override + public void setImageDescriptor(ImageDescriptor newImage) { + mAction.setImageDescriptor(newImage); + } + + @Override + public void setMenuCreator(IMenuCreator creator) { + mAction.setMenuCreator(creator); + } + + @Override + public void setText(String text) { + mAction.setText(text); + } + + @Override + public void setToolTipText(String text) { + mAction.setToolTipText(text); + } + + @Override + public void setAccelerator(int keycode) { + mAction.setAccelerator(keycode); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java index d8a45b6..e2c1d5e 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GestureManager.java @@ -717,6 +717,7 @@ public class GestureManager { // operation. List<SelectionItem> selections = selectionManager.getSelections(); mDragSelection.clear(); + SelectionItem primary = null; if (!selections.isEmpty()) { // Is the cursor on top of a selected element? @@ -724,6 +725,7 @@ public class GestureManager { for (SelectionItem cs : selections) { if (!cs.isRoot() && cs.getRect().contains(p.x, p.y)) { + primary = cs; insideSelection = true; break; } @@ -732,7 +734,7 @@ public class GestureManager { if (!insideSelection) { CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); if (vi != null && !vi.isRoot() && !vi.isHidden()) { - selectionManager.selectSingle(vi); + primary = selectionManager.selectSingle(vi); insideSelection = true; } } @@ -753,6 +755,8 @@ public class GestureManager { for (SelectionItem cs : selections) { if (!cs.isRoot() && !cs.isHidden()) { mDragSelection.add(cs); + } else if (cs == primary) { + primary = null; } } } @@ -763,7 +767,7 @@ public class GestureManager { if (mDragSelection.isEmpty()) { CanvasViewInfo vi = mCanvas.getViewHierarchy().findViewInfoAt(p); if (vi != null && !vi.isRoot() && !vi.isHidden()) { - selectionManager.selectSingle(vi); + primary = selectionManager.selectSingle(vi); mDragSelection.addAll(selections); } } @@ -773,7 +777,7 @@ public class GestureManager { e.doit = !mDragSelection.isEmpty(); int imageCount = mDragSelection.size(); if (e.doit) { - mDragElements = SelectionItem.getAsElements(mDragSelection); + mDragElements = SelectionItem.getAsElements(mDragSelection, primary); GlobalCanvasDragInfo.getInstance().startDrag(mDragElements, mDragSelection.toArray(new SelectionItem[imageCount]), mCanvas, new Runnable() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java index 06986cd..b918b00 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/GlobalCanvasDragInfo.java @@ -16,6 +16,8 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.api.IViewRule; import com.android.ide.common.api.Rect; @@ -63,6 +65,10 @@ final class GlobalCanvasDragInfo { * Registers the XML elements being dragged. * * @param elements The elements being dragged + * @param primary the "primary" element among the elements; when there is a + * single item dragged this will be the same, but in + * multi-selection it will be the element under the mouse as the + * selection was initiated * @param selection The selection (which can be null, for example when the * user drags from the palette) * @param sourceCanvas An object representing the source we are dragging @@ -71,8 +77,11 @@ final class GlobalCanvasDragInfo { * source. It should only be invoked if the drag operation is a * move, not a copy. */ - public void startDrag(SimpleElement[] elements, SelectionItem[] selection, - Object sourceCanvas, Runnable removeSourceHandler) { + public void startDrag( + @NonNull SimpleElement[] elements, + @Nullable SelectionItem[] selection, + @Nullable Object sourceCanvas, + @Nullable Runnable removeSourceHandler) { mCurrentElements = elements; mCurrentSelection = selection; mSourceCanvas = sourceCanvas; @@ -93,6 +102,7 @@ final class GlobalCanvasDragInfo { } /** Returns the elements being dragged. */ + @NonNull public SimpleElement[] getCurrentElements() { return mCurrentElements; } 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 d079ff4..3704d8f 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 @@ -190,6 +190,7 @@ public class ImageOverlay extends Overlay implements IImageFactory { public synchronized void paint(GC gc) { if (mImage != null) { boolean valid = mCanvas.getViewHierarchy().isValid(); + mCanvas.ensureZoomed(); if (!valid) { gc_setAlpha(gc, 128); // half-transparent } 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 371852c..f218069 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 @@ -34,6 +34,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.gre.ViewMetadataRepos 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.editors.uimodel.UiElementNode; +import com.android.ide.eclipse.adt.internal.lint.LintEditAction; import com.android.resources.Density; import com.android.sdklib.SdkConstants; import com.android.util.XmlUtils; @@ -180,6 +181,12 @@ public class LayoutCanvas extends Canvas { /** Copy action for the Edit or context menu. */ private Action mCopyAction; + /** Undo action: delegates to the text editor */ + private IAction mUndoAction; + + /** Redo action: delegates to the text editor */ + private IAction mRedoAction; + /** Root of the context menu. */ private MenuManager mMenuManager; @@ -287,11 +294,25 @@ public class LayoutCanvas extends Canvas { public void controlResized(ControlEvent e) { super.controlResized(e); - mHScale.setClientSize(getClientArea().width); - mVScale.setClientSize(getClientArea().height); + // Check editor state: + LayoutWindowCoordinator coordinator = LayoutWindowCoordinator.get(); + if (coordinator != null) { + IEditorSite editorSite = getEditorDelegate().getEditor().getEditorSite(); + coordinator.syncMaximizedState(editorSite.getPage()); + } + + Rectangle clientArea = getClientArea(); + mHScale.setClientSize(clientArea.width); + mVScale.setClientSize(clientArea.height); // Update the zoom level in the canvas when you toggle the zoom - getDisplay().asyncExec(mZoomCheck); + if (coordinator != null) { + mZoomCheck.run(); + } else { + // During startup, delay updates which can trigger further layout + getDisplay().asyncExec(mZoomCheck); + + } } }); @@ -329,14 +350,19 @@ public class LayoutCanvas extends Canvas { return; } - IEditorPart editor = getEditorDelegate().getEditor(); - IWorkbenchPage page = editor.getSite().getPage(); - Boolean zoomed = page.isPageZoomed(); - if (mWasZoomed != zoomed) { - if (mWasZoomed != null) { - setFitScale(true /*onlyZoomOut*/); + LayoutWindowCoordinator coordinator = LayoutWindowCoordinator.get(); + if (coordinator != null) { + Boolean zoomed = coordinator.isEditorMaximized(); + if (mWasZoomed != zoomed) { + if (mWasZoomed != null) { + LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor() + .getLayoutActionBar(); + if (actionBar.isZoomingAllowed()) { + setFitScale(true /*onlyZoomOut*/); + } + } + mWasZoomed = zoomed; } - mWasZoomed = zoomed; } } }; @@ -605,16 +631,16 @@ public class LayoutCanvas extends Canvas { mEditorDelegate.getGraphicalEditor().setModel(mViewHierarchy.getRoot()); if (image != null) { - mHScale.setSize(image.getImageData().width, getClientArea().width); - mVScale.setSize(image.getImageData().height, getClientArea().height); + Rectangle clientArea = getClientArea(); + mHScale.setSize(image.getImageData().width, clientArea.width); + mVScale.setSize(image.getImageData().height, clientArea.height); if (mZoomFitNextImage) { - mZoomFitNextImage = false; // Must be run asynchronously because getClientArea() returns 0 bounds // when the editor is being initialized getDisplay().asyncExec(new Runnable() { @Override public void run() { - setFitScale(true); + ensureZoomed(); } }); } @@ -624,7 +650,18 @@ public class LayoutCanvas extends Canvas { redraw(); } - /* package */ void setShowOutline(boolean newState) { + void ensureZoomed() { + if (mZoomFitNextImage && getClientArea().height > 0) { + mZoomFitNextImage = false; + LayoutActionBar actionBar = mEditorDelegate.getGraphicalEditor() + .getLayoutActionBar(); + if (actionBar.isZoomingAllowed()) { + setFitScale(true); + } + } + } + + void setShowOutline(boolean newState) { mShowOutline = newState; redraw(); } @@ -1225,6 +1262,21 @@ public class LayoutCanvas extends Canvas { bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), mPasteAction); bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), mDeleteAction); bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), mSelectAllAction); + + // Delegate the Undo and Redo actions to the text editor ones, but wrap them + // such that we run lint to update the results on the current page (this is + // normally done on each editor operation that goes through + // {@link AndroidXmlEditor#wrapUndoEditXmlModel}, but not undo/redo) + if (mUndoAction == null) { + IAction undoAction = editor.getAction(ActionFactory.UNDO.getId()); + mUndoAction = new LintEditAction(undoAction, getEditorDelegate().getEditor()); + } + bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), mUndoAction); + if (mRedoAction == null) { + IAction redoAction = editor.getAction(ActionFactory.REDO.getId()); + mRedoAction = new LintEditAction(redoAction, getEditorDelegate().getEditor()); + } + bars.setGlobalActionHandler(ActionFactory.REDO.getId(), mRedoAction); } else { bars.setGlobalActionHandler(ActionFactory.CUT.getId(), editor.getAction(ActionFactory.CUT.getId())); @@ -1236,13 +1288,12 @@ public class LayoutCanvas extends Canvas { editor.getAction(ActionFactory.DELETE.getId())); bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), editor.getAction(ActionFactory.SELECT_ALL.getId())); + bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), + editor.getAction(ActionFactory.UNDO.getId())); + bars.setGlobalActionHandler(ActionFactory.REDO.getId(), + editor.getAction(ActionFactory.REDO.getId())); } - IAction undoAction = editor.getAction(ActionFactory.UNDO.getId()); - bars.setGlobalActionHandler(ActionFactory.UNDO.getId(), undoAction); - IAction redoAction = editor.getAction(ActionFactory.REDO.getId()); - bars.setGlobalActionHandler(ActionFactory.REDO.getId(), redoAction); - bars.updateActionBars(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java index 6a6f564..a0672c6 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutWindowCoordinator.java @@ -16,10 +16,12 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditorDelegate; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IPartService; import org.eclipse.ui.IViewReference; @@ -73,13 +75,19 @@ public class LayoutWindowCoordinator implements IPartListener2 { */ private boolean mInitialized; + /** Singleton reference */ + private static LayoutWindowCoordinator sSingleton; + /** * Start the coordinator * * @param window the associated window */ public static void start(@NonNull IWorkbenchWindow window) { + assert sSingleton == null; + LayoutWindowCoordinator coordinator = new LayoutWindowCoordinator(window); + sSingleton = coordinator; IPartService service = window.getPartService(); if (service != null) { @@ -88,7 +96,27 @@ public class LayoutWindowCoordinator implements IPartListener2 { } } - private LayoutWindowCoordinator(IWorkbenchWindow window) { + /** + * Returns the coordinator. This method will return null if it is called before + * {@link #start} has been called, and non null after. + * + * @return the coordinator + */ + @Nullable + public static LayoutWindowCoordinator get() { + return sSingleton; + } + + /** + * Returns true if the main editor window is maximized + * + * @return true if the main editor window is maximized + */ + public boolean isEditorMaximized() { + return mEditorMaximized; + } + + private LayoutWindowCoordinator(@NonNull IWorkbenchWindow window) { mWindow = window; initialize(); @@ -122,8 +150,9 @@ public class LayoutWindowCoordinator implements IPartListener2 { mOutlineOpen = true; } } - mEditorMaximized = activePage.isPageZoomed(); - syncActive(); + if (!syncMaximizedState(activePage)) { + syncActive(); + } } static IViewReference findPropertySheetView(IWorkbenchPage activePage) { @@ -135,6 +164,43 @@ public class LayoutWindowCoordinator implements IPartListener2 { } /** + * Checks the maximized state of the page and updates internal state if + * necessary. + * <p> + * This is used in Eclipse 4.x, where the {@link IPartListener2} does not + * fire {@link IPartListener2#partHidden(IWorkbenchPartReference)} when the + * editor is maximized anymore (see issue + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=382120 for details). + * Instead, the layout editor listens for resize events, and upon resize it + * looks up the part state and calls this method to ensure that the right + * maximized state is known to the layout coordinator. + * + * @param page the active workbench page + * @return true if the state changed, false otherwise + */ + public boolean syncMaximizedState(IWorkbenchPage page) { + boolean maximized = isPageZoomed(page); + if (mEditorMaximized != maximized) { + mEditorMaximized = maximized; + syncActive(); + return true; + } + return false; + } + + private boolean isPageZoomed(IWorkbenchPage page) { + IWorkbenchPartReference reference = page.getActivePartReference(); + if (reference != null && reference instanceof IEditorReference) { + int state = page.getPartState(reference); + boolean maximized = (state & IWorkbenchPage.STATE_MAXIMIZED) != 0; + return maximized; + } + + // If the active reference isn't the editor, then the editor can't be maximized + return false; + } + + /** * Syncs the given editor's view state such that the property sheet and or * outline are shown or hidden according to the visibility of the global * outline and property sheet views. @@ -258,8 +324,9 @@ public class LayoutWindowCoordinator implements IPartListener2 { } } + boolean wasMaximized = mEditorMaximized; mEditorMaximized = visibleCount <= 1; - if (mEditorMaximized) { + if (mEditorMaximized && !wasMaximized) { // Only consider -maximizing- the window to be occasion for handling // a "property sheet closed" event as a "show outline. // And in fact we may want to remove it once you re-expose things diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java index 5d49426..d104e37 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionItem.java @@ -169,13 +169,39 @@ class SelectionItem { * @return An array of wrapper elements. Never null. */ @NonNull - static SimpleElement[] getAsElements(List<SelectionItem> items) { - ArrayList<SimpleElement> elements = new ArrayList<SimpleElement>(); + static SimpleElement[] getAsElements(@NonNull List<SelectionItem> items) { + return getAsElements(items, null); + } + + /** + * Returns elements representing the given selection of canvas items. + * + * @param items Items to wrap in elements + * @param primary The primary selected item which should be listed first + * @return An array of wrapper elements. Never null. + */ + @NonNull + static SimpleElement[] getAsElements( + @NonNull List<SelectionItem> items, + @Nullable SelectionItem primary) { + List<SimpleElement> elements = new ArrayList<SimpleElement>(); + + if (primary != null) { + CanvasViewInfo vi = primary.getViewInfo(); + SimpleElement e = vi.toSimpleElement(); + e.setSelectionItem(primary); + elements.add(e); + } for (SelectionItem cs : items) { - CanvasViewInfo vi = cs.getViewInfo(); + if (cs == primary) { + // Already handled + continue; + } + CanvasViewInfo vi = cs.getViewInfo(); SimpleElement e = vi.toSimpleElement(); + e.setSelectionItem(cs); elements.add(e); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java index 1450768..0bad5a5 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SelectionManager.java @@ -21,6 +21,7 @@ import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.Selection import static com.android.ide.eclipse.adt.internal.editors.layout.gle2.SelectionHandle.PIXEL_RADIUS; import com.android.annotations.NonNull; +import com.android.annotations.Nullable; import com.android.ide.common.api.INode; import com.android.ide.common.layout.GridLayoutRule; import com.android.ide.eclipse.adt.internal.editors.descriptors.ElementDescriptor; @@ -429,11 +430,15 @@ public class SelectionManager implements ISelectionProvider { /** * Removes all the currently selected item and only select the given item. - * Issues a {@link #redraw()} if the selection changes. + * Issues a redraw() if the selection changes. * * @param vi The new selected item if non-null. Selection becomes empty if null. + * @return the item selected, or null if the selection was cleared (e.g. vi was null) */ - /* package */ void selectSingle(CanvasViewInfo vi) { + @Nullable + SelectionItem selectSingle(CanvasViewInfo vi) { + SelectionItem item = null; + // reset alternate selection if any mAltSelection = null; @@ -449,13 +454,14 @@ public class SelectionManager implements ISelectionProvider { if (!mSelections.isEmpty()) { if (mSelections.size() == 1 && mSelections.getFirst().getViewInfo() == vi) { // CanvasSelection remains the same, don't touch it. - return; + return mSelections.getFirst(); } mSelections.clear(); } if (vi != null) { - mSelections.add(createSelection(vi)); + item = createSelection(vi); + mSelections.add(item); if (vi.isInvisible()) { redoLayout = true; } @@ -467,6 +473,8 @@ public class SelectionManager implements ISelectionProvider { } redraw(); + + return item; } /** Returns true if the view hierarchy is showing exploded items. */ @@ -1039,7 +1047,7 @@ public class SelectionManager implements ISelectionProvider { new ActionContributionItem(a).fill(menu, -1); a.setEnabled(true); - a = selectionManager.new SelectAction("Select None", SELECT_NONE); + a = selectionManager.new SelectAction("Deselect All", SELECT_NONE); new ActionContributionItem(a).fill(menu, -1); a.setEnabled(haveSelection); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java index 4feff25..9acc8c2 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/SimpleElement.java @@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.api.IDragElement; +import com.android.ide.common.api.INode; import com.android.ide.common.api.Rect; import java.util.ArrayList; @@ -47,6 +48,7 @@ public class SimpleElement implements IDragElement { private IDragAttribute[] mCachedAttributes = null; private IDragElement[] mCachedElements = null; + private SelectionItem mSelectionItem; /** * Creates a new {@link SimpleElement} with the specified element name. @@ -141,6 +143,43 @@ public class SimpleElement implements IDragElement { mElements.add(e); } + @Override + public boolean isSame(@NonNull INode node) { + if (mSelectionItem != null) { + return node == mSelectionItem.getNode(); + } else { + return node.getBounds().equals(mBounds); + } + } + + void setSelectionItem(@Nullable SelectionItem selectionItem) { + mSelectionItem = selectionItem; + } + + @Nullable + SelectionItem getSelectionItem() { + return mSelectionItem; + } + + @Nullable + static SimpleElement findPrimary(SimpleElement[] elements, SelectionItem primary) { + if (elements == null || elements.length == 0) { + return null; + } + + if (elements.length == 1 || primary == null) { + return elements[0]; + } + + for (SimpleElement element : elements) { + if (element.getSelectionItem() == primary) { + return element; + } + } + + return elements[0]; + } + // reader and writer methods @Override diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEditAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEditAction.java new file mode 100644 index 0000000..bf05ce0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/lint/LintEditAction.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.ide.eclipse.adt.internal.lint; + +import com.android.annotations.NonNull; +import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DelegatingAction; + +import org.eclipse.jface.action.IAction; +import org.eclipse.swt.widgets.Event; + +/** + * Action intended to wrap an existing XML editor action, and then runs lint after + * the edit. + */ +public class LintEditAction extends DelegatingAction { + private final AndroidXmlEditor mEditor; + + /** + * Creates a new {@link LintEditAction} associated with the given editor to + * wrap the given action + * + * @param action the action to be wrapped + * @param editor the editor associated with the action + */ + public LintEditAction(@NonNull IAction action, @NonNull AndroidXmlEditor editor) { + super(action); + mEditor = editor; + } + + @Override + public void runWithEvent(Event event) { + super.runWithEvent(event); + mEditor.runEditHooks(); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java new file mode 100644 index 0000000..7ccab06 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportPage.java @@ -0,0 +1,381 @@ +/* + * 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.newproject; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.tools.lint.detector.api.LintUtils; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.IColorProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ILabelProviderListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.wizard.IWizardPage; +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.Color; +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.DirectoryDialog; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkingSet; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** WizardPage for importing Android projects */ +class ImportPage extends WizardPage implements SelectionListener, IStructuredContentProvider, + ICheckStateListener, ILabelProvider, IColorProvider { + private final NewProjectWizardState mValues; + private List<ImportedProject> mProjectPaths; + private final IProject[] mExistingProjects; + + private Text mDir; + private Button mBrowseButton; + private Button mCopyCheckBox; + private Button mRefreshButton; + private Button mDeselectAllButton; + private Button mSelectAllButton; + private Table mTable; + private CheckboxTableViewer mCheckboxTableViewer; + private WorkingSetGroup mWorkingSetGroup; + + ImportPage(NewProjectWizardState values) { + super("importPage"); //$NON-NLS-1$ + mValues = values; + setTitle("Import Projects"); + setDescription("Select a directory to search for existing Android projects"); + mWorkingSetGroup = new WorkingSetGroup(); + setWorkingSets(new IWorkingSet[0]); + + // Record all projects such that we can ensure that the project names are unique + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + mExistingProjects = workspaceRoot.getProjects(); + } + + public void init(IStructuredSelection selection, IWorkbenchPart activePart) { + setWorkingSets(WorkingSetHelper.getSelectedWorkingSet(selection, activePart)); + } + + @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 directoryLabel = new Label(container, SWT.NONE); + directoryLabel.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); + directoryLabel.setText("Root Directory:"); + + mDir = new Text(container, SWT.BORDER); + mDir.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); + mDir.addSelectionListener(this); + + mBrowseButton = new Button(container, SWT.NONE); + mBrowseButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mBrowseButton.setText("Browse..."); + mBrowseButton.addSelectionListener(this); + + Label projectsLabel = new Label(container, SWT.NONE); + projectsLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); + projectsLabel.setText("Projects:"); + + mCheckboxTableViewer = CheckboxTableViewer.newCheckList(container, + SWT.BORDER | SWT.FULL_SELECTION); + mTable = mCheckboxTableViewer.getTable(); + mTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 4)); + mTable.addSelectionListener(this); + mCheckboxTableViewer.setLabelProvider(this); + mCheckboxTableViewer.setContentProvider(this); + mCheckboxTableViewer.setInput(this); + mCheckboxTableViewer.addCheckStateListener(this); + + mSelectAllButton = new Button(container, SWT.NONE); + mSelectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mSelectAllButton.setText("Select All"); + mSelectAllButton.addSelectionListener(this); + + mDeselectAllButton = new Button(container, SWT.NONE); + mDeselectAllButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mDeselectAllButton.setText("Deselect All"); + mDeselectAllButton.addSelectionListener(this); + + mRefreshButton = new Button(container, SWT.NONE); + mRefreshButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); + mRefreshButton.setText("Refresh"); + mRefreshButton.addSelectionListener(this); + new Label(container, SWT.NONE); + + mCopyCheckBox = new Button(container, SWT.CHECK); + mCopyCheckBox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); + mCopyCheckBox.setText("Copy projects into workspace"); + mCopyCheckBox.addSelectionListener(this); + + Composite group = mWorkingSetGroup.createControl(container); + group.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + validatePage(); + } + + private void refresh() { + File root = new File(mDir.getText().trim()); + mProjectPaths = searchForProjects(root); + mCheckboxTableViewer.refresh(); + mCheckboxTableViewer.setAllChecked(true); + + List<ImportedProject> selected = new ArrayList<ImportedProject>(); + List<ImportedProject> disabled = new ArrayList<ImportedProject>(); + for (ImportedProject project : mProjectPaths) { + String projectName = project.getProjectName(); + boolean invalid = false; + for (IProject existingProject : mExistingProjects) { + if (projectName.equals(existingProject.getName())) { + invalid = true; + break; + } + } + if (invalid) { + disabled.add(project); + } else { + selected.add(project); + } + } + + mValues.importProjects = selected; + + mCheckboxTableViewer.setGrayedElements(disabled.toArray()); + mCheckboxTableViewer.setCheckedElements(selected.toArray()); + mCheckboxTableViewer.refresh(); + mCheckboxTableViewer.getTable().setFocus(); + validatePage(); + } + + private List<ImportedProject> searchForProjects(File dir) { + List<ImportedProject> projects = new ArrayList<ImportedProject>(); + addProjects(dir, projects); + return projects; + } + + /** Finds all project directories under the given directory */ + private void addProjects(File dir, List<ImportedProject> projects) { + if (dir.isDirectory()) { + if (LintUtils.isProjectDir(dir)) { + projects.add(new ImportedProject(dir)); + } + + File[] children = dir.listFiles(); + if (children != null) { + for (File child : children) { + addProjects(child, projects); + } + } + } + } + + private void validatePage() { + IStatus status = null; + + // Validate project name -- unless we're creating a sample, in which case + // the user will get a chance to pick the name on the Sample page + if (mProjectPaths == null || mProjectPaths.isEmpty()) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + "Select a directory to search for existing Android projects"); + } else if (mValues.importProjects == null || mValues.importProjects.isEmpty()) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + "Select at least one project"); + } else { + for (ImportedProject project : mValues.importProjects) { + if (mCheckboxTableViewer.getGrayed(project)) { + status = new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + String.format("Cannot import %1$s because the project name is in use", + project.getProjectName())); + break; + } + } + } + + // -- update UI & enable finish if there's no error + 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); + } + } + + /** + * Returns the working sets to which the new project should be added. + * + * @return the selected working sets to which the new project should be added + */ + private IWorkingSet[] getWorkingSets() { + return mWorkingSetGroup.getSelectedWorkingSets(); + } + + /** + * Sets the working sets to which the new project should be added. + * + * @param workingSets the initial selected working sets + */ + private void setWorkingSets(IWorkingSet[] workingSets) { + assert workingSets != null; + mWorkingSetGroup.setWorkingSets(workingSets); + } + + @Override + public IWizardPage getNextPage() { + // Sync working set data to the value object, since the WorkingSetGroup + // doesn't let us add listeners to do this lazily + mValues.workingSets = getWorkingSets(); + + return super.getNextPage(); + } + + // ---- Implements SelectionListener ---- + + @Override + public void widgetSelected(SelectionEvent e) { + Object source = e.getSource(); + if (source == mBrowseButton) { + // Choose directory + DirectoryDialog dialog = new DirectoryDialog(getShell(), SWT.OPEN); + String path = mDir.getText().trim(); + if (path.length() > 0) { + dialog.setFilterPath(path); + } + String file = dialog.open(); + if (file != null) { + mDir.setText(file); + refresh(); + } + } else if (source == mSelectAllButton) { + mCheckboxTableViewer.setAllChecked(true); + mValues.importProjects = mProjectPaths; + } else if (source == mDeselectAllButton) { + mCheckboxTableViewer.setAllChecked(false); + mValues.importProjects = Collections.emptyList(); + } else if (source == mRefreshButton || source == mDir) { + refresh(); + } else if (source == mCopyCheckBox) { + mValues.copyIntoWorkspace = mCopyCheckBox.getSelection(); + } + + validatePage(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + // ---- Implements IStructuredContentProvider ---- + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public Object[] getElements(Object inputElement) { + return mProjectPaths != null ? mProjectPaths.toArray() : new Object[0]; + } + + // ---- Implements ICheckStateListener ---- + + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + // Try to disable other elements that conflict with this + Object[] checked = mCheckboxTableViewer.getCheckedElements(); + List<ImportedProject> selected = new ArrayList<ImportedProject>(checked.length); + for (Object o : checked) { + if (!mCheckboxTableViewer.getGrayed(o)) { + selected.add((ImportedProject) o); + } + } + mValues.importProjects = selected; + validatePage(); + } + + // ---- Implements ILabelProvider ---- + + @Override + public void addListener(ILabelProviderListener listener) { + } + + @Override + public void removeListener(ILabelProviderListener listener) { + } + + @Override + public boolean isLabelProperty(Object element, String property) { + return false; + } + + @Override + public Image getImage(Object element) { + return null; + } + + @Override + public String getText(Object element) { + ImportedProject file = (ImportedProject) element; + return String.format("%1$s (%2$s)", file.getProjectName(), file.getLocation().getPath()); + } + + // ---- IColorProvider ---- + + @Override + public Color getForeground(Object element) { + Display display = mTable.getDisplay(); + if (mCheckboxTableViewer.getGrayed(element)) { + return display.getSystemColor(SWT.COLOR_DARK_GRAY); + } + + return display.getSystemColor(SWT.COLOR_LIST_FOREGROUND); + } + + @Override + public Color getBackground(Object element) { + return mTable.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java new file mode 100644 index 0000000..9aaf574 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportProjectWizard.java @@ -0,0 +1,83 @@ +/* + * 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.newproject; + +import static com.android.sdklib.SdkConstants.FN_PROJECT_PROGUARD_FILE; +import static com.android.sdklib.SdkConstants.OS_SDK_TOOLS_LIB_FOLDER; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.wizards.newproject.NewProjectWizardState.Mode; + +import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction; +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 java.io.File; + + +/** + * An "Import Android Project" wizard. + */ +public class ImportProjectWizard extends Wizard implements INewWizard { + private static final String PROJECT_LOGO_LARGE = "icons/android-64.png"; //$NON-NLS-1$ + + private NewProjectWizardState mValues; + private ImportPage mImportPage; + + /** Constructs a new wizard default project wizard */ + public ImportProjectWizard() { + } + + @Override + public void addPages() { + mValues = new NewProjectWizardState(Mode.ANY); + mImportPage = new ImportPage(mValues); + addPage(mImportPage); + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + setHelpAvailable(false); // TODO have help + ImageDescriptor desc = AdtPlugin.getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + } + + @Override + public boolean performFinish() { + File file = new File(AdtPlugin.getOsSdkFolder(), OS_SDK_TOOLS_LIB_FOLDER + File.separator + + FN_PROJECT_PROGUARD_FILE); + if (!file.exists()) { + AdtPlugin.displayError("Tools Out of Date?", + String.format("It looks like you do not have the latest version of the " + + "SDK Tools installed. Make sure you update via the SDK Manager " + + "first. (Could not find %1$s)", file.getPath())); + return false; + } + + NewProjectCreator creator = new NewProjectCreator(mValues, getContainer()); + if (!(creator.createAndroidProjects())) { + return false; + } + + // Open the default Java Perspective + OpenJavaPerspectiveAction action = new OpenJavaPerspectiveAction(); + action.run(); + return true; + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java new file mode 100644 index 0000000..dc2f1b5 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/ImportedProject.java @@ -0,0 +1,200 @@ +/* + * 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.newproject; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.sdk.Sdk; +import com.android.io.FolderWrapper; +import com.android.sdklib.AndroidVersion; +import com.android.sdklib.IAndroidTarget; +import com.android.sdklib.internal.project.ProjectProperties; +import com.android.sdklib.internal.project.ProjectProperties.PropertyType; +import com.android.sdklib.xml.AndroidManifestParser; +import com.android.sdklib.xml.ManifestData; +import com.android.sdklib.xml.ManifestData.Activity; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.xml.sax.SAXException; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** An Android project to be imported */ +class ImportedProject { + private final File mLocation; + private String mActivityName; + private ManifestData mManifest; + private String mProjectName; + + ImportedProject(File location) { + super(); + mLocation = location; + } + + File getLocation() { + return mLocation; + } + + @Nullable + ManifestData getManifest() { + if (mManifest == null) { + try { + mManifest = AndroidManifestParser.parse(new FolderWrapper(mLocation)); + } catch (SAXException e) { + // Some sort of error in the manifest file: report to the user in a better way? + AdtPlugin.log(e, null); + return null; + } catch (Exception e) { + AdtPlugin.log(e, null); + return null; + } + } + + return mManifest; + } + + @Nullable + public String getActivityName() { + if (mActivityName == null) { + // Compute the project name and the package name from the manifest + ManifestData manifest = getManifest(); + if (manifest != null) { + if (manifest.getLauncherActivity() != null) { + mActivityName = manifest.getLauncherActivity().getName(); + } + if (mActivityName == null || mActivityName.isEmpty()) { + Activity[] activities = manifest.getActivities(); + for (Activity activity : activities) { + mActivityName = activity.getName(); + if (mActivityName != null && !mActivityName.isEmpty()) { + break; + } + } + } + } + } + + return mActivityName; + } + + @NonNull + public String getProjectName() { + if (mProjectName == null) { + String activityName = getActivityName(); + if (activityName == null || activityName.isEmpty()) { + // I could also look at the build files, say build.xml from ant, and + // try to glean the project name from there + mProjectName = mLocation.getName(); + } else { + // Try to derive it from the activity name: + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + IStatus nameStatus = workspace.validateName(activityName, IResource.PROJECT); + if (nameStatus.isOK()) { + mProjectName = activityName; + } else { + // Try to derive it by escaping characters + StringBuilder sb = new StringBuilder(); + for (int i = 0, n = activityName.length(); i < n; i++) { + char c = activityName.charAt(i); + if (c != IPath.DEVICE_SEPARATOR && c != IPath.SEPARATOR && c != '\\') { + sb.append(c); + } + } + if (sb.length() == 0) { + mProjectName = mLocation.getName(); + } else { + mProjectName = sb.toString(); + } + } + } + } + + return mProjectName; + } + + public IAndroidTarget getTarget() { + // Pick a target: + // First try to find the one requested by project.properties + IAndroidTarget[] targets = Sdk.getCurrent().getTargets(); + ProjectProperties properties = ProjectProperties.load(mLocation.getPath(), + PropertyType.PROJECT); + if (properties != null) { + String targetProperty = properties.getProperty(ProjectProperties.PROPERTY_TARGET); + if (targetProperty != null) { + Matcher m = Pattern.compile("android-(.+)").matcher( //$NON-NLS-1$ + targetProperty.trim()); + if (m.matches()) { + String targetName = m.group(1); + int targetLevel; + try { + targetLevel = Integer.parseInt(targetName); + } catch (NumberFormatException nufe) { + // pass + targetLevel = -1; + } + for (IAndroidTarget t : targets) { + AndroidVersion version = t.getVersion(); + if (version.isPreview() && targetName.equals(version.getCodename())) { + return t; + } else if (targetLevel == version.getApiLevel()) { + return t; + } + } + if (targetLevel > 0) { + // If not found, pick the closest one that is higher than the + // api level + IAndroidTarget target = targets[targets.length - 1]; + int targetDelta = target.getVersion().getApiLevel() - targetLevel; + for (IAndroidTarget t : targets) { + int newDelta = t.getVersion().getApiLevel() - targetLevel; + if (newDelta >= 0 && newDelta < targetDelta) { + targetDelta = newDelta; + target = t; + } + } + + return target; + } + } + } + } + + // If not found, pick the closest one to the one requested by the + // project (in project.properties) that is still >= the minSdk version + IAndroidTarget target = targets[targets.length - 1]; + ManifestData manifest = getManifest(); + if (manifest != null) { + int minSdkLevel = manifest.getMinSdkVersion(); + int targetDelta = target.getVersion().getApiLevel() - minSdkLevel; + for (IAndroidTarget t : targets) { + int newDelta = t.getVersion().getApiLevel() - minSdkLevel; + if (newDelta >= 0 && newDelta < targetDelta) { + targetDelta = newDelta; + target = t; + } + } + } + + return target; + } +}
\ No newline at end of file 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 2dc7c71..41c88d7 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 @@ -34,6 +34,7 @@ import com.android.io.StreamException; import com.android.resources.Density; import com.android.sdklib.IAndroidTarget; import com.android.sdklib.SdkConstants; +import com.android.sdklib.xml.ManifestData; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; @@ -78,7 +79,9 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -102,6 +105,7 @@ public class NewProjectCreator { private static final String PARAM_STRING_CONTENT = "STRING_CONTENT"; //$NON-NLS-1$ private static final String PARAM_IS_NEW_PROJECT = "IS_NEW_PROJECT"; //$NON-NLS-1$ private static final String PARAM_SAMPLE_LOCATION = "SAMPLE_LOCATION"; //$NON-NLS-1$ + private static final String PARAM_SOURCE = "SOURCE"; //$NON-NLS-1$ private static final String PARAM_SRC_FOLDER = "SRC_FOLDER"; //$NON-NLS-1$ private static final String PARAM_SDK_TARGET = "SDK_TARGET"; //$NON-NLS-1$ private static final String PARAM_MIN_SDK_VERSION = "MIN_SDK_VERSION"; //$NON-NLS-1$ @@ -197,7 +201,6 @@ public class NewProjectCreator { private final NewProjectWizardState mValues; private final IRunnableContext mRunnableContext; - private Object mPackageName; public NewProjectCreator(NewProjectWizardState values, IRunnableContext runnableContext) { mValues = values; @@ -268,6 +271,10 @@ public class NewProjectCreator { * @return True if the project could be created. */ public boolean createAndroidProjects() { + if (mValues.importProjects != null && !mValues.importProjects.isEmpty()) { + return importProjects(); + } + final ProjectInfo mainData = collectMainPageInfo(); final ProjectInfo testData = collectTestPageInfo(); @@ -275,7 +282,77 @@ public class NewProjectCreator { WorkspaceModifyOperation op = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor monitor) throws InvocationTargetException { - createProjectAsync(monitor, mainData, testData); + createProjectAsync(monitor, mainData, testData, null); + } + }; + + // Run the operation in a different thread + runAsyncOperation(op); + return true; + } + + /** + * Imports a list of projects + */ + private boolean importProjects() { + assert mValues.importProjects != null && !mValues.importProjects.isEmpty(); + IWorkspace workspace = ResourcesPlugin.getWorkspace(); + + final List<ProjectInfo> projectData = new ArrayList<ProjectInfo>(); + for (ImportedProject p : mValues.importProjects) { + + // Compute the project name and the package name from the manifest + ManifestData manifest = p.getManifest(); + if (manifest == null) { + continue; + } + String packageName = manifest.getPackage(); + String projectName = p.getProjectName(); + String minSdk = manifest.getMinSdkVersionString(); + + final IProject project = workspace.getRoot().getProject(projectName); + final IProjectDescription description = + workspace.newProjectDescription(project.getName()); + + final Map<String, Object> parameters = new HashMap<String, Object>(); + parameters.put(PARAM_PROJECT, projectName); + parameters.put(PARAM_PACKAGE, packageName); + parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); + parameters.put(PARAM_IS_NEW_PROJECT, Boolean.FALSE); + parameters.put(PARAM_SRC_FOLDER, SdkConstants.FD_SOURCES); + + parameters.put(PARAM_SDK_TARGET, p.getTarget()); + + // TODO: Find out if these end up getting used in the import-path through the code! + parameters.put(PARAM_MIN_SDK_VERSION, minSdk); + parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); + final HashMap<String, String> dictionary = new HashMap<String, String>(); + dictionary.put(STRING_APP_NAME, mValues.applicationName); + + if (mValues.copyIntoWorkspace) { + parameters.put(PARAM_SOURCE, p.getLocation()); + + // TODO: Make sure it isn't *already* in the workspace! + //IPath defaultLocation = Platform.getLocation(); + //if ((!mValues.useDefaultLocation || mValues.useExisting) + // && !defaultLocation.isPrefixOf(path)) { + //IPath workspaceLocation = Platform.getLocation().append(projectName); + //description.setLocation(workspaceLocation); + // DON'T SET THE LOCATION: It's IMPLIED and in fact it will generate + // an error if you set it! + } else { + // Create in place + description.setLocation(new Path(p.getLocation().getPath())); + } + + projectData.add(new ProjectInfo(project, description, parameters, dictionary)); + } + + // Create a monitored operation to create the actual project + WorkspaceModifyOperation op = new WorkspaceModifyOperation() { + @Override + protected void execute(IProgressMonitor monitor) throws InvocationTargetException { + createProjectAsync(monitor, null, null, projectData); } }; @@ -299,12 +376,9 @@ public class NewProjectCreator { final IProject project = workspace.getRoot().getProject(mValues.projectName); final IProjectDescription description = workspace.newProjectDescription(project.getName()); - // keep some variables to make them available once the wizard closes - mPackageName = mValues.packageName; - final Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put(PARAM_PROJECT, mValues.projectName); - parameters.put(PARAM_PACKAGE, mPackageName); + parameters.put(PARAM_PACKAGE, mValues.packageName); parameters.put(PARAM_APPLICATION, STRING_RSRC_PREFIX + STRING_APP_NAME); parameters.put(PARAM_SDK_TOOLS_DIR, AdtPlugin.getOsSdkToolsFolder()); parameters.put(PARAM_IS_NEW_PROJECT, mValues.mode == Mode.ANY && !mValues.useExisting); @@ -468,7 +542,8 @@ public class NewProjectCreator { */ private void createProjectAsync(IProgressMonitor monitor, ProjectInfo mainData, - ProjectInfo testData) + ProjectInfo testData, + List<ProjectInfo> importData) throws InvocationTargetException { monitor.beginTask("Create Android Project", 100); try { @@ -485,23 +560,12 @@ public class NewProjectCreator { if (mainProject != null) { final IJavaProject javaProject = JavaCore.create(mainProject); - Display.getDefault().syncExec(new Runnable() { - - @Override - public void run() { - IWorkingSet[] workingSets = mValues.workingSets; - if (workingSets.length > 0 && javaProject != null - && javaProject.exists()) { - PlatformUI.getWorkbench().getWorkingSetManager() - .addToWorkingSets(javaProject, workingSets); - } - } - }); + Display.getDefault().syncExec(new WorksetAdder(javaProject, + mValues.workingSets)); } } if (testData != null) { - Map<String, Object> parameters = testData.getParameters(); if (parameters.containsKey(PARAM_TARGET_MAIN) && mainProject != null) { parameters.put(PARAM_REFERENCE_PROJECT, mainProject); @@ -516,18 +580,45 @@ public class NewProjectCreator { null); if (testProject != null) { final IJavaProject javaProject = JavaCore.create(testProject); - Display.getDefault().syncExec(new Runnable() { - - @Override - public void run() { - IWorkingSet[] workingSets = mValues.workingSets; - if (workingSets.length > 0 && javaProject != null - && javaProject.exists()) { - PlatformUI.getWorkbench().getWorkingSetManager() - .addToWorkingSets(javaProject, workingSets); + Display.getDefault().syncExec(new WorksetAdder(javaProject, + mValues.workingSets)); + } + } + + if (importData != null) { + for (final ProjectInfo data : importData) { + ProjectPopulator projectPopulator = null; + if (mValues.copyIntoWorkspace) { + projectPopulator = new ProjectPopulator() { + @Override + public void populate(IProject project) { + // Copy + IFileSystem fileSystem = EFS.getLocalFileSystem(); + File source = (File) data.getParameters().get(PARAM_SOURCE); + IFileStore sourceDir = new ReadWriteFileStore( + fileSystem.getStore(source.toURI())); + IFileStore destDir = new ReadWriteFileStore( + fileSystem.getStore(AdtUtils.getAbsolutePath(project))); + try { + sourceDir.copy(destDir, EFS.OVERWRITE, null); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } } - } - }); + }; + } + IProject project = createEclipseProject( + new SubProgressMonitor(monitor, 50), + data.getProject(), + data.getDescription(), + data.getParameters(), + data.getDictionary(), + projectPopulator); + if (project != null) { + final IJavaProject javaProject = JavaCore.create(project); + Display.getDefault().syncExec(new WorksetAdder(javaProject, + mValues.workingSets)); + } } } } catch (CoreException e) { @@ -541,6 +632,10 @@ public class NewProjectCreator { } } + public interface ProjectPopulator { + public void populate(IProject project); + } + /** * Creates the actual project, sets its nature and adds the required folders * and files to it. This is run asynchronously in a different thread. @@ -558,7 +653,7 @@ public class NewProjectCreator { IProjectDescription description, Map<String, Object> parameters, Map<String, String> dictionary, - Runnable projectPopulator) + ProjectPopulator projectPopulator) throws CoreException, IOException, StreamException { // get the project target @@ -590,7 +685,7 @@ public class NewProjectCreator { } if (projectPopulator != null) { - projectPopulator.run(); + projectPopulator.populate(project); } // Setup class path: mark folders as source folders @@ -672,7 +767,7 @@ public class NewProjectCreator { IProgressMonitor monitor, IProject project, IAndroidTarget target, - Runnable projectPopulator) + ProjectPopulator projectPopulator) throws CoreException, IOException, StreamException { NewProjectCreator creator = new NewProjectCreator(null, null); @@ -1120,7 +1215,7 @@ public class NewProjectCreator { * @throws FileNotFoundException * @throws CoreException */ - private void addLocalFile(IProject project, File source, String destName, + public static void addLocalFile(IProject project, File source, String destName, IProgressMonitor monitor) throws FileNotFoundException, CoreException { IFile dest = project.getFile(destName); if (dest.exists() == false) { @@ -1257,4 +1352,23 @@ public class NewProjectCreator { return str; } + + private static class WorksetAdder implements Runnable { + private final IJavaProject mProject; + private final IWorkingSet[] mWorkingSets; + + private WorksetAdder(IJavaProject project, IWorkingSet[] workingSets) { + mProject = project; + mWorkingSets = workingSets; + } + + @Override + public void run() { + if (mWorkingSets.length > 0 && mProject != null + && mProject.exists()) { + PlatformUI.getWorkbench().getWorkingSetManager() + .addToWorkingSets(mProject, mWorkingSets); + } + } + } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java index 264f09d..1c1fb3a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/newproject/NewProjectWizardState.java @@ -16,6 +16,7 @@ package com.android.ide.eclipse.adt.internal.wizards.newproject; +import com.android.annotations.Nullable; import com.android.ide.eclipse.adt.AdtConstants; import com.android.ide.eclipse.adt.internal.project.AndroidManifestHelper; import com.android.ide.eclipse.adt.internal.sdk.Sdk; @@ -85,7 +86,8 @@ public class NewProjectWizardState { public boolean packageNameModifiedByUser; /** True if a new activity should be created */ - public boolean createActivity = true; + public boolean createActivity; + /** The name of the new activity to be created */ public String activityName; /** True if the activity name has been manually edited by the user */ @@ -163,6 +165,18 @@ public class NewProjectWizardState { public String testTargetPackageName; /** + * Copy project into workspace? This flag only applies when importing + * projects (creating projects from existing source) + */ + public boolean copyIntoWorkspace; + + /** + * List of projects to be imported. Null if not importing projects. + */ + @Nullable + public List<ImportedProject> importProjects; + + /** * Creates a new {@link NewProjectWizardState} * * @param mode the mode to run the wizard in 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 7592c58..7d3114b 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 @@ -83,7 +83,6 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo private Text mProjectNameText; private Text mLocationText; private Button mCreateSampleRadioButton; - private Button mCreateExistingRadioButton; private Button mCreateNewButton; private Button mUseDefaultCheckBox; private Button mBrowseButton; @@ -133,20 +132,14 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo mProjectNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); mProjectNameText.addModifyListener(this); - mCreateNewButton = new Button(container, SWT.RADIO); - mCreateNewButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); - mCreateNewButton.setText("Create new project in workspace"); - mCreateNewButton.addSelectionListener(this); - - mCreateExistingRadioButton = new Button(container, SWT.RADIO); - mCreateExistingRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, - 3, 1)); - mCreateExistingRadioButton.setText("Create project from existing source"); - mCreateExistingRadioButton.addSelectionListener(this); - - // TBD: Should we hide this completely, and make samples something you only invoke - // from the "New Sample Project" wizard? if (mValues.mode != Mode.TEST) { + mCreateNewButton = new Button(container, SWT.RADIO); + mCreateNewButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); + mCreateNewButton.setText("Create new project in workspace"); + mCreateNewButton.addSelectionListener(this); + + // TBD: Should we hide this completely, and make samples something you only invoke + // from the "New Sample Project" wizard? mCreateSampleRadioButton = new Button(container, SWT.RADIO); mCreateSampleRadioButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1)); @@ -192,8 +185,8 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo } if (mValues.mode == Mode.ANY || mValues.mode == Mode.TEST) { if (mValues.useExisting) { - mCreateExistingRadioButton.setSelection(true); - } else { + assert false; // This is now handled by the separate import wizard + } else if (mCreateNewButton != null) { mCreateNewButton.setSelection(true); } } else if (mValues.mode == Mode.SAMPLE) { @@ -271,7 +264,8 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo Object source = e.getSource(); - if (source == mCreateNewButton && mCreateNewButton.getSelection()) { + if (source == mCreateNewButton && mCreateNewButton != null + && mCreateNewButton.getSelection()) { mValues.useExisting = false; if (mValues.mode == Mode.SAMPLE) { // Only reset the mode if we're toggling from sample back to create new @@ -281,24 +275,6 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo mValues.mode = Mode.ANY; } updateLocationState(); - } else if (source == mCreateExistingRadioButton - && mCreateExistingRadioButton.getSelection()) { - mValues.useExisting = true; - if (mValues.mode == Mode.SAMPLE) { - mValues.mode = Mode.ANY; - } - if (!mValues.activityNameModifiedByUser) { - mValues.createActivity = false; - } - try { - mIgnore = true; - mValues.useDefaultLocation = false; - mUseDefaultCheckBox.setSelection(mValues.useDefaultLocation); - } finally { - mIgnore = false; - } - - updateLocationState(); } else if (source == mCreateSampleRadioButton && mCreateSampleRadioButton.getSelection()) { mValues.useExisting = true; mValues.useDefaultLocation = true; @@ -378,7 +354,7 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo * * @return the selected working sets to which the new project should be added */ - public IWorkingSet[] getWorkingSets() { + private IWorkingSet[] getWorkingSets() { return mWorkingSetGroup.getSelectedWorkingSets(); } @@ -387,7 +363,7 @@ public class ProjectNamePage extends WizardPage implements SelectionListener, Mo * * @param workingSets the initial selected working sets */ - public void setWorkingSets(IWorkingSet[] workingSets) { + private void setWorkingSets(IWorkingSet[] workingSets) { assert workingSets != null; mWorkingSetGroup.setWorkingSets(workingSets); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.java new file mode 100644 index 0000000..a2e3518 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewActivityWizard.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.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.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.IProject; +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.IWizardPage; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.INewWizard; +import org.eclipse.ui.IWorkbench; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Wizard for creating new activities. This is a hybrid between a New Project + * Wizard and a New Template Wizard: it has the "Activity selector" page from + * the New Project Wizard, which is used to dynamically select a wizard for the + * second page, but beyond that it runs the normal template wizard when it comes + * time to create the template. + */ +public class NewActivityWizard extends Wizard implements INewWizard { + private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ + + private IWorkbench mWorkbench; + private UpdateToolsPage mUpdatePage; + private NewProjectPage mMainPage; + private NewTemplatePage mTemplatePage; + private ActivityPage mActivityPage; + private NewProjectWizardState mValues; + private NewTemplateWizardState mActivityValues; + + /** Creates a new {@link NewActivityWizard} */ + public NewActivityWizard() { + } + + @Override + public void init(IWorkbench workbench, IStructuredSelection selection) { + mWorkbench = workbench; + + setWindowTitle("New Activity"); + setHelpAvailable(false); + ImageDescriptor desc = IconFactory.getInstance().getImageDescriptor(PROJECT_LOGO_LARGE); + setDefaultPageImageDescriptor(desc); + + if (!UpdateToolsPage.isUpToDate()) { + mUpdatePage = new UpdateToolsPage(); + } + + mValues = new NewProjectWizardState(); + mActivityPage = new ActivityPage(mValues); + + mActivityValues = mValues.activityValues; + List<IProject> projects = AdtUtils.getSelectedProjects(selection); + if (projects.size() == 1) { + mActivityValues.project = projects.get(0); + } + } + + @Override + public void addPages() { + if (mUpdatePage != null) { + addPage(mUpdatePage); + } + + addPage(mActivityPage); + } + + @Override + public IWizardPage getStartingPage() { + if (mUpdatePage != null && mUpdatePage.isPageComplete()) { + return mMainPage; + } + return super.getStartingPage(); + } + + @Override + public IWizardPage getNextPage(IWizardPage page) { + if (page == mActivityPage) { + if (mTemplatePage == null) { + Set<String> hidden = mActivityValues.hidden; + hidden.add(ATTR_PACKAGE_NAME); + hidden.add(ATTR_MIN_API); + hidden.add(ATTR_MIN_API_LEVEL); + hidden.add(ATTR_TARGET_API); + + mTemplatePage = new NewTemplatePage(mActivityValues, true); + addPage(mTemplatePage); + } + return mTemplatePage; + } + + return super.getNextPage(page); + } + + @Override + public boolean canFinish() { + // 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 (mTemplatePage == null || !mTemplatePage.isPageComplete()) { + return false; + } + + return super.canFinish(); + } + + @Override + public boolean performFinish() { + try { + Shell shell = getShell(); + if (shell != null) { + shell.setVisible(false); + } + IProject project = mActivityValues.project; + File outputPath = AdtUtils.getAbsolutePath(project).toFile(); + assert mValues.createActivity; + NewTemplateWizardState activityValues = mValues.activityValues; + Map<String, Object> parameters = activityValues.parameters; + 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()); + TemplateHandler activityTemplate = activityValues.getTemplateHandler(); + activityTemplate.setBackupMergedFiles(false); + activityTemplate.render(outputPath, parameters); + List<String> filesToOpen = activityTemplate.getFilesToOpen(); + + try { + project.refreshLocal(DEPTH_INFINITE, new NullProgressMonitor()); + } catch (CoreException e) { + AdtPlugin.log(e, null); + } + + // Open the primary file/files + NewTemplateWizard.openFiles(project, filesToOpen, mWorkbench); + + return true; + } catch (Exception ioe) { + AdtPlugin.log(ioe, null); + return false; + } + } +} diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java index 869fc2f..b6993c7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectPage.java @@ -18,7 +18,6 @@ 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; @@ -309,11 +308,11 @@ public class NewProjectPage extends WizardPage super.setVisible(visible); // DURING DEVELOPMENT ONLY - if (assertionsEnabled()) { - String uniqueProjectName = AdtUtils.getUniqueProjectName("Test", ""); - mProjectText.setText(uniqueProjectName); - mPackageText.setText("test.pkg"); - } + //if (assertionsEnabled()) { + // String uniqueProjectName = AdtUtils.getUniqueProjectName("Test", ""); + // mProjectText.setText(uniqueProjectName); + // mPackageText.setText("test.pkg"); + //} validatePage(); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java index 5e359a4..1de4b2c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewProjectWizard.java @@ -25,7 +25,9 @@ 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.newproject.NewProjectCreator.ProjectPopulator; import com.android.ide.eclipse.adt.internal.wizards.newxmlfile.NewXmlFileWizard; +import com.android.sdklib.SdkConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -228,9 +230,25 @@ public class NewProjectWizard extends Wizard implements INewWizard { // of the merged files (such as the manifest file) in that case. template.setBackupMergedFiles(false); - Runnable projectPopulator = new Runnable() { + ProjectPopulator projectPopulator = new ProjectPopulator() { @Override - public void run() { + public void populate(IProject project) { + // Copy in the proguard file; templates don't provide this one. + // add the default proguard config + File libFolder = new File(AdtPlugin.getOsSdkToolsFolder(), + SdkConstants.FD_LIB); + try { + assert project == newProject; + NewProjectCreator.addLocalFile(project, + new File(libFolder, SdkConstants.FN_PROJECT_PROGUARD_FILE), + // Write ProGuard config files with the extension .pro which + // is what is used in the ProGuard documentation and samples + SdkConstants.FN_PROJECT_PROGUARD_FILE, + new NullProgressMonitor()); + } catch (Exception e) { + AdtPlugin.log(e, null); + } + // Generate basic output skeleton Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put(ATTR_PACKAGE_NAME, mValues.packageName); 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 index 143db78..28f5ca6 100644 --- 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 @@ -50,11 +50,9 @@ 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 */ + /** Template name and location under $sdk/templates for the default activity */ static final String BLANK_ACTIVITY = "activities/BlankActivity"; //$NON-NLS-1$ - /** Template name and location under /templates in the plugin */ - static final String MASTER_DETAIL_FLOW = "activities/MasterDetailFlow"; //$NON-NLS-1$ - /** Template name and location under /templates in the plugin */ + /** Template name and location under $sdk/templates for the custom view template */ static final String CUSTOM_VIEW = "other/CustomView"; //$NON-NLS-1$ private static final String PROJECT_LOGO_LARGE = "android-64"; //$NON-NLS-1$ @@ -209,42 +207,6 @@ public class NewTemplateWizard extends Wizard implements INewWizard { } /** - * 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 { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java index 1fb73d8..aaf1a6a 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/NewTemplateWizardState.java @@ -89,6 +89,4 @@ public class NewTemplateWizardState { File getTemplateLocation() { return mTemplateLocation; } - - } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java index f22a742..ee6115f 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/wizards/templates/TemplateHandler.java @@ -17,6 +17,7 @@ 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_XML; +import static com.android.sdklib.SdkConstants.FD_EXTRAS; import static com.android.sdklib.SdkConstants.FD_TEMPLATES; import static com.android.sdklib.SdkConstants.FD_TOOLS; @@ -277,7 +278,20 @@ class TemplateHandler { public static File getTemplateRootFolder() { String location = AdtPrefs.getPrefs().getOsSdkFolder(); if (location != null) { - File folder = new File(location, FD_TOOLS + File.separator + FD_TEMPLATES); + File folder = new File(location, FD_TOOLS + File.separator + FD_TEMPLATES); + if (folder.isDirectory()) { + return folder; + } + } + + return null; + } + + @Nullable + public static File getExtraTemplateRootFolder() { + String location = AdtPrefs.getPrefs().getOsSdkFolder(); + if (location != null) { + File folder = new File(location, FD_EXTRAS + File.separator + FD_TEMPLATES); if (folder.isDirectory()) { return folder; } @@ -300,7 +314,6 @@ class TemplateHandler { } return null; - } @Nullable @@ -941,6 +954,17 @@ class TemplateHandler { } } + // Add in templates from extras/ as well. + root = getExtraTemplateRootFolder(); + if (root != null) { + File[] files = new File(root, folder).listFiles(); + if (files != null) { + for (File file : files) { + templates.add(file); + } + } + } + return templates; } } diff --git a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java index fc99764..db69ffe 100644 --- a/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java +++ b/eclipse/plugins/com.android.ide.eclipse.gldebugger/src/com/android/ide/eclipse/gltrace/editors/GLFunctionTraceViewer.java @@ -778,7 +778,9 @@ public class GLFunctionTraceViewer extends EditorPart implements ISelectionProvi return; } - mFindDialog = new FindDialog(Display.getDefault().getActiveShell(), mFindTarget); + mFindDialog = new FindDialog(Display.getDefault().getActiveShell(), + mFindTarget, + FindDialog.FIND_NEXT_ID); mFindDialog.open(); // blocks until find dialog is closed mFindDialog = null; } diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java index eb0a432..5b55532 100644 --- a/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java +++ b/eclipse/plugins/com.android.ide.eclipse.tests/unittests/com/android/ide/common/layout/TestDragElement.java @@ -21,6 +21,7 @@ import static com.android.ide.common.layout.LayoutConstants.ATTR_ID; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.api.IDragElement; +import com.android.ide.common.api.INode; import com.android.ide.common.api.Rect; import java.util.ArrayList; @@ -150,5 +151,8 @@ public class TestDragElement implements IDragElement { + mRect + "]"; } - + @Override + public boolean isSame(INode node) { + return node.getBounds().equals(getBounds()); + } } diff --git a/files/ant/build.xml b/files/ant/build.xml index 542943e..c3073fe 100644 --- a/files/ant/build.xml +++ b/files/ant/build.xml @@ -539,7 +539,7 @@ libraryPackagesOut="project.library.packages" libraryManifestFilePathOut="project.library.manifest.file.path" libraryResFolderPathOut="project.library.res.folder.path" - libraryBinFolderPathOut="project.library.bin.folder.path" + libraryBinAidlFolderPathOut="project.library.bin.aidl.folder.path" libraryNativeFolderPathOut="project.library.native.folder.path" jarLibraryPathOut="project.all.jars.path" targetApi="${project.target.apilevel}" @@ -622,7 +622,7 @@ <echo level="info">Handling aidl files...</echo> <aidl executable="${aidl}" framework="${project.target.framework.aidl}" - libraryBinFolderPathRefid="project.library.bin.folder.path" + libraryBinAidlFolderPathRefid="project.library.bin.aidl.folder.path" genFolder="${gen.absolute.dir}" aidlOutFolder="${out.aidl.absolute.dir}"> <source path="${source.absolute.dir}"/> diff --git a/files/proguard-android-optimize.txt b/files/proguard-android-optimize.txt new file mode 100644 index 0000000..f8f5bcb --- /dev/null +++ b/files/proguard-android-optimize.txt @@ -0,0 +1,64 @@ +# This is a configuration file for ProGuard. +# http://proguard.sourceforge.net/index.html#manual/usage.html + +# Optimizations: If you don't want to optimize, use the +# proguard-android.txt configuration file instead of this one, which +# turns off the optimization flags. Adding optimization introduces +# certain risks, since for example not all optimizations performed by +# ProGuard works on all versions of Dalvik. The following flags turn +# off various optimizations known to have issues, but the list may not +# be complete or up to date. (The "arithmetic" optimization can be +# used if you are only targeting Android 2.0 or later.) Make sure you +# test thoroughly if you go this route. +-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* +-optimizationpasses 5 +-allowaccessmodification +-dontpreverify + +# The remainder of this file is identical to the non-optimized version +# of the Proguard configuration file (except that the other file has +# flags to turn off optimization). + +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-verbose + +-keepattributes *Annotation* +-keep public class com.google.vending.licensing.ILicensingService +-keep public class com.android.vending.licensing.ILicensingService + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames class * { + native <methods>; +} + +# keep setters in Views so that animations can still work. +# see http://proguard.sourceforge.net/manual/examples.html#beans +-keepclassmembers public class * extends android.view.View { + void set*(***); + *** get*(); +} + +# We want to keep methods in Activity that could be used in the XML attribute onClick +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} + +-keepclassmembers class **.R$* { + public static <fields>; +} + +# The support library contains references to newer platform versions. +# Don't warn about those in case this app is linking against an older +# platform version. We know about them, and they are safe. +-dontwarn android.support.** diff --git a/files/proguard-android.txt b/files/proguard-android.txt index 3cc5c8a..fe73bae 100644 --- a/files/proguard-android.txt +++ b/files/proguard-android.txt @@ -10,18 +10,11 @@ # of these optimizations on its own). -dontoptimize -dontpreverify - -# If you want to enable optimization, you should include the -# following: -# -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* -# -optimizationpasses 5 -# -allowaccessmodification -# -# Note that you cannot just include these flags in your own -# configuration file; if you are including this file, optimization -# will be turned off. You'll need to either edit this file, or -# duplicate the contents of this file and remove the include of this -# file from your project's proguard.config path property. +# Note that if you want to enable optimization, you cannot just +# include optimization flags in your own project configuration file; +# instead you will need to point to the +# "proguard-android-optimize.txt" file instead of this one from your +# project.properties file. -keepattributes *Annotation* -keep public class com.google.vending.licensing.ILicensingService diff --git a/ide_common/src/com/android/ide/common/resources/FrameworkResources.java b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java index 381516c..4d8e681 100644..100755 --- a/ide_common/src/com/android/ide/common/resources/FrameworkResources.java +++ b/ide_common/src/com/android/ide/common/resources/FrameworkResources.java @@ -120,34 +120,6 @@ public class FrameworkResources extends ResourceRepository { ResourceType lastType = null; String lastTypeName = ""; - - // Precompute maps from name to ResourceItem such that when we find - // a public item's name we can quickly locate it. Without this, - // it's a linear search for each item, n times -- O(n^2). - // Precomputing a map is O(n) and looking up n times in the map is - // also O(n). - Map<ResourceType, Map<String, ResourceItem>> nameMap = - new HashMap<ResourceType, Map<String, ResourceItem>>(); - for (Entry<ResourceType, List<ResourceItem>> entry: mResourceMap.entrySet()) { - ResourceType type = entry.getKey(); - if (type == ResourceType.PUBLIC || type == ResourceType.DECLARE_STYLEABLE) { - // These are large maps (in android-15 for example the "public" - // ResourceType has 1734 items and declare-styleable has 210) that - // currently have no public exported names. Therefore, don't bother - // creating name lookup maps for these. (However, if by chance a future - // public.xml file does specify these, it will be found by the sequential - // search if map=null below.) - continue; - } - List<ResourceItem> items = entry.getValue(); - int size = items.size(); - Map<String, ResourceItem> map = new HashMap<String, ResourceItem>(size); - for (ResourceItem item : items) { - map.put(item.getName(), item); - } - nameMap.put(type, map); - } - while (true) { int event = parser.next(); if (event == XmlPullParser.START_TAG) { @@ -183,23 +155,9 @@ public class FrameworkResources extends ResourceRepository { } if (type != null) { ResourceItem match = null; - Map<String, ResourceItem> map = nameMap.get(type); + Map<String, ResourceItem> map = mResourceMap.get(type); if (map != null) { match = map.get(name); - } else { - // We skipped computing name maps for some large lists - // that currently don't have any public names, but - // on the off chance that they will show up, leave the - // old iteration based lookup here - List<ResourceItem> typeList = mResourceMap.get(type); - if (typeList != null) { - for (ResourceItem item : typeList) { - if (name.equals(item.getName())) { - match = item; - break; - } - } - } } if (match != null) { @@ -276,3 +234,4 @@ public class FrameworkResources extends ResourceRepository { } } } + diff --git a/ide_common/src/com/android/ide/common/resources/ResourceFolder.java b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java index 03b6eb4..d0d91c7 100644 --- a/ide_common/src/com/android/ide/common/resources/ResourceFolder.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceFolder.java @@ -28,7 +28,9 @@ import com.android.resources.ResourceType; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Resource Folder class. Contains list of {@link ResourceFile}s, @@ -38,10 +40,10 @@ public final class ResourceFolder implements Configurable { final ResourceFolderType mType; final FolderConfiguration mConfiguration; IAbstractFolder mFolder; - ArrayList<ResourceFile> mFiles = null; + List<ResourceFile> mFiles = null; + Map<String, ResourceFile> mNames = null; private final ResourceRepository mRepository; - /** * Creates a new {@link ResourceFolder} * @param type The type of the folder @@ -69,32 +71,13 @@ public final class ResourceFolder implements Configurable { public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind, ScanningContext context) { // look for this file if it's already been created - ResourceFile resFile = getFile(file); + ResourceFile resFile = getFile(file, context); if (resFile == null) { if (kind != ResourceDeltaKind.REMOVED) { // create a ResourceFile for it. - // check if that's a single or multi resource type folder. For now we define this by - // the number of possible resource type output by files in the folder. - // We have a special case for layout/menu folders which can also generate IDs. - // This does - // not make the difference between several resource types from a single file or - // the ability to have 2 files in the same folder generating 2 different types of - // resource. The former is handled by MultiResourceFile properly while we don't - // handle the latter. If we were to add this behavior we'd have to change this call. - List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType); - - if (types.size() == 1) { - resFile = new SingleResourceFile(file, this); - } else if (types.contains(ResourceType.LAYOUT)) { - resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT); - } else if (types.contains(ResourceType.MENU)) { - resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU); - } else { - resFile = new MultiResourceFile(file, this); - } - + resFile = createResourceFile(file); resFile.load(context); // add it to the folder @@ -111,6 +94,29 @@ public final class ResourceFolder implements Configurable { return resFile; } + private ResourceFile createResourceFile(IAbstractFile file) { + // check if that's a single or multi resource type folder. For now we define this by + // the number of possible resource type output by files in the folder. + // We have a special case for layout/menu folders which can also generate IDs. + // This does + // not make the difference between several resource types from a single file or + // the ability to have 2 files in the same folder generating 2 different types of + // resource. The former is handled by MultiResourceFile properly while we don't + // handle the latter. If we were to add this behavior we'd have to change this call. + List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType); + + ResourceFile resFile = null; + if (types.size() == 1) { + resFile = new SingleResourceFile(file, this); + } else if (types.contains(ResourceType.LAYOUT)) { + resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT); + } else if (types.contains(ResourceType.MENU)) { + resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU); + } else { + resFile = new MultiResourceFile(file, this); + } + return resFile; + } /** * Adds a {@link ResourceFile} to the folder. @@ -119,15 +125,68 @@ public final class ResourceFolder implements Configurable { @VisibleForTesting(visibility=Visibility.PROTECTED) public void addFile(ResourceFile file) { if (mFiles == null) { - mFiles = new ArrayList<ResourceFile>(); + int initialSize = 16; + if (mRepository.isFrameworkRepository()) { + String name = mFolder.getName(); + // Pick some reasonable initial sizes for framework data structures + // since they are typically (a) large and (b) their sizes are roughly known + // in advance + switch (mType) { + case DRAWABLE: { + // See if it's one of the -mdpi, -hdpi etc folders which + // are large (~1250 items) + int index = name.indexOf('-'); + if (index == -1) { + initialSize = 230; // "drawable" folder + } else { + index = name.indexOf('-', index + 1); + if (index == -1) { + // One of the "drawable-<density>" folders + initialSize = 1260; + } else { + // "drawable-sw600dp-hdpi" etc + initialSize = 30; + } + } + break; + } + case LAYOUT: { + // The main layout folder has about ~185 layouts in it; + // the others are small + if (name.indexOf('-') == -1) { + initialSize = 200; + } + break; + } + case VALUES: { + if (name.indexOf('-') == -1) { + initialSize = 32; + } else { + initialSize = 4; + } + break; + } + case ANIM: initialSize = 85; break; + case COLOR: initialSize = 32; break; + case RAW: initialSize = 4; break; + default: + // Stick with the 16 default + break; + } + } + + mFiles = new ArrayList<ResourceFile>(initialSize); + mNames = new HashMap<String, ResourceFile>(initialSize, 2.0f); } mFiles.add(file); + mNames.put(file.getFile().getName(), file); } protected void removeFile(ResourceFile file, ScanningContext context) { file.dispose(context); mFiles.remove(file); + mNames.remove(file.getFile().getName()); } protected void dispose(ScanningContext context) { @@ -137,6 +196,7 @@ public final class ResourceFolder implements Configurable { } mFiles.clear(); + mNames.clear(); } } @@ -191,22 +251,43 @@ public final class ResourceFolder implements Configurable { * @param name the name of the file. */ public boolean hasFile(String name) { + if (mNames.containsKey(name)) { + return true; + } + + // Note: mNames.containsKey(name) is faster, but doesn't give the same result; this + // method seems to be called on this ResourceFolder before it has been processed, + // so we need to use the file system check instead: return mFolder.hasFile(name); } /** * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object. + * * @param file The {@link IAbstractFile} object. + * @param context a context object with state for the current update, such + * as a place to stash errors encountered * @return the {@link ResourceFile} or null if no match was found. */ - private ResourceFile getFile(IAbstractFile file) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - if (f.getFile().equals(file)) { - return f; - } + private ResourceFile getFile(IAbstractFile file, ScanningContext context) { + assert mFolder.equals(file.getParentFolder()); + + if (mNames != null) { + ResourceFile resFile = mNames.get(file.getName()); + if (resFile != null) { + return resFile; } } + + // If the file actually exists, the resource folder may not have been + // scanned yet; add it lazily + if (file.exists()) { + ResourceFile resFile = createResourceFile(file); + resFile.load(context); + addFile(resFile); + return resFile; + } + return null; } @@ -216,13 +297,23 @@ public final class ResourceFolder implements Configurable { * @return the {@link ResourceFile} or <code>null</code> if no match was found. */ public ResourceFile getFile(String filename) { - if (mFiles != null) { - for (ResourceFile f : mFiles) { - if (f.getFile().getName().equals(filename)) { - return f; - } + if (mNames != null) { + ResourceFile resFile = mNames.get(filename); + if (resFile != null) { + return resFile; } } + + // If the file actually exists, the resource folder may not have been + // scanned yet; add it lazily + IAbstractFile file = mFolder.getFile(filename); + if (file != null && file.exists()) { + ResourceFile resFile = createResourceFile(file); + resFile.load(new ScanningContext(mRepository)); + addFile(resFile); + return resFile; + } + return null; } diff --git a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java index 3040dc0..7e0338f 100644..100755 --- a/ide_common/src/com/android/ide/common/resources/ResourceRepository.java +++ b/ide_common/src/com/android/ide/common/resources/ResourceRepository.java @@ -36,8 +36,10 @@ import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @@ -58,11 +60,12 @@ public abstract class ResourceRepository { protected final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap = new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class); - protected final Map<ResourceType, List<ResourceItem>> mResourceMap = - new EnumMap<ResourceType, List<ResourceItem>>(ResourceType.class); + protected final Map<ResourceType, Map<String, ResourceItem>> mResourceMap = + new EnumMap<ResourceType, Map<String, ResourceItem>>( + ResourceType.class); - private final Map<List<ResourceItem>, List<ResourceItem>> mReadOnlyListMap = - new IdentityHashMap<List<ResourceItem>, List<ResourceItem>>(); + private final Map<Map<String, ResourceItem>, Collection<ResourceItem>> mReadOnlyListMap = + new IdentityHashMap<Map<String, ResourceItem>, Collection<ResourceItem>>(); private final boolean mFrameworkRepository; @@ -193,13 +196,13 @@ public abstract class ResourceRepository { * @return true if the resource is known */ public boolean hasResourceItem(ResourceType type, String name) { - List<ResourceItem> list = mResourceMap.get(type); + Map<String, ResourceItem> map = mResourceMap.get(type); - if (list != null) { - for (ResourceItem item : list) { - if (name.equals(item.getName())) { - return true; - } + if (map != null) { + + ResourceItem resourceItem = map.get(name); + if (resourceItem != null) { + return true; } } @@ -225,16 +228,56 @@ public abstract class ResourceRepository { item = createResourceItem(name); - List<ResourceItem> list = mResourceMap.get(type); - if (list == null) { - list = new ArrayList<ResourceItem>(); - mResourceMap.put(type, list); + Map<String, ResourceItem> map = mResourceMap.get(type); + + if (map == null) { + if (isFrameworkRepository()) { + // Pick initial size for the maps. Also change the load factor to 1.0 + // to avoid rehashing the whole table when we (as expected) get near + // the known rough size of each resource type map. + int size; + switch (type) { + // Based on counts in API 16. Going back to API 10, the counts + // are roughly 25-50% smaller (e.g. compared to the top 5 types below + // the fractions are 1107 vs 1734, 831 vs 1508, 895 vs 1255, + // 733 vs 1064 and 171 vs 783. + case PUBLIC: size = 1734; break; + case DRAWABLE: size = 1508; break; + case STRING: size = 1255; break; + case ATTR: size = 1064; break; + case STYLE: size = 783; break; + case ID: size = 347; break; + case DECLARE_STYLEABLE: size = 210; break; + case LAYOUT: size = 187; break; + case COLOR: size = 120; break; + case ANIM: size = 95; break; + case DIMEN: size = 81; break; + case BOOL: size = 54; break; + case INTEGER: size = 52; break; + case ARRAY: size = 51; break; + case PLURALS: size = 20; break; + case XML: size = 14; break; + case INTERPOLATOR : size = 13; break; + case ANIMATOR: size = 8; break; + case RAW: size = 4; break; + case MENU: size = 2; break; + case MIPMAP: size = 2; break; + case FRACTION: size = 1; break; + default: + size = 2; + } + map = new HashMap<String, ResourceItem>(size, 1.0f); + } else { + map = new HashMap<String, ResourceItem>(); + } + mResourceMap.put(type, map); } - list.add(item); + map.put(item.getName(), item); if (oldItem != null) { - list.remove(oldItem); + map.remove(oldItem.getName()); + } } @@ -324,16 +367,16 @@ public abstract class ResourceRepository { * @return a non null collection of resource items */ public Collection<ResourceItem> getResourceItemsOfType(ResourceType type) { - List<ResourceItem> list = mResourceMap.get(type); + Map<String, ResourceItem> map = mResourceMap.get(type); - if (list == null) { + if (map == null) { return Collections.emptyList(); } - List<ResourceItem> roList = mReadOnlyListMap.get(list); + Collection<ResourceItem> roList = mReadOnlyListMap.get(map); if (roList == null) { - roList = Collections.unmodifiableList(list); - mReadOnlyListMap.put(list, roList); + roList = Collections.unmodifiableCollection(map.values()); + mReadOnlyListMap.put(map, roList); } return roList; @@ -345,7 +388,7 @@ public abstract class ResourceRepository { * @return true if the repository contains resources of the given type, false otherwise. */ public boolean hasResourcesOfType(ResourceType type) { - List<ResourceItem> items = mResourceMap.get(type); + Map<String, ResourceItem> items = mResourceMap.get(type); return (items != null && items.size() > 0); } @@ -567,10 +610,10 @@ public abstract class ResourceRepository { } protected void removeFile(ResourceType type, ResourceFile file) { - List<ResourceItem> list = mResourceMap.get(type); - if (list != null) { - for (int i = 0 ; i < list.size(); i++) { - ResourceItem item = list.get(i); + Map<String, ResourceItem> map = mResourceMap.get(type); + if (map != null) { + Collection<ResourceItem> values = map.values(); + for (ResourceItem item : values) { item.removeFile(file); } } @@ -587,7 +630,7 @@ public abstract class ResourceRepository { FolderConfiguration referenceConfig) { // get the resource item for the given type - List<ResourceItem> items = mResourceMap.get(type); + Map<String, ResourceItem> items = mResourceMap.get(type); if (items == null) { return new HashMap<String, ResourceValue>(); } @@ -595,7 +638,7 @@ public abstract class ResourceRepository { // create the map HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(items.size()); - for (ResourceItem item : items) { + for (ResourceItem item : items.values()) { ResourceValue value = item.getResourceValue(type, referenceConfig, isFrameworkRepository()); if (value != null) { @@ -614,13 +657,15 @@ public abstract class ResourceRepository { // Since removed files/folders remove source files from existing ResourceItem, loop through // all resource items and remove the ones that have no source files. - Collection<List<ResourceItem>> lists = mResourceMap.values(); - for (List<ResourceItem> list : lists) { - for (int i = 0 ; i < list.size() ;) { - if (list.get(i).hasNoSourceFile()) { - list.remove(i); - } else { - i++; + Collection<Map<String, ResourceItem>> maps = mResourceMap.values(); + for (Map<String, ResourceItem> map : maps) { + Set<String> keySet = map.keySet(); + Iterator<String> iterator = keySet.iterator(); + while (iterator.hasNext()) { + String name = iterator.next(); + ResourceItem resourceItem = map.get(name); + if (resourceItem.hasNoSourceFile()) { + iterator.remove(); } } } @@ -634,17 +679,16 @@ public abstract class ResourceRepository { * @return the existing ResourceItem or null if no match was found. */ private ResourceItem findDeclaredResourceItem(ResourceType type, String name) { - List<ResourceItem> list = mResourceMap.get(type); + Map<String, ResourceItem> map = mResourceMap.get(type); - if (list != null) { - for (ResourceItem item : list) { - // ignore inline - if (name.equals(item.getName()) && item.isDeclaredInline() == false) { - return item; - } + if (map != null) { + ResourceItem resourceItem = map.get(name); + if (resourceItem != null && !resourceItem.isDeclaredInline()) { + return resourceItem; } } return null; } } + diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java index abe8a52..288a976 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java @@ -18,7 +18,6 @@ package com.android.tools.lint.client.api; import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML; import static com.android.tools.lint.detector.api.LintConstants.ATTR_IGNORE; -import static com.android.tools.lint.detector.api.LintConstants.BIN_FOLDER; import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAR; import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA; @@ -529,18 +528,18 @@ public class LintDriver { // right thing, which is to see if you're pointing right at a project or // right within it (say at the src/ or res/) folder, and if not, you're // hopefully pointing at a project tree that you want to scan recursively. - if (isProjectDir(file)) { + if (LintUtils.isProjectDir(file)) { registerProjectFile(fileToProject, file, file, rootDir); continue; } else { File parent = file.getParentFile(); if (parent != null) { - if (isProjectDir(parent)) { + if (LintUtils.isProjectDir(parent)) { registerProjectFile(fileToProject, file, parent, parent); continue; } else { parent = parent.getParentFile(); - if (parent != null && isProjectDir(parent)) { + if (parent != null && LintUtils.isProjectDir(parent)) { registerProjectFile(fileToProject, file, parent, parent); continue; } @@ -554,7 +553,7 @@ public class LintDriver { // Pointed at a file: Search upwards for the containing project File parent = file.getParentFile(); while (parent != null) { - if (isProjectDir(parent)) { + if (LintUtils.isProjectDir(parent)) { registerProjectFile(fileToProject, file, parent, parent); break; } @@ -626,7 +625,7 @@ public class LintDriver { return; } - if (isProjectDir(dir)) { + if (LintUtils.isProjectDir(dir)) { registerProjectFile(fileToProject, dir, dir, rootDir); } else { File[] files = dir.listFiles(); @@ -640,25 +639,6 @@ public class LintDriver { } } - private boolean isProjectDir(@NonNull File dir) { - boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists(); - if (hasManifest) { - // Special case: the bin/ folder can also contain a copy of the - // manifest file, but this is *not* a project directory - if (dir.getName().equals(BIN_FOLDER)) { - // ...unless of course it just *happens* to be a project named bin, in - // which case we peek at its parent to see if this is the case - dir = dir.getParentFile(); - if (dir != null && isProjectDir(dir)) { - // Yes, it's a bin/ directory inside a real project: ignore this dir - return false; - } - } - } - - return hasManifest; - } - private void checkProject(@NonNull Project project) { File projectDir = project.getDir(); diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java index 448720e..0969228 100644 --- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java +++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java @@ -16,6 +16,8 @@ package com.android.tools.lint.detector.api; +import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML; +import static com.android.tools.lint.detector.api.LintConstants.BIN_FOLDER; import static com.android.tools.lint.detector.api.LintConstants.DOT_XML; import static com.android.tools.lint.detector.api.LintConstants.ID_RESOURCE_PREFIX; import static com.android.tools.lint.detector.api.LintConstants.NEW_ID_RESOURCE_PREFIX; @@ -518,4 +520,32 @@ public class LintUtils { return true; } + + /** + * Returns true if the given directory represents an Android project + * directory. Note: This doesn't necessarily mean it's an Eclipse directory, + * only that it looks like it contains a logical Android project -- one + * including a manifest file, a resource folder, etc. + * + * @param dir the directory to check + * @return true if the directory looks like an Android project + */ + public static boolean isProjectDir(@NonNull File dir) { + boolean hasManifest = new File(dir, ANDROID_MANIFEST_XML).exists(); + if (hasManifest) { + // Special case: the bin/ folder can also contain a copy of the + // manifest file, but this is *not* a project directory + if (dir.getName().equals(BIN_FOLDER)) { + // ...unless of course it just *happens* to be a project named bin, in + // which case we peek at its parent to see if this is the case + dir = dir.getParentFile(); + if (dir != null && isProjectDir(dir)) { + // Yes, it's a bin/ directory inside a real project: ignore this dir + return false; + } + } + } + + return hasManifest; + } } diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java index ae125e2..67d2dfa 100644 --- a/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java +++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/detector/api/LintUtilsTest.java @@ -244,5 +244,4 @@ public class LintUtilsTest extends TestCase { checkEncoding("UTF_32", true /*bom*/, "\r\n"); checkEncoding("UTF_32LE", true /*bom*/, "\r\n"); } - }
\ No newline at end of file diff --git a/rule_api/src/com/android/ide/common/api/IDragElement.java b/rule_api/src/com/android/ide/common/api/IDragElement.java index 885ba35..50a5014 100644 --- a/rule_api/src/com/android/ide/common/api/IDragElement.java +++ b/rule_api/src/com/android/ide/common/api/IDragElement.java @@ -86,6 +86,14 @@ public interface IDragElement { public abstract IDragElement[] getInnerElements(); /** + * Returns true if the given {@link INode} represents this drag element + * + * @param node the node to be checked + * @return true if the given node represents this drag element + */ + public abstract boolean isSame(@NonNull INode node); + + /** * An XML attribute in the {@link IDragElement}. * <p/> * The attribute is always represented by a namespace URI, a name and a value. diff --git a/rule_api/src/com/android/ide/common/api/IViewRule.java b/rule_api/src/com/android/ide/common/api/IViewRule.java index bcf4e89..c115795 100644 --- a/rule_api/src/com/android/ide/common/api/IViewRule.java +++ b/rule_api/src/com/android/ide/common/api/IViewRule.java @@ -161,7 +161,9 @@ public interface IViewRule { * @param targetView the corresponding View object for the target layout, or * null if not known * @param elements an array of {@link IDragElement} element descriptors for - * the dragged views + * the dragged views. When there are more than one element, the + * first element will always be the "primary" element (e.g. the + * one that the mouse is actively dragging.) * @return a {@link DropFeedback} object with drop state (which will be * supplied to a follow-up {@link #onDropMove} call), or null if the * drop should be ignored @@ -178,7 +180,9 @@ public interface IViewRule { * @param targetNode the {@link INode} for the target layout receiving a * drop event * @param elements an array of {@link IDragElement} element descriptors for - * the dragged views + * the dragged views. When there are more than one element, the + * first element will always be the "primary" element (e.g. the + * one that the mouse is actively dragging.) * @param feedback the {@link DropFeedback} object created by * {@link #onDropEnter(INode, Object, IDragElement[])} * @param where the current mouse drag position @@ -211,7 +215,9 @@ public interface IViewRule { * @param targetNode the {@link INode} for the target layout receiving a * drop event * @param elements an array of {@link IDragElement} element descriptors for - * the dragged views + * the dragged views. When there are more than one element, the + * first element will always be the "primary" element (e.g. the + * one that the mouse is actively dragging.) * @param feedback the {@link DropFeedback} object created by * {@link #onDropEnter(INode, Object, IDragElement[])} */ @@ -230,7 +236,9 @@ public interface IViewRule { * @param targetNode the {@link INode} for the target layout receiving a * drop event * @param elements an array of {@link IDragElement} element descriptors for - * the dragged views + * the dragged views. When there are more than one element, the + * first element will always be the "primary" element (e.g. the + * one that the mouse is actively dragging.) * @param feedback the {@link DropFeedback} object created by * {@link #onDropEnter(INode, Object, IDragElement[])} * @param where the mouse drop position diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java index a78e1a3..24b9260 100644 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/SdkConstants.java @@ -168,6 +168,8 @@ public final class SdkConstants { /** global Android proguard config file */ public final static String FN_ANDROID_PROGUARD_FILE = "proguard-android.txt"; //$NON-NLS-1$ + /** global Android proguard config file with optimization enabled */ + public final static String FN_ANDROID_OPT_PROGUARD_FILE = "proguard-android-optimize.txt"; //$NON-NLS-1$ /** default proguard config file with new file extension (for project specific stuff) */ public final static String FN_PROJECT_PROGUARD_FILE = "proguard-project.txt"; //$NON-NLS-1$ diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd index 546b00d..546b00d 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-addon-5.xsd +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-addon-5.xsd diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd index ea18070..ea18070 100755 --- a/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/-sdk-repository-7.xsd +++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/repository/sdk-repository-7.xsd |