auto import from //depot/cupcake/@136594
This commit is contained in:
@@ -174,14 +174,16 @@ public class SoftKeyboard extends InputMethodService
|
||||
// We now look for a few special variations of text that will
|
||||
// modify our behavior.
|
||||
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
|
||||
// when they are entering a password.
|
||||
mPredictionOn = false;
|
||||
}
|
||||
|
||||
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
|
||||
// or URIs.
|
||||
mPredictionOn = false;
|
||||
@@ -207,6 +209,7 @@ public class SoftKeyboard extends InputMethodService
|
||||
// For all unknown input types, default to the alphabetic
|
||||
// keyboard with no special features.
|
||||
mCurKeyboard = mQwertyKeyboard;
|
||||
updateShiftKeyState(attribute);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (attr != null
|
||||
&& 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);
|
||||
}
|
||||
}
|
||||
@@ -553,7 +560,7 @@ public class SoftKeyboard extends InputMethodService
|
||||
final int length = mComposing.length();
|
||||
if (length > 1) {
|
||||
mComposing.delete(length - 1, length);
|
||||
getCurrentInputConnection().setComposingText(mComposing, mComposing.length());
|
||||
getCurrentInputConnection().setComposingText(mComposing, 1);
|
||||
updateCandidates();
|
||||
} else if (length > 0) {
|
||||
mComposing.setLength(0);
|
||||
@@ -594,7 +601,7 @@ public class SoftKeyboard extends InputMethodService
|
||||
}
|
||||
if (isAlphabet(primaryCode) && mPredictionOn) {
|
||||
mComposing.append((char) primaryCode);
|
||||
getCurrentInputConnection().setComposingText(mComposing, mComposing.length());
|
||||
getCurrentInputConnection().setComposingText(mComposing, 1);
|
||||
updateShiftKeyState(getCurrentInputEditorInfo());
|
||||
updateCandidates();
|
||||
} else {
|
||||
@@ -605,7 +612,7 @@ public class SoftKeyboard extends InputMethodService
|
||||
|
||||
private void handleClose() {
|
||||
commitTyped(getCurrentInputConnection());
|
||||
dismissSoftInput(0);
|
||||
requestHideSelf(0);
|
||||
mInputView.closing();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -1117,19 +1117,6 @@ public class ApkBuilder extends BaseBuilder {
|
||||
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.
|
||||
* @param file the IFile representing the file.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" (|).
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user