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