aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--draw9patch/src/com/android/draw9patch/Application.java5
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java13
-rw-r--r--draw9patch/src/com/android/draw9patch/ui/MainFrame.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java21
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java13
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java146
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java208
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java24
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java6
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java14
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java84
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java8
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java8
-rw-r--r--hierarchyviewer/src/com/android/hierarchyviewer/ui/LayoutRenderer.java58
-rw-r--r--hierarchyviewer/src/com/android/hierarchyviewer/ui/ScreenViewer.java19
-rw-r--r--hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java19
-rw-r--r--sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java41
23 files changed, 583 insertions, 142 deletions
diff --git a/draw9patch/src/com/android/draw9patch/Application.java b/draw9patch/src/com/android/draw9patch/Application.java
index c7c6aaf..68c792a 100644
--- a/draw9patch/src/com/android/draw9patch/Application.java
+++ b/draw9patch/src/com/android/draw9patch/Application.java
@@ -40,11 +40,12 @@ public class Application {
}
}
- public static void main(String... args) {
+ public static void main(final String... args) {
initUserInterface();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
- MainFrame frame = new MainFrame();
+ String arg = args.length > 0 ? args[0] : null;
+ MainFrame frame = new MainFrame(arg);
frame.setDefaultCloseOperation(MainFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
diff --git a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
index 86c801f..6901c98 100644
--- a/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
+++ b/draw9patch/src/com/android/draw9patch/ui/ImageEditorPanel.java
@@ -651,6 +651,7 @@ class ImageEditorPanel extends JPanel {
private int lastPositionX;
private int lastPositionY;
+ private int currentButton;
private boolean showCursor;
private JLabel helpLabel;
@@ -687,16 +688,20 @@ class ImageEditorPanel extends JPanel {
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
- paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 :
- event.getButton());
+ // Store the button here instead of retrieving it again in MouseDragged
+ // below, because on linux, calling MouseEvent.getButton() for the drag
+ // event returns 0, which appears to be technically correct (no button
+ // changed state).
+ currentButton = event.isShiftDown() ? MouseEvent.BUTTON3 : event.getButton();
+ paint(event.getX(), event.getY(), currentButton);
}
});
addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent event) {
if (!checkLockedRegion(event.getX(), event.getY())) {
- paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 :
- event.getButton());
+ // use the stored button, see note above
+ paint(event.getX(), event.getY(), currentButton);
}
}
diff --git a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java
index 9ffd93e..d5b6409 100644
--- a/draw9patch/src/com/android/draw9patch/ui/MainFrame.java
+++ b/draw9patch/src/com/android/draw9patch/ui/MainFrame.java
@@ -40,14 +40,24 @@ public class MainFrame extends JFrame {
private JMenuItem saveMenuItem;
private ImageEditorPanel imageEditor;
- public MainFrame() throws HeadlessException {
+ public MainFrame(String path) throws HeadlessException {
super("Draw 9-patch");
buildActions();
buildMenuBar();
buildContent();
- showOpenFilePanel();
+ if (path == null) {
+ showOpenFilePanel();
+ } else {
+ try {
+ File file = new File(path);
+ BufferedImage img = GraphicsUtilities.loadCompatibleImage(file.toURI().toURL());
+ showImageEditor(img, file.getAbsolutePath());
+ } catch (Exception ex) {
+ showOpenFilePanel();
+ }
+ }
// pack();
setSize(1024, 600);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
index e71ae47..18d9745 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/build/ApkBuilder.java
@@ -346,7 +346,7 @@ public class ApkBuilder extends BaseBuilder {
}
// also check the final file(s)!
- String finalPackageName = getFileName(project, null /*config*/);
+ String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
if (mBuildFinalPackage == false) {
tmp = outputFolder.findMember(finalPackageName);
if (tmp == null || (tmp instanceof IFile &&
@@ -359,7 +359,7 @@ public class ApkBuilder extends BaseBuilder {
Set<Entry<String, String>> entrySet = configs.entrySet();
for (Entry<String, String> entry : entrySet) {
- String filename = getFileName(project, entry.getKey());
+ String filename = ProjectHelper.getApkFilename(project, entry.getKey());
tmp = outputFolder.findMember(filename);
if (tmp == null || (tmp instanceof IFile &&
@@ -409,7 +409,7 @@ public class ApkBuilder extends BaseBuilder {
Set<Entry<String, String>> entrySet = configs.entrySet();
for (Entry<String, String> entry : entrySet) {
String packageFilepath = osBinPath + File.separator +
- getFileName(project, entry.getKey());
+ ProjectHelper.getApkFilename(project, entry.getKey());
finalPackage = new File(packageFilepath);
finalPackage.delete();
@@ -532,7 +532,7 @@ public class ApkBuilder extends BaseBuilder {
// make the filename for the apk to generate
String apkOsFilePath = osBinPath + File.separator +
- getFileName(project, entry.getKey());
+ ProjectHelper.getApkFilename(project, entry.getKey());
if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
referencedJavaProjects) == false) {
return referencedProjects;
@@ -1118,19 +1118,6 @@ public class ApkBuilder extends BaseBuilder {
}
/**
- * Returns the apk filename for the given project
- * @param project The project.
- * @param config An optional config name. Can be null.
- */
- private static String getFileName(IProject project, String config) {
- if (config != null) {
- return project.getName() + "-" + config + AndroidConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
- }
-
- return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
- }
-
- /**
* Checks a {@link IFile} to make sure it should be packaged as standard resources.
* @param file the IFile representing the file.
* @return true if the file should be packaged as standard java resources.
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
index cbeddd7..b1f8ffc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/ProjectHelper.java
@@ -665,4 +665,17 @@ public final class ProjectHelper {
return false;
}
+
+ /**
+ * Returns the apk filename for the given project
+ * @param project The project.
+ * @param config An optional config name. Can be null.
+ */
+ public static String getApkFilename(IProject project, String config) {
+ if (config != null) {
+ return project.getName() + "-" + config + AndroidConstants.DOT_ANDROID_PACKAGE; //$NON-NLS-1$
+ }
+
+ return project.getName() + AndroidConstants.DOT_ANDROID_PACKAGE;
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
index 399eac9..b1b971d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ExportWizard.java
@@ -18,14 +18,20 @@ package com.android.ide.eclipse.adt.project.export;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper;
+import com.android.ide.eclipse.common.project.BaseProjectHelper;
import com.android.jarutils.KeystoreHelper;
import com.android.jarutils.SignedJarBuilder;
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException;
+import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
@@ -35,12 +41,14 @@ import org.eclipse.swt.events.VerifyListener;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
@@ -49,6 +57,9 @@ import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
/**
* Export wizard to export an apk signed with a release key/certificate.
@@ -66,7 +77,12 @@ public final class ExportWizard extends Wizard implements IExportWizard {
static final String PROPERTY_KEYSTORE = "keystore"; //$NON-NLS-1$
static final String PROPERTY_ALIAS = "alias"; //$NON-NLS-1$
static final String PROPERTY_DESTINATION = "destination"; //$NON-NLS-1$
+ static final String PROPERTY_FILENAME = "baseFilename"; //$NON-NLS-1$
+ static final int APK_FILE_SOURCE = 0;
+ static final int APK_FILE_DEST = 1;
+ static final int APK_COUNT = 2;
+
/**
* Base page class for the ExportWizard page. This class add the {@link #onShow()} callback.
*/
@@ -131,7 +147,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
* Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object.
*/
- protected final void onException(Throwable t) {
+ protected void onException(Throwable t) {
String message = getExceptionMessage(t);
setErrorMessage(message);
@@ -155,9 +171,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
private PrivateKey mPrivateKey;
private X509Certificate mCertificate;
- private String mDestinationPath;
- private String mApkFilePath;
- private String mApkFileName;
+ private File mDestinationParentFolder;
private ExportWizardPage mKeystoreSelectionPage;
private ExportWizardPage mKeyCreationPage;
@@ -168,6 +182,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
private List<String> mExistingAliases;
+ private Map<String, String[]> mApkMap;
+
public ExportWizard() {
setHelpAvailable(false); // TODO have help
setWindowTitle("Export Android Application");
@@ -186,24 +202,46 @@ public final class ExportWizard extends Wizard implements IExportWizard {
@Override
public boolean performFinish() {
- // first we make sure export is fine if the destination file already exists
- File f = new File(mDestinationPath);
- if (f.isFile()) {
- if (AdtPlugin.displayPrompt("Export Wizard",
- "File already exists. Do you want to overwrite it?") == false) {
- return false;
- }
- }
-
// save the properties
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias);
- ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION, mDestinationPath);
+ ProjectHelper.saveStringProperty(mProject, PROPERTY_DESTINATION,
+ mDestinationParentFolder.getAbsolutePath());
+ ProjectHelper.saveStringProperty(mProject, PROPERTY_FILENAME,
+ mApkMap.get(null)[APK_FILE_DEST]);
+
+ // run the export in an UI runnable.
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ final boolean[] result = new boolean[1];
+ try {
+ workbench.getProgressService().busyCursorWhile(new IRunnableWithProgress() {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException,
+ InterruptedException {
+ try {
+ result[0] = doExport(monitor);
+ } finally {
+ monitor.done();
+ }
+ }
+ });
+ } catch (InvocationTargetException e) {
+ return false;
+ } catch (InterruptedException e) {
+ return false;
+ }
+ return result[0];
+ }
+
+ private boolean doExport(IProgressMonitor monitor) {
try {
+ // first we make sure the project is built
+ mProject.build(IncrementalProjectBuilder.INCREMENTAL_BUILD, monitor);
+
+ // if needed, create the keystore and/or key.
if (mKeystoreCreationMode || mKeyCreationMode) {
final ArrayList<String> output = new ArrayList<String>();
- if (KeystoreHelper.createNewStore(
+ boolean createdStore = KeystoreHelper.createNewStore(
mKeystore,
null /*storeType*/,
mKeystorePassword,
@@ -218,7 +256,9 @@ public final class ExportWizard extends Wizard implements IExportWizard {
public void out(String message) {
output.add(message);
}
- }) == false) {
+ });
+
+ if (createdStore == false) {
// keystore creation error!
displayError(output.toArray(new String[output.size()]));
return false;
@@ -245,20 +285,42 @@ public final class ExportWizard extends Wizard implements IExportWizard {
// check the private key/certificate again since it may have been created just above.
if (mPrivateKey != null && mCertificate != null) {
- FileOutputStream fos = new FileOutputStream(mDestinationPath);
- SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
-
- // get the input file.
- FileInputStream fis = new FileInputStream(mApkFilePath);
- try {
- builder.writeZip(fis, null /* filter */);
- } finally {
- fis.close();
+ // get the output folder of the project to export.
+ // this is where we'll find the built apks to resign and export.
+ IFolder outputIFolder = BaseProjectHelper.getOutputFolder(mProject);
+ if (outputIFolder == null) {
+ return false;
}
-
- builder.close();
- fos.close();
+ String outputOsPath = outputIFolder.getLocation().toOSString();
+ // now generate the packages.
+ Set<Entry<String, String[]>> set = mApkMap.entrySet();
+ for (Entry<String, String[]> entry : set) {
+ String[] defaultApk = entry.getValue();
+ String srcFilename = defaultApk[APK_FILE_SOURCE];
+ String destFilename = defaultApk[APK_FILE_DEST];
+
+ FileOutputStream fos = new FileOutputStream(
+ new File(mDestinationParentFolder, destFilename));
+ SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate);
+
+ // get the input file.
+ FileInputStream fis = new FileInputStream(new File(outputOsPath, srcFilename));
+
+ // add the content of the source file to the output file, and sign it at
+ // the same time.
+ try {
+ builder.writeZip(fis, null /* filter */);
+ // close the builder: write the final signature files, and close the archive.
+ builder.close();
+ } finally {
+ try {
+ fis.close();
+ } finally {
+ fos.close();
+ }
+ }
+ }
return true;
}
} catch (FileNotFoundException e) {
@@ -271,6 +333,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
displayError(e);
} catch (KeytoolException e) {
displayError(e);
+ } catch (CoreException e) {
+ displayError(e);
}
return false;
@@ -282,10 +346,10 @@ public final class ExportWizard extends Wizard implements IExportWizard {
// a private key/certificate or the creation mode. In creation mode, unless
// all the key/keystore info is valid, the user cannot reach the last page, so there's
// no need to check them again here.
- return mApkFilePath != null &&
+ return mApkMap != null && mApkMap.size() > 0 &&
((mPrivateKey != null && mCertificate != null)
|| mKeystoreCreationMode || mKeyCreationMode) &&
- mDestinationPath != null;
+ mDestinationParentFolder != null;
}
/*
@@ -334,18 +398,12 @@ public final class ExportWizard extends Wizard implements IExportWizard {
return mProject;
}
- void setProject(IProject project, String apkFilePath, String filename) {
+ void setProject(IProject project) {
mProject = project;
- mApkFilePath = apkFilePath;
- mApkFileName = filename;
updatePageOnChange(ExportWizardPage.DATA_PROJECT);
}
- String getApkFilename() {
- return mApkFileName;
- }
-
void setKeystore(String path) {
mKeystore = path;
mPrivateKey = null;
@@ -444,10 +502,16 @@ public final class ExportWizard extends Wizard implements IExportWizard {
mCertificate = certificate;
}
- void setDestination(String path) {
- mDestinationPath = path;
+ void setDestination(File parentFolder, Map<String, String[]> apkMap) {
+ mDestinationParentFolder = parentFolder;
+ mApkMap = apkMap;
}
-
+
+ void resetDestination() {
+ mDestinationParentFolder = null;
+ mApkMap = null;
+ }
+
void updatePageOnChange(int changeMask) {
for (ExportWizardPage page : mPages) {
page.projectDataChanged(changeMask);
@@ -484,7 +548,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
* <p/>If no Throwable in the chain has a valid message, the canonical name of the first
* exception is returned.
*/
- private static String getExceptionMessage(Throwable t) {
+ static String getExceptionMessage(Throwable t) {
String message = t.getMessage();
if (message == null) {
Throwable cause = t.getCause();
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
index c64bf10..8a9703d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/KeyCheckPage.java
@@ -18,13 +18,18 @@ package com.android.ide.eclipse.adt.project.export;
import com.android.ide.eclipse.adt.project.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage;
+import com.android.ide.eclipse.adt.sdk.Sdk;
import org.eclipse.core.resources.IProject;
import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ScrolledComposite;
+import org.eclipse.swt.events.ControlAdapter;
+import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
@@ -47,6 +52,10 @@ import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
/**
* Final page of the wizard that checks the key and ask for the ouput location.
@@ -59,6 +68,12 @@ final class KeyCheckPage extends ExportWizardPage {
private Text mDestination;
private boolean mFatalSigningError;
private FormText mDetailText;
+ /** The Apk Config map for the current project */
+ private Map<String, String> mApkConfig;
+ private ScrolledComposite mScrolledComposite;
+
+ private String mKeyDetails;
+ private String mDestinationDetails;
protected KeyCheckPage(ExportWizard wizard, String pageName) {
super(pageName);
@@ -86,7 +101,7 @@ final class KeyCheckPage extends ExportWizardPage {
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
- onDestinationChange();
+ onDestinationChange(false /*forceDetailUpdate*/);
}
});
final Button browseButton = new Button(composite, SWT.PUSH);
@@ -97,7 +112,10 @@ final class KeyCheckPage extends ExportWizardPage {
FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name");
- fileDialog.setFileName(mWizard.getApkFilename());
+ // get a default apk name based on the project
+ String filename = ProjectHelper.getApkFilename(mWizard.getProject(),
+ null /*config*/);
+ fileDialog.setFileName(filename);
String saveLocation = fileDialog.open();
if (saveLocation != null) {
@@ -106,9 +124,21 @@ final class KeyCheckPage extends ExportWizardPage {
}
});
- mDetailText = new FormText(composite, SWT.NONE);
- mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
+ mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
+ mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3;
+ mScrolledComposite.setExpandHorizontal(true);
+ mScrolledComposite.setExpandVertical(true);
+
+ mDetailText = new FormText(mScrolledComposite, SWT.NONE);
+ mScrolledComposite.setContent(mDetailText);
+
+ mScrolledComposite.addControlListener(new ControlAdapter() {
+ @Override
+ public void controlResized(ControlEvent e) {
+ updateScrolling();
+ }
+ });
setControl(composite);
}
@@ -119,11 +149,14 @@ final class KeyCheckPage extends ExportWizardPage {
if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the destination from the content of the project
IProject project = mWizard.getProject();
+ mApkConfig = Sdk.getCurrent().getProjectApkConfigs(project);
String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION);
- if (destination != null) {
- mDestination.setText(destination);
+ String filename = ProjectHelper.loadStringProperty(project,
+ ExportWizard.PROPERTY_FILENAME);
+ if (destination != null && filename != null) {
+ mDestination.setText(destination + File.separator + filename);
}
}
@@ -134,11 +167,14 @@ final class KeyCheckPage extends ExportWizardPage {
// reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found.
mWizard.setSigningInfo(null, null);
+ mPrivateKey = null;
+ mCertificate = null;
+ mKeyDetails = null;
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
int validity = mWizard.getValidity();
StringBuilder sb = new StringBuilder(
- String.format("<form><p>Certificate expires in %d years.</p>",
+ String.format("<p>Certificate expires in %d years.</p>",
validity));
if (validity < 25) {
@@ -149,8 +185,7 @@ final class KeyCheckPage extends ExportWizardPage {
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
- sb.append("</form>");
- mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
+ mKeyDetails = sb.toString();
} else {
try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
@@ -192,10 +227,9 @@ final class KeyCheckPage extends ExportWizardPage {
Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) {
- mDetailText.setText(String.format(
- "<form><p>Certificate expired on %s</p></form>",
- mCertificate.getNotAfter().toString()),
- true /* parseTags */, true /* expandURLs */);
+ mKeyDetails = String.format(
+ "<p>Certificate expired on %s</p>",
+ mCertificate.getNotAfter().toString());
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
@@ -207,7 +241,7 @@ final class KeyCheckPage extends ExportWizardPage {
mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format(
- "<form><p>Certificate expires on %s.</p>",
+ "<p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR);
@@ -232,11 +266,8 @@ final class KeyCheckPage extends ExportWizardPage {
sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
}
- sb.append("</form>");
-
- mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
+ mKeyDetails = sb.toString();
}
- mDetailText.getParent().layout();
} else {
// fatal error = nothing can make the page complete.
mFatalSigningError = true;
@@ -244,10 +275,15 @@ final class KeyCheckPage extends ExportWizardPage {
}
}
- onDestinationChange();
+ onDestinationChange(true /*forceDetailUpdate*/);
}
- private void onDestinationChange() {
+ /**
+ * Callback for destination field edition
+ * @param forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal
+ * error has happened in the signing.
+ */
+ private void onDestinationChange(boolean forceDetailUpdate) {
if (mFatalSigningError == false) {
// reset messages for now.
setErrorMessage(null);
@@ -257,7 +293,8 @@ final class KeyCheckPage extends ExportWizardPage {
if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file.");
- mWizard.setDestination(null); // this is to reset canFinish in the wizard
+ // reset canFinish in the wizard.
+ mWizard.resetDestination();
setPageComplete(false);
return;
}
@@ -265,27 +302,140 @@ final class KeyCheckPage extends ExportWizardPage {
File file = new File(path);
if (file.isDirectory()) {
setErrorMessage("Destination is a directory.");
- mWizard.setDestination(null); // this is to reset canFinish in the wizard
+ // reset canFinish in the wizard.
+ mWizard.resetDestination();
setPageComplete(false);
return;
}
- File parentFile = file.getParentFile();
- if (parentFile == null || parentFile.isDirectory() == false) {
+ File parentFolder = file.getParentFile();
+ if (parentFolder == null || parentFolder.isDirectory() == false) {
setErrorMessage("Not a valid directory.");
- mWizard.setDestination(null); // this is to reset canFinish in the wizard
+ // reset canFinish in the wizard.
+ mWizard.resetDestination();
setPageComplete(false);
return;
}
+ // display the list of files that will actually be created
+ Map<String, String[]> apkFileMap = getApkFileMap(file);
+
+ // display them
+ boolean fileExists = false;
+ StringBuilder sb = new StringBuilder(String.format(
+ "<p>This will create the following files:</p>"));
+
+ Set<Entry<String, String[]>> set = apkFileMap.entrySet();
+ for (Entry<String, String[]> entry : set) {
+ String[] apkArray = entry.getValue();
+ String filename = apkArray[ExportWizard.APK_FILE_DEST];
+ File f = new File(parentFolder, filename);
+ if (f.isFile()) {
+ fileExists = true;
+ sb.append(String.format("<li>%1$s (WARNING: already exists)</li>", filename));
+ } else if (f.isDirectory()) {
+ setErrorMessage(String.format("%1$s is a directory.", filename));
+ // reset canFinish in the wizard.
+ mWizard.resetDestination();
+ setPageComplete(false);
+ return;
+ } else {
+ sb.append(String.format("<li>%1$s</li>", filename));
+ }
+ }
+
+ mDestinationDetails = sb.toString();
+
// no error, set the destination in the wizard.
- mWizard.setDestination(path);
+ mWizard.setDestination(parentFolder, apkFileMap);
setPageComplete(true);
-
+
// However, we should also test if the file already exists.
- if (file.isFile()) {
- setMessage("Destination file already exists.", WARNING);
+ if (fileExists) {
+ setMessage("A destination file already exists.", WARNING);
}
+
+ updateDetailText();
+ } else if (forceDetailUpdate) {
+ updateDetailText();
}
}
+
+ /**
+ * Updates the scrollbar to match the content of the {@link FormText} or the new size
+ * of the {@link ScrolledComposite}.
+ */
+ private void updateScrolling() {
+ if (mDetailText != null) {
+ Rectangle r = mScrolledComposite.getClientArea();
+ mScrolledComposite.setMinSize(mDetailText.computeSize(r.width, SWT.DEFAULT));
+ mScrolledComposite.layout();
+ }
+ }
+
+ private void updateDetailText() {
+ StringBuilder sb = new StringBuilder("<form>");
+ if (mKeyDetails != null) {
+ sb.append(mKeyDetails);
+ }
+
+ if (mDestinationDetails != null && mFatalSigningError == false) {
+ sb.append(mDestinationDetails);
+ }
+
+ sb.append("</form>");
+
+ mDetailText.setText(sb.toString(), true /* parseTags */,
+ true /* expandURLs */);
+
+ mDetailText.getParent().layout();
+
+ updateScrolling();
+
+ }
+
+ /**
+ * Creates the list of destination filenames based on the content of the destination field
+ * and the list of APK configurations for the project.
+ * @param file
+ * @return
+ */
+ private Map<String, String[]> getApkFileMap(File file) {
+ String filename = file.getName();
+
+ HashMap<String, String[]> map = new HashMap<String, String[]>();
+
+ // add the default APK filename
+ String[] apkArray = new String[ExportWizard.APK_COUNT];
+ apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
+ mWizard.getProject(), null /*config*/);
+ apkArray[ExportWizard.APK_FILE_DEST] = filename;
+ map.put(null, apkArray);
+
+ // add the APKs for each APK configuration.
+ if (mApkConfig != null && mApkConfig.size() > 0) {
+ // remove the extension.
+ int index = filename.lastIndexOf('.');
+ String base = filename.substring(0, index);
+ String extension = filename.substring(index);
+
+ Set<Entry<String, String>> set = mApkConfig.entrySet();
+ for (Entry<String, String> entry : set) {
+ apkArray = new String[ExportWizard.APK_COUNT];
+ apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
+ mWizard.getProject(), entry.getKey());
+ apkArray[ExportWizard.APK_FILE_DEST] = base + "-" + entry.getKey() + extension;
+ map.put(entry.getKey(), apkArray);
+ }
+ }
+
+ return map;
+ }
+
+ @Override
+ protected void onException(Throwable t) {
+ super.onException(t);
+
+ mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t));
+ }
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java
index e161e18..054a072 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/project/export/ProjectCheckPage.java
@@ -266,7 +266,7 @@ final class ProjectCheckPage extends ExportWizardPage {
}
// update the wizard with the new project
- mWizard.setProject(null, null, null);
+ mWizard.setProject(null);
//test the project name first!
String text = mProjectText.getText().trim();
@@ -289,7 +289,7 @@ final class ProjectCheckPage extends ExportWizardPage {
setErrorMessage(null);
// update the wizard with the new project
- setApkFilePathInWizard(found);
+ mWizard.setProject(found);
// now rebuild the error ui.
buildErrorUi(found);
@@ -299,24 +299,4 @@ final class ProjectCheckPage extends ExportWizardPage {
}
}
}
-
- private void setApkFilePathInWizard(IProject project) {
- if (project != null) {
- IFolder outputIFolder = BaseProjectHelper.getOutputFolder(project);
- if (outputIFolder != null) {
- String outputOsPath = outputIFolder.getLocation().toOSString();
- String apkFilePath = outputOsPath + File.separator + project.getName() +
- AndroidConstants.DOT_ANDROID_PACKAGE;
-
- File f = new File(apkFilePath);
- if (f.isFile()) {
- mWizard.setProject(project, apkFilePath, f.getName());
- return;
- }
- }
- }
-
- mWizard.setProject(null, null, null);
- }
-
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
index cb79796..af45fa9 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/wizards/newproject/NewProjectWizard.java
@@ -352,11 +352,17 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
- // Setup class path
+ // Setup class path: mark folders as source folders
IJavaProject javaProject = JavaCore.create(project);
for (String sourceFolder : sourceFolders) {
setupSourceFolder(javaProject, sourceFolder, monitor);
}
+
+ // Mark the gen source folder as derived
+ IFolder genSrcFolder = project.getFolder(AndroidConstants.WS_ROOT + GEN_SRC_DIRECTORY);
+ if (genSrcFolder.exists()) {
+ genSrcFolder.setDerived(true);
+ }
if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java
index a6db786..332ce6f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/AndroidContentAssist.java
@@ -328,7 +328,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
}
if (currAttrNode != null) {
- choices = currAttrNode.getPossibleValues();
+ choices = currAttrNode.getPossibleValues(value);
if (currAttrNode instanceof UiFlagAttributeNode) {
// A "flag" can consist of several values separated by "or" (|).
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
index f8aac1d..f886080 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiClassAttributeNode.java
@@ -616,7 +616,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
}
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
// TODO: compute a list of existing classes for content assist completion
return null;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java
index 02fb44f..1fe9b75 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/manifest/model/UiPackageAttributeNode.java
@@ -311,7 +311,7 @@ public class UiPackageAttributeNode extends UiTextAttributeNode {
}
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
// TODO: compute a list of existing packages for content assist completion
return null;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java
index 304dd14..dc4836d 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/ui/ListValueCellEditor.java
@@ -48,7 +48,7 @@ public class ListValueCellEditor extends ComboBoxCellEditor {
UiListAttributeNode uiListAttribute = (UiListAttributeNode)value;
// set the possible values in the combo
- String[] items = uiListAttribute.getPossibleValues();
+ String[] items = uiListAttribute.getPossibleValues(null);
mItems = new String[items.length];
System.arraycopy(items, 0, mItems, 0, items.length);
setItems(mItems);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java
index 5972f22..cada844 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiAttributeNode.java
@@ -117,9 +117,13 @@ public abstract class UiAttributeNode {
* <p/>
* Implementations that do not have any known values should return null.
*
- * @return A list of possible completion values or null.
+ * @param prefix An optional prefix string, which is whatever the user has already started
+ * typing. Can be null or an empty string. The implementation can use this to filter choices
+ * and only return strings that match this prefix. A lazy or default implementation can
+ * simply ignore this and return everything.
+ * @return A list of possible completion values, and empty array or null.
*/
- public abstract String[] getPossibleValues();
+ public abstract String[] getPossibleValues(String prefix);
/**
* Called when the XML is being loaded or has changed to
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java
index ddcf0a0..c799518 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiFlagAttributeNode.java
@@ -124,9 +124,11 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
/**
* Get the flag names, either from the initial names set in the attribute
* or by querying the framework resource parser.
+ *
+ * {@inheritDoc}
*/
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
String attr_name = getDescriptor().getXmlLocalName();
String element_name = getUiParent().getDescriptor().getXmlName();
@@ -242,7 +244,7 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
final TableColumn column = new TableColumn(mTable, SWT.NONE);
// List all the expected flag names and check those which are currently used
- String[] names = getPossibleValues();
+ String[] names = getPossibleValues(null);
if (names != null) {
for (String name : names) {
TableItem item = new TableItem(mTable, SWT.NONE);
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java
index c5c10aa..faac013 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiListAttributeNode.java
@@ -108,7 +108,7 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
}
protected void fillCombo() {
- String[] values = getPossibleValues();
+ String[] values = getPossibleValues(null);
if (values == null) {
AdtPlugin.log(IStatus.ERROR,
@@ -124,9 +124,11 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
/**
* Get the list values, either from the initial values set in the attribute
* or by querying the framework resource parser.
+ *
+ * {@inheritDoc}
*/
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
AttributeDescriptor descriptor = getDescriptor();
UiElementNode uiParent = getUiParent();
@@ -134,13 +136,13 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
String element_name = uiParent.getDescriptor().getXmlName();
// FrameworkResourceManager expects a specific prefix for the attribute.
- String prefix = "";
+ String nsPrefix = "";
if (SdkConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) {
- prefix = "android:"; //$NON-NLS-1$
+ nsPrefix = "android:"; //$NON-NLS-1$
} else if (XmlnsAttributeDescriptor.XMLNS_URI.equals(descriptor.getNamespaceUri())) {
- prefix = "xmlns:"; //$NON-NLS-1$
+ nsPrefix = "xmlns:"; //$NON-NLS-1$
}
- attr_name = prefix + attr_name;
+ attr_name = nsPrefix + attr_name;
String[] values = null;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java
index 1c1e1bd..48f8a7f 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiResourceAttributeNode.java
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.editors.uimodel;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.common.resources.IResourceRepository;
+import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.editors.AndroidEditor;
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor;
@@ -44,6 +45,10 @@ import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.TableWrapData;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
/**
* Represents an XML attribute for a resource that can be modified using a simple text field or
* a dialog to choose an existing resource.
@@ -154,8 +159,81 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
}
@Override
- public String[] getPossibleValues() {
- // TODO: compute a list of existing resources for content assist completion
- return null;
+ public String[] getPossibleValues(String prefix) {
+ IResourceRepository repository = null;
+ boolean isSystem = false;
+
+ UiElementNode uiNode = getUiParent();
+ AndroidEditor editor = uiNode.getEditor();
+
+ if (prefix == null || prefix.indexOf("android:") < 0) {
+ IProject project = editor.getProject();
+ if (project != null) {
+ // get the resource repository for this project and the system resources.
+ repository = ResourceManager.getInstance().getProjectResources(project);
+ }
+ } else {
+ // If there's a prefix with "android:" in it, use the system resources
+ AndroidTargetData data = editor.getTargetData();
+ repository = data.getSystemResources();
+ isSystem = true;
+ }
+
+ // Get list of potential resource types, either specific to this project
+ // or the generic list.
+ ResourceType[] resTypes = (repository != null) ?
+ repository.getAvailableResourceTypes() :
+ ResourceType.values();
+
+ // Get the type name from the prefix, if any. It's any word before the / if there's one
+ String typeName = null;
+ if (prefix != null) {
+ Matcher m = Pattern.compile(".*?([a-z]+)/.*").matcher(prefix);
+ if (m.matches()) {
+ typeName = m.group(1);
+ }
+ }
+
+ // Now collect results
+ ArrayList<String> results = new ArrayList<String>();
+
+ if (typeName == null) {
+ // This prefix does not have a / in it, so the resource string is either empty
+ // or does not have the resource type in it. Simply offer the list of potential
+ // resource types.
+
+ for (ResourceType resType : resTypes) {
+ results.add("@" + resType.getName() + "/");
+ if (resType == ResourceType.ID) {
+ // Also offer the + version to create an id from scratch
+ results.add("@+" + resType.getName() + "/");
+ }
+ }
+ } else if (repository != null) {
+ // We have a style name and a repository. Find all resources that match this
+ // type and recreate suggestions out of them.
+
+ ResourceType resType = ResourceType.getEnum(typeName);
+ if (resType != null) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('@');
+ if (prefix.indexOf('+') >= 0) {
+ sb.append('+');
+ }
+
+ if (isSystem) {
+ sb.append("android:");
+ }
+
+ sb.append(typeName).append('/');
+ String base = sb.toString();
+
+ for (ResourceItem item : repository.getResources(resType)) {
+ results.add(base + item.getName());
+ }
+ }
+ }
+
+ return results.toArray(new String[results.size()]);
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java
index 192f752..a6111d4 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiSeparatorAttributeNode.java
@@ -96,9 +96,13 @@ public class UiSeparatorAttributeNode extends UiAttributeNode {
sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
}
- /** No completion values for this UI attribute. */
+ /**
+ * No completion values for this UI attribute.
+ *
+ * {@inheritDoc}
+ */
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
return null;
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java
index 4c53f4c..652debe 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/editors/uimodel/UiTextAttributeNode.java
@@ -70,9 +70,13 @@ public class UiTextAttributeNode extends UiAbstractTextAttributeNode {
setTextWidget(text);
}
- /** No completion values for this UI attribute. */
+ /**
+ * No completion values for this UI attribute.
+ *
+ * {@inheritDoc}
+ */
@Override
- public String[] getPossibleValues() {
+ public String[] getPossibleValues(String prefix) {
return null;
}
diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/LayoutRenderer.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/LayoutRenderer.java
index a50905c..8943834 100644
--- a/hierarchyviewer/src/com/android/hierarchyviewer/ui/LayoutRenderer.java
+++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/LayoutRenderer.java
@@ -25,6 +25,8 @@ import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
import java.util.Set;
class LayoutRenderer extends JComponent {
@@ -34,14 +36,23 @@ class LayoutRenderer extends JComponent {
private boolean showExtras;
private ViewHierarchyScene scene;
+ private JComponent sceneView;
- LayoutRenderer(ViewHierarchyScene scene) {
+ LayoutRenderer(ViewHierarchyScene scene, JComponent sceneView) {
this.scene = scene;
+ this.sceneView = sceneView;
setOpaque(true);
setBorder(BorderFactory.createEmptyBorder(0, 0, 12, 0));
setBackground(Color.BLACK);
setForeground(Color.WHITE);
+
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent event) {
+ selectChild(event.getX(), event.getY());
+ }
+ });
}
@Override
@@ -118,4 +129,49 @@ class LayoutRenderer extends JComponent {
this.showExtras = showExtras;
repaint();
}
+
+ private void selectChild(int x, int y) {
+
+ if (scene == null) {
+ return;
+ }
+
+ ViewNode root = scene.getRoot();
+ if (root == null) {
+ return;
+ }
+
+ Insets insets = getInsets();
+
+ int xoffset = (getWidth() - insets.left - insets.right - root.width) / 2 + insets.left + 1;
+ int yoffset = (getHeight() - insets.top - insets.bottom - root.height) / 2 + insets.top + 1;
+
+ x -= xoffset;
+ y -= yoffset;
+ if (x >= 0 && x < EMULATED_SCREEN_WIDTH && y >= 0 && y < EMULATED_SCREEN_HEIGHT) {
+ ViewNode hit = findChild(root, root, x, y);
+ scene.setFocusedObject(hit);
+ sceneView.repaint();
+ }
+ }
+
+ private ViewNode findChild(ViewNode root, ViewNode besthit, int x, int y) {
+ ViewNode hit = besthit;
+ for (ViewNode node : root.children) {
+
+ if (node.left <= x && x < node.left + node.width &&
+ node.top <= y && y < node.top + node.height) {
+ if (node.width <= hit.width && node.height <= hit.height) {
+ hit = node;
+ }
+ }
+
+ if (node.children.size() > 0) {
+ hit = findChild(node, hit,
+ x - (node.left - node.parent.scrollX),
+ y - (node.top - node.parent.scrollY));
+ }
+ }
+ return hit;
+ }
}
diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/ScreenViewer.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/ScreenViewer.java
index 83d926f..e4144b1 100644
--- a/hierarchyviewer/src/com/android/hierarchyviewer/ui/ScreenViewer.java
+++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/ScreenViewer.java
@@ -45,6 +45,8 @@ import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
import java.util.concurrent.ExecutionException;
class ScreenViewer extends JPanel implements ActionListener {
@@ -69,6 +71,8 @@ class ScreenViewer extends JPanel implements ActionListener {
private Timer timer;
private ViewNode node;
+ private JSlider zoomSlider;
+
ScreenViewer(Workspace workspace, Device device, int spacing) {
setLayout(new BorderLayout());
setOpaque(false);
@@ -95,6 +99,7 @@ class ScreenViewer extends JPanel implements ActionListener {
private JPanel buildLoupePanel(int spacing) {
loupe = new LoupeViewer();
+ loupe.addMouseWheelListener(new WheelZoomListener());
CrosshairPanel crosshairPanel = new CrosshairPanel(loupe);
JPanel loupePanel = new JPanel(new BorderLayout());
@@ -106,9 +111,20 @@ class ScreenViewer extends JPanel implements ActionListener {
return loupePanel;
}
+ private class WheelZoomListener implements MouseWheelListener {
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (zoomSlider != null) {
+ int val = zoomSlider.getValue();
+ val -= e.getWheelRotation() * 2;
+ zoomSlider.setValue(val);
+ }
+ }
+ }
+
private JPanel buildViewerAndControls() {
JPanel panel = new JPanel(new GridBagLayout());
crosshair = new Crosshair(new ScreenshotViewer());
+ crosshair.addMouseWheelListener(new WheelZoomListener());
panel.add(crosshair,
new GridBagConstraints(0, y++, 2, 1, 1.0f, 0.0f,
GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
@@ -131,7 +147,8 @@ class ScreenViewer extends JPanel implements ActionListener {
timer.restart();
}
});
- buildSlider(panel, "Zoom:", "2x", "24x", 2, 24, 8, 2).addChangeListener(
+ zoomSlider = buildSlider(panel, "Zoom:", "2x", "24x", 2, 24, 8, 2);
+ zoomSlider.addChangeListener(
new ChangeListener() {
public void stateChanged(ChangeEvent event) {
zoom = ((JSlider) event.getSource()).getValue();
diff --git a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
index 20093ae..77ebb39 100644
--- a/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
+++ b/hierarchyviewer/src/com/android/hierarchyviewer/ui/Workspace.java
@@ -67,6 +67,7 @@ import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
+import javax.swing.JScrollBar;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTable;
@@ -105,6 +106,8 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -173,6 +176,7 @@ public class Workspace extends JFrame {
add(buildMainPanel());
setJMenuBar(buildMenuBar());
+ devices.changeSelection(0, 0, false, false);
currentDeviceChanged();
pack();
@@ -648,6 +652,7 @@ public class Workspace extends JFrame {
sceneView = scene.createView();
sceneView.addMouseListener(new NodeClickListener());
+ sceneView.addMouseWheelListener(new WheelZoomListener());
sceneScroller.setViewportView(sceneView);
if (extrasPanel != null) {
@@ -678,7 +683,10 @@ public class Workspace extends JFrame {
private JPanel buildExtrasPanel() {
extrasPanel = new JPanel(new BorderLayout());
- extrasPanel.add(new JScrollPane(layoutView = new LayoutRenderer(scene)));
+ JScrollPane p = new JScrollPane(layoutView = new LayoutRenderer(scene, sceneView));
+ JScrollBar b = p.getVerticalScrollBar();
+ b.setUnitIncrement(10);
+ extrasPanel.add(p);
extrasPanel.add(scene.createSatelliteView(), BorderLayout.SOUTH);
extrasPanel.add(buildLayoutViewControlButtons(), BorderLayout.NORTH);
return extrasPanel;
@@ -1231,6 +1239,15 @@ public class Workspace extends JFrame {
}
}
+ private class WheelZoomListener implements MouseWheelListener {
+ public void mouseWheelMoved(MouseWheelEvent e) {
+ if (zoomSlider != null) {
+ int val = zoomSlider.getValue();
+ val -= e.getWheelRotation() * 10;
+ zoomSlider.setValue(val);
+ }
+ }
+ }
private class DevicesTableModel extends DefaultTableModel implements
AndroidDebugBridge.IDeviceChangeListener {
diff --git a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
index 0ea89d1..7b8fdbe 100644
--- a/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
+++ b/sdkmanager/libs/sdklib/src/com/android/sdklib/avd/AvdManager.java
@@ -48,11 +48,49 @@ public final class AvdManager {
private final static String AVD_INFO_PATH = "path";
private final static String AVD_INFO_TARGET = "target";
+ /**
+ * AVD/config.ini key name representing the SDK-relative path of the skin folder, if any,
+ * or a 320x480 like constant for a numeric skin size.
+ *
+ * @see #NUMERIC_SKIN_SIZE
+ */
public final static String AVD_INI_SKIN_PATH = "skin.path";
+ /**
+ * AVD/config.ini key name representing an UI name for the skin.
+ * This config key is ignored by the emulator. It is only used by the SDK manager or
+ * tools to give a friendlier name to the skin.
+ * If missing, use the {@link #AVD_INI_SKIN_PATH} key instead.
+ */
public final static String AVD_INI_SKIN_NAME = "skin.name";
+ /**
+ * AVD/config.ini key name representing the path to the sdcard file.
+ * If missing, the default name "sdcard.img" will be used for the sdcard, if there's such
+ * a file.
+ *
+ * @see #SDCARD_IMG
+ */
public final static String AVD_INI_SDCARD_PATH = "sdcard.path";
+ /**
+ * AVD/config.ini key name representing the size of the SD card.
+ * This property is for UI purposes only. It is not used by the emulator.
+ *
+ * @see #SDCARD_SIZE_PATTERN
+ */
public final static String AVD_INI_SDCARD_SIZE = "sdcard.size";
+ /**
+ * AVD/config.ini key name representing the first path where the emulator looks
+ * for system images. Typically this is the path to the add-on system image or
+ * the path to the platform system image if there's no add-on.
+ * <p/>
+ * The emulator looks at {@link #AVD_INI_IMAGES_1} before {@link #AVD_INI_IMAGES_2}.
+ */
public final static String AVD_INI_IMAGES_1 = "image.sysdir.1";
+ /**
+ * AVD/config.ini key name representing the second path where the emulator looks
+ * for system images. Typically this is the path to the platform system image.
+ *
+ * @see #AVD_INI_IMAGES_1
+ */
public final static String AVD_INI_IMAGES_2 = "image.sysdir.2";
/**
@@ -69,6 +107,9 @@ public final class AvdManager {
private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + INI_EXTENSION + "$",
Pattern.CASE_INSENSITIVE);
+ /**
+ * Pattern for matching SD Card sizes, e.g. "4K" or "16M".
+ */
private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
/** An immutable structure describing an Android Virtual Device. */