auto import from //depot/cupcake/@136594

This commit is contained in:
The Android Open Source Project
2009-03-05 14:34:30 -08:00
parent 52d4c30ca5
commit edd86fdaa9
24 changed files with 596 additions and 148 deletions

View File

@@ -174,14 +174,16 @@ public class SoftKeyboard extends InputMethodService
// We now look for a few special variations of text that will // We now look for a few special variations of text that will
// modify our behavior. // modify our behavior.
int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION; int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) { if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
// Do not display predictions / what the user is typing // Do not display predictions / what the user is typing
// when they are entering a password. // when they are entering a password.
mPredictionOn = false; mPredictionOn = false;
} }
if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|| variation == EditorInfo.TYPE_TEXT_VARIATION_URI) { || variation == EditorInfo.TYPE_TEXT_VARIATION_URI
|| variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
// Our predictions are not useful for e-mail addresses // Our predictions are not useful for e-mail addresses
// or URIs. // or URIs.
mPredictionOn = false; mPredictionOn = false;
@@ -207,6 +209,7 @@ public class SoftKeyboard extends InputMethodService
// For all unknown input types, default to the alphabetic // For all unknown input types, default to the alphabetic
// keyboard with no special features. // keyboard with no special features.
mCurKeyboard = mQwertyKeyboard; mCurKeyboard = mQwertyKeyboard;
updateShiftKeyState(attribute);
} }
// Update the label on the enter key, depending on what the application // Update the label on the enter key, depending on what the application
@@ -428,7 +431,11 @@ public class SoftKeyboard extends InputMethodService
private void updateShiftKeyState(EditorInfo attr) { private void updateShiftKeyState(EditorInfo attr) {
if (attr != null if (attr != null
&& mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) { && mInputView != null && mQwertyKeyboard == mInputView.getKeyboard()) {
int caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType); int caps = 0;
EditorInfo ei = getCurrentInputEditorInfo();
if (ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
caps = getCurrentInputConnection().getCursorCapsMode(attr.inputType);
}
mInputView.setShifted(mCapsLock || caps != 0); mInputView.setShifted(mCapsLock || caps != 0);
} }
} }
@@ -553,7 +560,7 @@ public class SoftKeyboard extends InputMethodService
final int length = mComposing.length(); final int length = mComposing.length();
if (length > 1) { if (length > 1) {
mComposing.delete(length - 1, length); mComposing.delete(length - 1, length);
getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); getCurrentInputConnection().setComposingText(mComposing, 1);
updateCandidates(); updateCandidates();
} else if (length > 0) { } else if (length > 0) {
mComposing.setLength(0); mComposing.setLength(0);
@@ -594,7 +601,7 @@ public class SoftKeyboard extends InputMethodService
} }
if (isAlphabet(primaryCode) && mPredictionOn) { if (isAlphabet(primaryCode) && mPredictionOn) {
mComposing.append((char) primaryCode); mComposing.append((char) primaryCode);
getCurrentInputConnection().setComposingText(mComposing, mComposing.length()); getCurrentInputConnection().setComposingText(mComposing, 1);
updateShiftKeyState(getCurrentInputEditorInfo()); updateShiftKeyState(getCurrentInputEditorInfo());
updateCandidates(); updateCandidates();
} else { } else {
@@ -605,7 +612,7 @@ public class SoftKeyboard extends InputMethodService
private void handleClose() { private void handleClose() {
commitTyped(getCurrentInputConnection()); commitTyped(getCurrentInputConnection());
dismissSoftInput(0); requestHideSelf(0);
mInputView.closing(); mInputView.closing();
} }

View File

@@ -40,11 +40,12 @@ public class Application {
} }
} }
public static void main(String... args) { public static void main(final String... args) {
initUserInterface(); initUserInterface();
SwingUtilities.invokeLater(new Runnable() { SwingUtilities.invokeLater(new Runnable() {
public void run() { 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.setDefaultCloseOperation(MainFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null); frame.setLocationRelativeTo(null);
frame.setVisible(true); frame.setVisible(true);

View File

@@ -651,6 +651,7 @@ class ImageEditorPanel extends JPanel {
private int lastPositionX; private int lastPositionX;
private int lastPositionY; private int lastPositionY;
private int currentButton;
private boolean showCursor; private boolean showCursor;
private JLabel helpLabel; private JLabel helpLabel;
@@ -687,16 +688,20 @@ class ImageEditorPanel extends JPanel {
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
@Override @Override
public void mousePressed(MouseEvent event) { public void mousePressed(MouseEvent event) {
paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 : // Store the button here instead of retrieving it again in MouseDragged
event.getButton()); // 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() { addMouseMotionListener(new MouseMotionAdapter() {
@Override @Override
public void mouseDragged(MouseEvent event) { public void mouseDragged(MouseEvent event) {
if (!checkLockedRegion(event.getX(), event.getY())) { if (!checkLockedRegion(event.getX(), event.getY())) {
paint(event.getX(), event.getY(), event.isShiftDown() ? MouseEvent.BUTTON3 : // use the stored button, see note above
event.getButton()); paint(event.getX(), event.getY(), currentButton);
} }
} }

View File

@@ -40,14 +40,24 @@ public class MainFrame extends JFrame {
private JMenuItem saveMenuItem; private JMenuItem saveMenuItem;
private ImageEditorPanel imageEditor; private ImageEditorPanel imageEditor;
public MainFrame() throws HeadlessException { public MainFrame(String path) throws HeadlessException {
super("Draw 9-patch"); super("Draw 9-patch");
buildActions(); buildActions();
buildMenuBar(); buildMenuBar();
buildContent(); 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(); // pack();
setSize(1024, 600); setSize(1024, 600);

View File

@@ -346,7 +346,7 @@ public class ApkBuilder extends BaseBuilder {
} }
// also check the final file(s)! // also check the final file(s)!
String finalPackageName = getFileName(project, null /*config*/); String finalPackageName = ProjectHelper.getApkFilename(project, null /*config*/);
if (mBuildFinalPackage == false) { if (mBuildFinalPackage == false) {
tmp = outputFolder.findMember(finalPackageName); tmp = outputFolder.findMember(finalPackageName);
if (tmp == null || (tmp instanceof IFile && if (tmp == null || (tmp instanceof IFile &&
@@ -359,7 +359,7 @@ public class ApkBuilder extends BaseBuilder {
Set<Entry<String, String>> entrySet = configs.entrySet(); Set<Entry<String, String>> entrySet = configs.entrySet();
for (Entry<String, String> entry : entrySet) { for (Entry<String, String> entry : entrySet) {
String filename = getFileName(project, entry.getKey()); String filename = ProjectHelper.getApkFilename(project, entry.getKey());
tmp = outputFolder.findMember(filename); tmp = outputFolder.findMember(filename);
if (tmp == null || (tmp instanceof IFile && if (tmp == null || (tmp instanceof IFile &&
@@ -409,7 +409,7 @@ public class ApkBuilder extends BaseBuilder {
Set<Entry<String, String>> entrySet = configs.entrySet(); Set<Entry<String, String>> entrySet = configs.entrySet();
for (Entry<String, String> entry : entrySet) { for (Entry<String, String> entry : entrySet) {
String packageFilepath = osBinPath + File.separator + String packageFilepath = osBinPath + File.separator +
getFileName(project, entry.getKey()); ProjectHelper.getApkFilename(project, entry.getKey());
finalPackage = new File(packageFilepath); finalPackage = new File(packageFilepath);
finalPackage.delete(); finalPackage.delete();
@@ -532,7 +532,7 @@ public class ApkBuilder extends BaseBuilder {
// make the filename for the apk to generate // make the filename for the apk to generate
String apkOsFilePath = osBinPath + File.separator + String apkOsFilePath = osBinPath + File.separator +
getFileName(project, entry.getKey()); ProjectHelper.getApkFilename(project, entry.getKey());
if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject, if (finalPackage(resPath, classesDexPath, apkOsFilePath, javaProject,
referencedJavaProjects) == false) { referencedJavaProjects) == false) {
return referencedProjects; return referencedProjects;
@@ -1117,19 +1117,6 @@ public class ApkBuilder extends BaseBuilder {
return list.toArray(new IJavaProject[list.size()]); return list.toArray(new IJavaProject[list.size()]);
} }
/**
* 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. * Checks a {@link IFile} to make sure it should be packaged as standard resources.
* @param file the IFile representing the file. * @param file the IFile representing the file.

View File

@@ -665,4 +665,17 @@ public final class ProjectHelper {
return false; 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;
}
} }

View File

@@ -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.AdtPlugin;
import com.android.ide.eclipse.adt.project.ProjectHelper; 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.KeystoreHelper;
import com.android.jarutils.SignedJarBuilder; import com.android.jarutils.SignedJarBuilder;
import com.android.jarutils.DebugKeyProvider.IKeyGenOutput; import com.android.jarutils.DebugKeyProvider.IKeyGenOutput;
import com.android.jarutils.DebugKeyProvider.KeytoolException; import com.android.jarutils.DebugKeyProvider.KeytoolException;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource; 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.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard; 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.swt.widgets.Text;
import org.eclipse.ui.IExportWizard; import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -49,6 +57,9 @@ import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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. * 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_KEYSTORE = "keystore"; //$NON-NLS-1$
static final String PROPERTY_ALIAS = "alias"; //$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_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. * 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 * Calls {@link #setErrorMessage(String)} and {@link #setPageComplete(boolean)} based on a
* {@link Throwable} object. * {@link Throwable} object.
*/ */
protected final void onException(Throwable t) { protected void onException(Throwable t) {
String message = getExceptionMessage(t); String message = getExceptionMessage(t);
setErrorMessage(message); setErrorMessage(message);
@@ -155,9 +171,7 @@ public final class ExportWizard extends Wizard implements IExportWizard {
private PrivateKey mPrivateKey; private PrivateKey mPrivateKey;
private X509Certificate mCertificate; private X509Certificate mCertificate;
private String mDestinationPath; private File mDestinationParentFolder;
private String mApkFilePath;
private String mApkFileName;
private ExportWizardPage mKeystoreSelectionPage; private ExportWizardPage mKeystoreSelectionPage;
private ExportWizardPage mKeyCreationPage; private ExportWizardPage mKeyCreationPage;
@@ -168,6 +182,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
private List<String> mExistingAliases; private List<String> mExistingAliases;
private Map<String, String[]> mApkMap;
public ExportWizard() { public ExportWizard() {
setHelpAvailable(false); // TODO have help setHelpAvailable(false); // TODO have help
setWindowTitle("Export Android Application"); setWindowTitle("Export Android Application");
@@ -186,24 +202,46 @@ public final class ExportWizard extends Wizard implements IExportWizard {
@Override @Override
public boolean performFinish() { 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 // save the properties
ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore); ProjectHelper.saveStringProperty(mProject, PROPERTY_KEYSTORE, mKeystore);
ProjectHelper.saveStringProperty(mProject, PROPERTY_ALIAS, mKeyAlias); 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 { 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) { if (mKeystoreCreationMode || mKeyCreationMode) {
final ArrayList<String> output = new ArrayList<String>(); final ArrayList<String> output = new ArrayList<String>();
if (KeystoreHelper.createNewStore( boolean createdStore = KeystoreHelper.createNewStore(
mKeystore, mKeystore,
null /*storeType*/, null /*storeType*/,
mKeystorePassword, mKeystorePassword,
@@ -218,7 +256,9 @@ public final class ExportWizard extends Wizard implements IExportWizard {
public void out(String message) { public void out(String message) {
output.add(message); output.add(message);
} }
}) == false) { });
if (createdStore == false) {
// keystore creation error! // keystore creation error!
displayError(output.toArray(new String[output.size()])); displayError(output.toArray(new String[output.size()]));
return false; 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. // check the private key/certificate again since it may have been created just above.
if (mPrivateKey != null && mCertificate != null) { if (mPrivateKey != null && mCertificate != null) {
FileOutputStream fos = new FileOutputStream(mDestinationPath); // get the output folder of the project to export.
SignedJarBuilder builder = new SignedJarBuilder(fos, mPrivateKey, mCertificate); // this is where we'll find the built apks to resign and export.
IFolder outputIFolder = BaseProjectHelper.getOutputFolder(mProject);
// get the input file. if (outputIFolder == null) {
FileInputStream fis = new FileInputStream(mApkFilePath); return false;
try {
builder.writeZip(fis, null /* filter */);
} finally {
fis.close();
} }
String outputOsPath = outputIFolder.getLocation().toOSString();
builder.close();
fos.close();
// 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; return true;
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@@ -271,6 +333,8 @@ public final class ExportWizard extends Wizard implements IExportWizard {
displayError(e); displayError(e);
} catch (KeytoolException e) { } catch (KeytoolException e) {
displayError(e); displayError(e);
} catch (CoreException e) {
displayError(e);
} }
return false; 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 // 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 // all the key/keystore info is valid, the user cannot reach the last page, so there's
// no need to check them again here. // no need to check them again here.
return mApkFilePath != null && return mApkMap != null && mApkMap.size() > 0 &&
((mPrivateKey != null && mCertificate != null) ((mPrivateKey != null && mCertificate != null)
|| mKeystoreCreationMode || mKeyCreationMode) && || mKeystoreCreationMode || mKeyCreationMode) &&
mDestinationPath != null; mDestinationParentFolder != null;
} }
/* /*
@@ -334,18 +398,12 @@ public final class ExportWizard extends Wizard implements IExportWizard {
return mProject; return mProject;
} }
void setProject(IProject project, String apkFilePath, String filename) { void setProject(IProject project) {
mProject = project; mProject = project;
mApkFilePath = apkFilePath;
mApkFileName = filename;
updatePageOnChange(ExportWizardPage.DATA_PROJECT); updatePageOnChange(ExportWizardPage.DATA_PROJECT);
} }
String getApkFilename() {
return mApkFileName;
}
void setKeystore(String path) { void setKeystore(String path) {
mKeystore = path; mKeystore = path;
mPrivateKey = null; mPrivateKey = null;
@@ -444,10 +502,16 @@ public final class ExportWizard extends Wizard implements IExportWizard {
mCertificate = certificate; mCertificate = certificate;
} }
void setDestination(String path) { void setDestination(File parentFolder, Map<String, String[]> apkMap) {
mDestinationPath = path; mDestinationParentFolder = parentFolder;
mApkMap = apkMap;
} }
void resetDestination() {
mDestinationParentFolder = null;
mApkMap = null;
}
void updatePageOnChange(int changeMask) { void updatePageOnChange(int changeMask) {
for (ExportWizardPage page : mPages) { for (ExportWizardPage page : mPages) {
page.projectDataChanged(changeMask); 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 * <p/>If no Throwable in the chain has a valid message, the canonical name of the first
* exception is returned. * exception is returned.
*/ */
private static String getExceptionMessage(Throwable t) { static String getExceptionMessage(Throwable t) {
String message = t.getMessage(); String message = t.getMessage();
if (message == null) { if (message == null) {
Throwable cause = t.getCause(); Throwable cause = t.getCause();

View File

@@ -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.ProjectHelper;
import com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage; 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.core.resources.IProject;
import org.eclipse.swt.SWT; 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.ModifyEvent;
import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
@@ -47,6 +52,10 @@ import java.security.KeyStore.PrivateKeyEntry;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Calendar; 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. * 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 Text mDestination;
private boolean mFatalSigningError; private boolean mFatalSigningError;
private FormText mDetailText; 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) { protected KeyCheckPage(ExportWizard wizard, String pageName) {
super(pageName); super(pageName);
@@ -86,7 +101,7 @@ final class KeyCheckPage extends ExportWizardPage {
mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
mDestination.addModifyListener(new ModifyListener() { mDestination.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) { public void modifyText(ModifyEvent e) {
onDestinationChange(); onDestinationChange(false /*forceDetailUpdate*/);
} }
}); });
final Button browseButton = new Button(composite, SWT.PUSH); 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 fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
fileDialog.setText("Destination file name"); 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(); String saveLocation = fileDialog.open();
if (saveLocation != null) { if (saveLocation != null) {
@@ -106,9 +124,21 @@ final class KeyCheckPage extends ExportWizardPage {
} }
}); });
mDetailText = new FormText(composite, SWT.NONE); mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
mDetailText.setLayoutData(gd = new GridData(GridData.FILL_BOTH)); mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
gd.horizontalSpan = 3; 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); setControl(composite);
} }
@@ -119,11 +149,14 @@ final class KeyCheckPage extends ExportWizardPage {
if ((mProjectDataChanged & DATA_PROJECT) != 0) { if ((mProjectDataChanged & DATA_PROJECT) != 0) {
// reset the destination from the content of the project // reset the destination from the content of the project
IProject project = mWizard.getProject(); IProject project = mWizard.getProject();
mApkConfig = Sdk.getCurrent().getProjectApkConfigs(project);
String destination = ProjectHelper.loadStringProperty(project, String destination = ProjectHelper.loadStringProperty(project,
ExportWizard.PROPERTY_DESTINATION); ExportWizard.PROPERTY_DESTINATION);
if (destination != null) { String filename = ProjectHelper.loadStringProperty(project,
mDestination.setText(destination); 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 // reset the wizard with no key/cert to make it not finishable, unless a valid
// key/cert is found. // key/cert is found.
mWizard.setSigningInfo(null, null); mWizard.setSigningInfo(null, null);
mPrivateKey = null;
mCertificate = null;
mKeyDetails = null;
if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) { if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
int validity = mWizard.getValidity(); int validity = mWizard.getValidity();
StringBuilder sb = new StringBuilder( StringBuilder sb = new StringBuilder(
String.format("<form><p>Certificate expires in %d years.</p>", String.format("<p>Certificate expires in %d years.</p>",
validity)); validity));
if (validity < 25) { 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("<p>Android Market currently requires certificates to be valid until 2033.</p>");
} }
sb.append("</form>"); mKeyDetails = sb.toString();
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
} else { } else {
try { try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
@@ -192,10 +227,9 @@ final class KeyCheckPage extends ExportWizardPage {
Calendar today = Calendar.getInstance(); Calendar today = Calendar.getInstance();
if (expirationCalendar.before(today)) { if (expirationCalendar.before(today)) {
mDetailText.setText(String.format( mKeyDetails = String.format(
"<form><p>Certificate expired on %s</p></form>", "<p>Certificate expired on %s</p>",
mCertificate.getNotAfter().toString()), mCertificate.getNotAfter().toString());
true /* parseTags */, true /* expandURLs */);
// fatal error = nothing can make the page complete. // fatal error = nothing can make the page complete.
mFatalSigningError = true; mFatalSigningError = true;
@@ -207,7 +241,7 @@ final class KeyCheckPage extends ExportWizardPage {
mWizard.setSigningInfo(mPrivateKey, mCertificate); mWizard.setSigningInfo(mPrivateKey, mCertificate);
StringBuilder sb = new StringBuilder(String.format( StringBuilder sb = new StringBuilder(String.format(
"<form><p>Certificate expires on %s.</p>", "<p>Certificate expires on %s.</p>",
mCertificate.getNotAfter().toString())); mCertificate.getNotAfter().toString()));
int expirationYear = expirationCalendar.get(Calendar.YEAR); 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("<p>Android Market currently requires certificates to be valid until 2033.</p>");
} }
sb.append("</form>"); mKeyDetails = sb.toString();
mDetailText.setText(sb.toString(), true /* parseTags */, true /* expandURLs */);
} }
mDetailText.getParent().layout();
} else { } else {
// fatal error = nothing can make the page complete. // fatal error = nothing can make the page complete.
mFatalSigningError = true; 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) { if (mFatalSigningError == false) {
// reset messages for now. // reset messages for now.
setErrorMessage(null); setErrorMessage(null);
@@ -257,7 +293,8 @@ final class KeyCheckPage extends ExportWizardPage {
if (path.length() == 0) { if (path.length() == 0) {
setErrorMessage("Enter destination for the APK file."); 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); setPageComplete(false);
return; return;
} }
@@ -265,27 +302,140 @@ final class KeyCheckPage extends ExportWizardPage {
File file = new File(path); File file = new File(path);
if (file.isDirectory()) { if (file.isDirectory()) {
setErrorMessage("Destination is a directory."); 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); setPageComplete(false);
return; return;
} }
File parentFile = file.getParentFile(); File parentFolder = file.getParentFile();
if (parentFile == null || parentFile.isDirectory() == false) { if (parentFolder == null || parentFolder.isDirectory() == false) {
setErrorMessage("Not a valid directory."); 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); setPageComplete(false);
return; 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. // no error, set the destination in the wizard.
mWizard.setDestination(path); mWizard.setDestination(parentFolder, apkFileMap);
setPageComplete(true); setPageComplete(true);
// However, we should also test if the file already exists. // However, we should also test if the file already exists.
if (file.isFile()) { if (fileExists) {
setMessage("Destination file already exists.", WARNING); 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));
}
} }

View File

@@ -266,7 +266,7 @@ final class ProjectCheckPage extends ExportWizardPage {
} }
// update the wizard with the new project // update the wizard with the new project
mWizard.setProject(null, null, null); mWizard.setProject(null);
//test the project name first! //test the project name first!
String text = mProjectText.getText().trim(); String text = mProjectText.getText().trim();
@@ -289,7 +289,7 @@ final class ProjectCheckPage extends ExportWizardPage {
setErrorMessage(null); setErrorMessage(null);
// update the wizard with the new project // update the wizard with the new project
setApkFilePathInWizard(found); mWizard.setProject(found);
// now rebuild the error ui. // now rebuild the error ui.
buildErrorUi(found); 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);
}
} }

View File

@@ -352,11 +352,17 @@ public class NewProjectWizard extends Wizard implements INewWizard {
// Create the resource folders in the project if they don't already exist. // Create the resource folders in the project if they don't already exist.
addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor); addDefaultDirectories(project, RES_DIRECTORY, RES_DIRECTORIES, monitor);
// Setup class path // Setup class path: mark folders as source folders
IJavaProject javaProject = JavaCore.create(project); IJavaProject javaProject = JavaCore.create(project);
for (String sourceFolder : sourceFolders) { for (String sourceFolder : sourceFolders) {
setupSourceFolder(javaProject, sourceFolder, monitor); 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()) { if (((Boolean) parameters.get(PARAM_IS_NEW_PROJECT)).booleanValue()) {
// Create files in the project if they don't already exist // Create files in the project if they don't already exist

View File

@@ -328,7 +328,7 @@ public abstract class AndroidContentAssist implements IContentAssistProcessor {
} }
if (currAttrNode != null) { if (currAttrNode != null) {
choices = currAttrNode.getPossibleValues(); choices = currAttrNode.getPossibleValues(value);
if (currAttrNode instanceof UiFlagAttributeNode) { if (currAttrNode instanceof UiFlagAttributeNode) {
// A "flag" can consist of several values separated by "or" (|). // A "flag" can consist of several values separated by "or" (|).

View File

@@ -616,7 +616,7 @@ public class UiClassAttributeNode extends UiTextAttributeNode {
} }
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
// TODO: compute a list of existing classes for content assist completion // TODO: compute a list of existing classes for content assist completion
return null; return null;
} }

View File

@@ -311,7 +311,7 @@ public class UiPackageAttributeNode extends UiTextAttributeNode {
} }
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
// TODO: compute a list of existing packages for content assist completion // TODO: compute a list of existing packages for content assist completion
return null; return null;
} }

View File

@@ -48,7 +48,7 @@ public class ListValueCellEditor extends ComboBoxCellEditor {
UiListAttributeNode uiListAttribute = (UiListAttributeNode)value; UiListAttributeNode uiListAttribute = (UiListAttributeNode)value;
// set the possible values in the combo // set the possible values in the combo
String[] items = uiListAttribute.getPossibleValues(); String[] items = uiListAttribute.getPossibleValues(null);
mItems = new String[items.length]; mItems = new String[items.length];
System.arraycopy(items, 0, mItems, 0, items.length); System.arraycopy(items, 0, mItems, 0, items.length);
setItems(mItems); setItems(mItems);

View File

@@ -117,9 +117,13 @@ public abstract class UiAttributeNode {
* <p/> * <p/>
* Implementations that do not have any known values should return null. * 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 * Called when the XML is being loaded or has changed to

View File

@@ -124,9 +124,11 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
/** /**
* Get the flag names, either from the initial names set in the attribute * Get the flag names, either from the initial names set in the attribute
* or by querying the framework resource parser. * or by querying the framework resource parser.
*
* {@inheritDoc}
*/ */
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
String attr_name = getDescriptor().getXmlLocalName(); String attr_name = getDescriptor().getXmlLocalName();
String element_name = getUiParent().getDescriptor().getXmlName(); String element_name = getUiParent().getDescriptor().getXmlName();
@@ -242,7 +244,7 @@ public class UiFlagAttributeNode extends UiTextAttributeNode {
final TableColumn column = new TableColumn(mTable, SWT.NONE); final TableColumn column = new TableColumn(mTable, SWT.NONE);
// List all the expected flag names and check those which are currently used // List all the expected flag names and check those which are currently used
String[] names = getPossibleValues(); String[] names = getPossibleValues(null);
if (names != null) { if (names != null) {
for (String name : names) { for (String name : names) {
TableItem item = new TableItem(mTable, SWT.NONE); TableItem item = new TableItem(mTable, SWT.NONE);

View File

@@ -108,7 +108,7 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
} }
protected void fillCombo() { protected void fillCombo() {
String[] values = getPossibleValues(); String[] values = getPossibleValues(null);
if (values == null) { if (values == null) {
AdtPlugin.log(IStatus.ERROR, 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 * Get the list values, either from the initial values set in the attribute
* or by querying the framework resource parser. * or by querying the framework resource parser.
*
* {@inheritDoc}
*/ */
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
AttributeDescriptor descriptor = getDescriptor(); AttributeDescriptor descriptor = getDescriptor();
UiElementNode uiParent = getUiParent(); UiElementNode uiParent = getUiParent();
@@ -134,13 +136,13 @@ public class UiListAttributeNode extends UiAbstractTextAttributeNode {
String element_name = uiParent.getDescriptor().getXmlName(); String element_name = uiParent.getDescriptor().getXmlName();
// FrameworkResourceManager expects a specific prefix for the attribute. // FrameworkResourceManager expects a specific prefix for the attribute.
String prefix = ""; String nsPrefix = "";
if (SdkConstants.NS_RESOURCES.equals(descriptor.getNamespaceUri())) { 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())) { } 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; String[] values = null;

View File

@@ -18,6 +18,7 @@ package com.android.ide.eclipse.editors.uimodel;
import com.android.ide.eclipse.adt.sdk.AndroidTargetData; import com.android.ide.eclipse.adt.sdk.AndroidTargetData;
import com.android.ide.eclipse.common.resources.IResourceRepository; import com.android.ide.eclipse.common.resources.IResourceRepository;
import com.android.ide.eclipse.common.resources.ResourceItem;
import com.android.ide.eclipse.common.resources.ResourceType; import com.android.ide.eclipse.common.resources.ResourceType;
import com.android.ide.eclipse.editors.AndroidEditor; import com.android.ide.eclipse.editors.AndroidEditor;
import com.android.ide.eclipse.editors.descriptors.AttributeDescriptor; 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.FormToolkit;
import org.eclipse.ui.forms.widgets.TableWrapData; 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 * Represents an XML attribute for a resource that can be modified using a simple text field or
* a dialog to choose an existing resource. * a dialog to choose an existing resource.
@@ -154,8 +159,81 @@ public class UiResourceAttributeNode extends UiTextAttributeNode {
} }
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
// TODO: compute a list of existing resources for content assist completion IResourceRepository repository = null;
return 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()]);
} }
} }

View File

@@ -96,9 +96,13 @@ public class UiSeparatorAttributeNode extends UiAttributeNode {
sep.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); 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 @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
return null; return null;
} }

View File

@@ -70,9 +70,13 @@ public class UiTextAttributeNode extends UiAbstractTextAttributeNode {
setTextWidget(text); setTextWidget(text);
} }
/** No completion values for this UI attribute. */ /**
* No completion values for this UI attribute.
*
* {@inheritDoc}
*/
@Override @Override
public String[] getPossibleValues() { public String[] getPossibleValues(String prefix) {
return null; return null;
} }

View File

@@ -25,6 +25,8 @@ import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Set; import java.util.Set;
class LayoutRenderer extends JComponent { class LayoutRenderer extends JComponent {
@@ -34,14 +36,23 @@ class LayoutRenderer extends JComponent {
private boolean showExtras; private boolean showExtras;
private ViewHierarchyScene scene; private ViewHierarchyScene scene;
private JComponent sceneView;
LayoutRenderer(ViewHierarchyScene scene) { LayoutRenderer(ViewHierarchyScene scene, JComponent sceneView) {
this.scene = scene; this.scene = scene;
this.sceneView = sceneView;
setOpaque(true); setOpaque(true);
setBorder(BorderFactory.createEmptyBorder(0, 0, 12, 0)); setBorder(BorderFactory.createEmptyBorder(0, 0, 12, 0));
setBackground(Color.BLACK); setBackground(Color.BLACK);
setForeground(Color.WHITE); setForeground(Color.WHITE);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
selectChild(event.getX(), event.getY());
}
});
} }
@Override @Override
@@ -118,4 +129,49 @@ class LayoutRenderer extends JComponent {
this.showExtras = showExtras; this.showExtras = showExtras;
repaint(); 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;
}
} }

View File

@@ -45,6 +45,8 @@ import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
class ScreenViewer extends JPanel implements ActionListener { class ScreenViewer extends JPanel implements ActionListener {
@@ -69,6 +71,8 @@ class ScreenViewer extends JPanel implements ActionListener {
private Timer timer; private Timer timer;
private ViewNode node; private ViewNode node;
private JSlider zoomSlider;
ScreenViewer(Workspace workspace, Device device, int spacing) { ScreenViewer(Workspace workspace, Device device, int spacing) {
setLayout(new BorderLayout()); setLayout(new BorderLayout());
setOpaque(false); setOpaque(false);
@@ -95,6 +99,7 @@ class ScreenViewer extends JPanel implements ActionListener {
private JPanel buildLoupePanel(int spacing) { private JPanel buildLoupePanel(int spacing) {
loupe = new LoupeViewer(); loupe = new LoupeViewer();
loupe.addMouseWheelListener(new WheelZoomListener());
CrosshairPanel crosshairPanel = new CrosshairPanel(loupe); CrosshairPanel crosshairPanel = new CrosshairPanel(loupe);
JPanel loupePanel = new JPanel(new BorderLayout()); JPanel loupePanel = new JPanel(new BorderLayout());
@@ -106,9 +111,20 @@ class ScreenViewer extends JPanel implements ActionListener {
return loupePanel; 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() { private JPanel buildViewerAndControls() {
JPanel panel = new JPanel(new GridBagLayout()); JPanel panel = new JPanel(new GridBagLayout());
crosshair = new Crosshair(new ScreenshotViewer()); crosshair = new Crosshair(new ScreenshotViewer());
crosshair.addMouseWheelListener(new WheelZoomListener());
panel.add(crosshair, panel.add(crosshair,
new GridBagConstraints(0, y++, 2, 1, 1.0f, 0.0f, new GridBagConstraints(0, y++, 2, 1, 1.0f, 0.0f,
GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
@@ -131,7 +147,8 @@ class ScreenViewer extends JPanel implements ActionListener {
timer.restart(); 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() { new ChangeListener() {
public void stateChanged(ChangeEvent event) { public void stateChanged(ChangeEvent event) {
zoom = ((JSlider) event.getSource()).getValue(); zoom = ((JSlider) event.getSource()).getValue();

View File

@@ -67,6 +67,7 @@ import javax.swing.JMenuItem;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JProgressBar; import javax.swing.JProgressBar;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JScrollBar;
import javax.swing.JSlider; import javax.swing.JSlider;
import javax.swing.JSplitPane; import javax.swing.JSplitPane;
import javax.swing.JTable; import javax.swing.JTable;
@@ -105,6 +106,8 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -173,6 +176,7 @@ public class Workspace extends JFrame {
add(buildMainPanel()); add(buildMainPanel());
setJMenuBar(buildMenuBar()); setJMenuBar(buildMenuBar());
devices.changeSelection(0, 0, false, false);
currentDeviceChanged(); currentDeviceChanged();
pack(); pack();
@@ -648,6 +652,7 @@ public class Workspace extends JFrame {
sceneView = scene.createView(); sceneView = scene.createView();
sceneView.addMouseListener(new NodeClickListener()); sceneView.addMouseListener(new NodeClickListener());
sceneView.addMouseWheelListener(new WheelZoomListener());
sceneScroller.setViewportView(sceneView); sceneScroller.setViewportView(sceneView);
if (extrasPanel != null) { if (extrasPanel != null) {
@@ -678,7 +683,10 @@ public class Workspace extends JFrame {
private JPanel buildExtrasPanel() { private JPanel buildExtrasPanel() {
extrasPanel = new JPanel(new BorderLayout()); 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(scene.createSatelliteView(), BorderLayout.SOUTH);
extrasPanel.add(buildLayoutViewControlButtons(), BorderLayout.NORTH); extrasPanel.add(buildLayoutViewControlButtons(), BorderLayout.NORTH);
return extrasPanel; 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 private class DevicesTableModel extends DefaultTableModel implements
AndroidDebugBridge.IDeviceChangeListener { AndroidDebugBridge.IDeviceChangeListener {

View File

@@ -48,11 +48,49 @@ public final class AvdManager {
private final static String AVD_INFO_PATH = "path"; private final static String AVD_INFO_PATH = "path";
private final static String AVD_INFO_TARGET = "target"; 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"; 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"; 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"; 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"; 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"; 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"; 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 + "$", private final static Pattern INI_NAME_PATTERN = Pattern.compile("(.+)\\" + INI_EXTENSION + "$",
Pattern.CASE_INSENSITIVE); 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]?"); private final static Pattern SDCARD_SIZE_PATTERN = Pattern.compile("\\d+[MK]?");
/** An immutable structure describing an Android Virtual Device. */ /** An immutable structure describing an Android Virtual Device. */