diff options
author | Raphael Moll <ralf@android.com> | 2012-10-11 23:19:37 -0700 |
---|---|---|
committer | Raphael Moll <ralf@android.com> | 2012-10-12 16:34:38 -0700 |
commit | 65d21372a2f299c767e4b93893398acf271a0ef7 (patch) | |
tree | b90a366ded2370248752c724497635f1ce7e82c7 /sdkmanager/libs/sdkuilib | |
parent | 6c45a16548becd0e448e15bf9d176ea71421eb58 (diff) | |
download | sdk-65d21372a2f299c767e4b93893398acf271a0ef7.zip sdk-65d21372a2f299c767e4b93893398acf271a0ef7.tar.gz sdk-65d21372a2f299c767e4b93893398acf271a0ef7.tar.bz2 |
AVD: display devices in a tab+list.
Change-Id: I70c6e0fa0b9622e8050e5d949674377e5ac0ffad
Diffstat (limited to 'sdkmanager/libs/sdkuilib')
11 files changed, 917 insertions, 85 deletions
diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_generic_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_generic_16.png Binary files differnew file mode 100755 index 0000000..6f59cd4 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_generic_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_manufacturer_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_manufacturer_16.png Binary files differnew file mode 100755 index 0000000..422276d --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_manufacturer_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_user_16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_user_16.png Binary files differnew file mode 100755 index 0000000..f8a173c --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/devman_user_16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/warning_icon16.png b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/warning_icon16.png Binary files differindex 0f5128d..ca3b6ed 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/warning_icon16.png +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/icons/warning_icon16.png diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AddonSitesDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AddonSitesDialog.java index 2b9f072..77f82b1 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AddonSitesDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AddonSitesDialog.java @@ -54,7 +54,6 @@ import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; @@ -110,7 +109,6 @@ public class AddonSitesDialog extends UpdaterBaseDialog { shell.setSize(600, 400); TabFolder tabFolder = new TabFolder(shell, SWT.NONE); - tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); GridDataBuilder.create(tabFolder).fill().grab().hSpan(2); TabItem sitesTabItem = new TabItem(tabFolder, SWT.NONE); diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AvdManagerWindowImpl1.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AvdManagerWindowImpl1.java index ae6ba1c..36e01ba 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AvdManagerWindowImpl1.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/AvdManagerWindowImpl1.java @@ -18,7 +18,6 @@ package com.android.sdkuilib.internal.repository.ui; import com.android.SdkConstants; -import com.android.sdklib.devices.Device; import com.android.sdklib.devices.DeviceManager; import com.android.sdklib.internal.repository.ITaskFactory; import com.android.sdkuilib.internal.repository.AboutDialog; @@ -27,30 +26,27 @@ import com.android.sdkuilib.internal.repository.SettingsController; import com.android.sdkuilib.internal.repository.SettingsDialog; import com.android.sdkuilib.internal.repository.UpdaterData; import com.android.sdkuilib.internal.repository.icons.ImageFactory; -import com.android.sdkuilib.internal.widgets.DeviceCreationDialog; import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext; import com.android.sdkuilib.repository.ISdkChangeListener; import com.android.sdkuilib.repository.SdkUpdaterWindow; +import com.android.sdkuilib.ui.GridDataBuilder; +import com.android.sdkuilib.ui.GridLayoutBuilder; import com.android.utils.ILogger; -import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import org.eclipse.swt.widgets.TabFolder; +import org.eclipse.swt.widgets.TabItem; /** * This is an intermediate version of the {@link AvdManagerPage} @@ -169,10 +165,6 @@ public class AvdManagerWindowImpl1 { }); GridLayout glShell = new GridLayout(2, false); - glShell.verticalSpacing = 0; - glShell.horizontalSpacing = 0; - glShell.marginWidth = 0; - glShell.marginHeight = 0; mShell.setLayout(glShell); mShell.setMinimumSize(new Point(500, 300)); @@ -184,8 +176,36 @@ public class AvdManagerWindowImpl1 { private void createContents() { - mAvdPage = new AvdManagerPage(mShell, SWT.NONE, mUpdaterData, mDeviceManager); - mAvdPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + TabFolder tabFolder = new TabFolder(mShell, SWT.NONE); + GridDataBuilder.create(tabFolder).fill().grab().hSpan(2); + + // avd tab + TabItem avdTabItem = new TabItem(tabFolder, SWT.NONE); + avdTabItem.setText("Android Virtual Devices"); + createAvdTab(tabFolder, avdTabItem); + + // device tab + TabItem devTabItem = new TabItem(tabFolder, SWT.NONE); + devTabItem.setText("Device Definitions"); + createDeviceTab(tabFolder, devTabItem); + } + + private void createAvdTab(TabFolder tabFolder, TabItem avdTabItem) { + Composite root = new Composite(tabFolder, SWT.NONE); + avdTabItem.setControl(root); + GridLayoutBuilder.create(root).columns(1); + + mAvdPage = new AvdManagerPage(root, SWT.NONE, mUpdaterData, mDeviceManager); + GridDataBuilder.create(mAvdPage).fill().grab(); + } + + private void createDeviceTab(TabFolder tabFolder, TabItem devTabItem) { + Composite root = new Composite(tabFolder, SWT.NONE); + devTabItem.setControl(root); + GridLayoutBuilder.create(root).columns(1); + + DeviceManagerPage container = new DeviceManagerPage(root, SWT.NONE, mUpdaterData, mDeviceManager); + GridDataBuilder.create(container).fill().grab(); } @SuppressWarnings("unused") @@ -242,10 +262,6 @@ public class AvdManagerWindowImpl1 { e.printStackTrace(); } } - - MenuItem menuBarDevices = new MenuItem(menuBar, SWT.CASCADE); - menuBarDevices.setText("Devices"); - setupDevices(menuBarDevices); } @@ -350,62 +366,6 @@ public class AvdManagerWindowImpl1 { } /** - * Sets up the devices in the device menu. - */ - @SuppressWarnings("unused") - private void setupDevices(final MenuItem menuBarDevices) { - Menu menuDevices = new Menu(menuBarDevices); - menuBarDevices.setMenu(menuDevices); - - MenuItem createDevice = new MenuItem(menuDevices, SWT.NONE); - createDevice.setText("Create New Device"); - createDevice.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - DeviceCreationDialog dlg = new DeviceCreationDialog( - mShell, mDeviceManager, mUpdaterData.getImageFactory(), null); - if (dlg.open() == Window.OK) { - setupDevices(menuBarDevices); - } - } - }); - new MenuItem(menuDevices, SWT.SEPARATOR); - - Map<String, List<Device>> devices = new HashMap<String, List<Device>>(); - for (Device d : mDeviceManager.getDevices(mUpdaterData.getOsSdkRoot())) { - List<Device> l; - if (devices.containsKey(d.getManufacturer())) { - l = devices.get(d.getManufacturer()); - } else { - l = new ArrayList<Device>(); - devices.put(d.getManufacturer(), l); - } - l.add(d); - } - - for (String manufacturer : devices.keySet()) { - MenuItem manufacturerItem = new MenuItem(menuDevices, SWT.CASCADE); - manufacturerItem.setText(manufacturer); - Menu manufacturerMenu = new Menu(menuDevices); - manufacturerItem.setMenu(manufacturerMenu); - for (final Device d : devices.get(manufacturer)) { - MenuItem deviceItem = new MenuItem(manufacturerMenu, SWT.NONE); - deviceItem.setText(d.getName()); - deviceItem.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - DeviceCreationDialog dlg = new DeviceCreationDialog( - mShell, mDeviceManager, mUpdaterData.getImageFactory(), d); - if(dlg.open() == Window.OK) { - setupDevices(menuBarDevices); - } - } - }); - } - } - } - - /** * Initializes settings. * This must be called after addExtraPages(), which created a settings page. * Iterate through all the pages to find the first (and supposedly unique) setting page, diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/DeviceManagerPage.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/DeviceManagerPage.java new file mode 100755 index 0000000..62f8bd5 --- /dev/null +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/repository/ui/DeviceManagerPage.java @@ -0,0 +1,818 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sdkuilib.internal.repository.ui; + +import com.android.sdklib.devices.Device; +import com.android.sdklib.devices.DeviceManager; +import com.android.sdklib.devices.DeviceManager.DevicesChangeListener; +import com.android.sdklib.devices.Hardware; +import com.android.sdklib.devices.Screen; +import com.android.sdklib.devices.Storage; +import com.android.sdklib.devices.Storage.Unit; +import com.android.sdklib.internal.avd.AvdInfo; +import com.android.sdkuilib.internal.repository.UpdaterData; +import com.android.sdkuilib.internal.repository.icons.ImageFactory; +import com.android.sdkuilib.internal.widgets.AvdCreationDialog; +import com.android.sdkuilib.internal.widgets.AvdSelector; +import com.android.sdkuilib.internal.widgets.DeviceCreationDialog; +import com.android.sdkuilib.repository.ISdkChangeListener; +import com.android.sdkuilib.ui.GridDataBuilder; +import com.android.sdkuilib.ui.GridLayoutBuilder; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.graphics.Resource; +import org.eclipse.swt.graphics.TextLayout; +import org.eclipse.swt.graphics.TextStyle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A page displaying Device Manager entries. + * <p/> + * This is displayed as a second tab in the AVD Manager window. + * The layout purposely matches the one from {@link AvdManagerPage} and {@link AvdSelector} + * so that there's a good consistency when switching tabs. + * The table displays a few properties of each device as well as actions to edit/add/delete + * devices and a button to create an AVD from a given device. + * + * Non-goals: this tries to keep it simple for a first iteration. Possible enhancements: + * - a way to sort the device list by name, manufacturer or screen size. + * - possibly a tree organized by manufacturer. + * - a filter box to do a string search on any part of the display. + */ +public class DeviceManagerPage extends Composite + implements ISdkChangeListener, DevicesChangeListener, DisposeListener { + + private final UpdaterData mUpdaterData; + private final DeviceManager mDeviceManager; + private Table mTable; + private Button mNewButton; + private Button mEditButton; + private Button mDeleteButton; + private Button mNewAvdButton; + private Button mRefreshButton; + private ImageFactory mImageFactory; + private Image mUserImage; + private Image mGenericImage; + private Image mOtherImage; + private int mImageWidth; + private boolean mDisableRefresh; + /** + * Create the composite. + * @param parent The parent of the composite. + * @param updaterData An instance of {@link UpdaterData}. + */ + public DeviceManagerPage(Composite parent, + int swtStyle, + UpdaterData updaterData, + DeviceManager deviceManager) { + super(parent, swtStyle); + + mUpdaterData = updaterData; + mUpdaterData.addListeners(this); + + mDeviceManager = deviceManager; + mDeviceManager.registerListener(this); + + createContents(this); + postCreate(); //$hide$ + } + + private void createContents(Composite parent) { + + // get some bitmaps. + mImageFactory = new ImageFactory(parent.getDisplay()); + mUserImage = mImageFactory.getImageByName("devman_user_16.png"); + mGenericImage = mImageFactory.getImageByName("devman_generic_16.png"); + mOtherImage = mImageFactory.getImageByName("devman_manufacturer_16.png"); + mImageWidth = Math.max(mGenericImage.getImageData().width, + Math.max(mUserImage.getImageData().width, + mOtherImage.getImageData().width)); + + // Layout has 2 columns + GridLayoutBuilder.create(parent).columns(2); + + // Insert a top label explanation. This matches the design in AvdManagerPage so + // that the table starts at the same height on both tabs. + Label label = new Label(parent, SWT.NONE); + label.setText("List of known device definitions. This can later be used to create Android Virtual Devices."); + GridDataBuilder.create(label).hSpan(2); + + // Device table. + mTable = new Table(parent, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER); + mTable.setHeaderVisible(true); + mTable.setLinesVisible(true); + mTable.setFont(parent.getFont()); + setTableHeightHint(30); + + // Buttons on the side. + Composite buttons = new Composite(parent, SWT.NONE); + GridLayoutBuilder.create(buttons).columns(1).noMargins(); + GridDataBuilder.create(buttons).vFill(); + buttons.setFont(parent.getFont()); + + mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNewButton.setText("New Device..."); + mNewButton.setToolTipText("Creates a new user device definition."); + mNewButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onNewDevice(); + } + }); + + mEditButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mEditButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mEditButton.setText("Edit..."); + mEditButton.setToolTipText("Edit an existing device definition."); + mEditButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onEditDevice(); + } + }); + + mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mDeleteButton.setText("Delete..."); + mDeleteButton.setToolTipText("Deletes the selected AVD."); + mDeleteButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onDeleteDevice(); + } + }); + + @SuppressWarnings("unused") + Label spacing = new Label(buttons, SWT.NONE); + + mNewAvdButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mNewAvdButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mNewAvdButton.setText("Create AVD..."); + mNewAvdButton.setToolTipText("Creates a new AVD based on this device definition."); + mNewAvdButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onNewAvd(); + } + }); + + Composite padding = new Composite(buttons, SWT.NONE); + padding.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + + mRefreshButton = new Button(buttons, SWT.PUSH | SWT.FLAT); + mRefreshButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mRefreshButton.setText("Refresh"); + mRefreshButton.setToolTipText("Reloads the list of devices."); + mRefreshButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent arg0) { + onRefresh(); + } + }); + + // Legend at the bottom. + // This matches the one on AvdSelector so that the table height in the tab be similar. + Composite legend = new Composite(parent, SWT.NONE); + GridLayoutBuilder.create(legend).columns(4).noMargins(); + GridDataBuilder.create(legend).hFill().vTop().hGrab().hSpan(2); + legend.setFont(parent.getFont()); + + new Label(legend, SWT.NONE).setImage(mUserImage); + new Label(legend, SWT.NONE).setText("A user-created device definition."); + new Label(legend, SWT.NONE).setImage(mGenericImage); + new Label(legend, SWT.NONE).setText("A generic device definition."); + new Label(legend, SWT.NONE).setImage(mOtherImage); + Label l = new Label(legend, SWT.NONE); + l.setText("A manufacturer-specific device definition."); + GridData gd; + l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL)); + gd.horizontalSpan = 3; + + + // create the table columns + final TableColumn column0 = new TableColumn(mTable, SWT.NONE); + column0.setText("Device"); + + adjustColumnsWidth(mTable, column0); + setupSelectionListener(mTable); + fillTable(mTable); + updateButtonStates(); + setEnabled(true); + } + + private void adjustColumnsWidth(final Table table, final TableColumn column0) { + // Add a listener to resize the column to the full width of the table + table.addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + Rectangle r = table.getClientArea(); + column0.setWidth(r.width * 100 / 100 - 1); // 100% + } + }); + } + + private void setupSelectionListener(Table table) { + // TODO Auto-generated method stub + + } + + /** + * Sets the table grid layout data. + * + * @param heightHint If > 0, the height hint is set to the requested value. + */ + public void setTableHeightHint(int heightHint) { + GridData data = new GridData(); + if (heightHint > 0) { + data.heightHint = heightHint; + } + data.grabExcessVerticalSpace = true; + data.grabExcessHorizontalSpace = true; + data.horizontalAlignment = GridData.FILL; + data.verticalAlignment = GridData.FILL; + mTable.setLayoutData(data); + } + + @Override + public void widgetDisposed(DisposeEvent e) { + dispose(); + } + + @Override + public void dispose() { + mUpdaterData.removeListener(this); + mDeviceManager.unregisterListener(this); + super.dispose(); + } + + @Override + protected void checkSubclass() { + // Disable the check that prevents subclassing of SWT components + } + + // -- Start of internal part ---------- + // Hide everything down-below from SWT designer + //$hide>>$ + + /** + * Called by the constructor right after {@link #createContents(Composite)}. + */ + private void postCreate() { + // nothing to be done for now. + } + + + // ------- + + private static class CellInfo { + final boolean mIsUser; + final Device mDevice; + final TextLayout mWidget; + Rectangle mBounds; + + CellInfo(boolean isUser, Device device, TextLayout widget) { + mIsUser = isUser; + mDevice = device; + mWidget = widget; + } + } + + private void fillTable(final Table table) { + + table.removeAll(); + disposeTableResources(table.getData("disposeResources")); + + final List<Resource> disposables = new ArrayList<Resource>(); + + Font boldFont = getBoldFont(table); + if (boldFont != null) { + disposables.add(boldFont); + } else { + boldFont = table.getFont(); + } + + try { + mDisableRefresh = true; + disposables.addAll(fillDevices(table, boldFont, true, + mDeviceManager.getUserDevices(), + null)); + disposables.addAll(fillDevices(table, boldFont, false, + mDeviceManager.getDefaultDevices(), + mDeviceManager.getVendorDevices(mUpdaterData.getOsSdkRoot()))); + } finally { + mDisableRefresh = false; + } + + table.setData("disposeResources", disposables); + + if (!Boolean.TRUE.equals(table.getData("createdTableListeners"))) { + table.addListener(SWT.PaintItem, new Listener() { + @Override + public void handleEvent(Event event) { + if (event.item != null) { + Object info = event.item.getData(); + if (info instanceof CellInfo) { + ((CellInfo) info).mWidget.draw(event.gc, event.x, event.y + 1); + } + } + } + }); + + table.addListener(SWT.MeasureItem, new Listener() { + @Override + public void handleEvent(Event event) { + if (event.item != null) { + Object info = event.item.getData(); + if (info instanceof CellInfo) { + CellInfo ci = (CellInfo) info; + Rectangle bounds = ci.mBounds; + if (bounds == null) { + // TextLayout.getBounds() seems expensive, so let's cache it. + ci.mBounds = bounds = ci.mWidget.getBounds(); + } + event.width = bounds.width + 2; + event.height = bounds.height + 4; + } + } + } + }); + + table.addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent event) { + disposeTableResources(table.getData("disposeResources")); + } + }); + + table.addSelectionListener(new SelectionListener() { + /** Handles single clicks on a row. */ + @Override + public void widgetSelected(SelectionEvent event) { + updateButtonStates(); + } + + /** Handles double click on a row. */ + @Override + public void widgetDefaultSelected(SelectionEvent event) { + // FIXME: should double-click be to edit a device or create a new AVD? + onEditDevice(); + } + }); + } + + if (table.getItemCount() == 0) { + table.setEnabled(true); + TableItem item = new TableItem(table, SWT.NONE); + item.setData(null); + item.setText(0, "No devices available"); + return; + } + + table.setData("createdTableListeners", Boolean.TRUE); + } + + private void disposeTableResources(Object disposablesList) { + if (disposablesList instanceof List<?>) { + for (Object obj : (List<?>) disposablesList) { + if (obj instanceof Resource) { + ((Resource) obj).dispose(); + } + } + } + } + + private Font getBoldFont(Table table) { + Display display = table.getDisplay(); + FontData[] fds = table.getFont().getFontData(); + if (fds != null && fds.length > 0) { + fds[0].setStyle(SWT.BOLD); + return new Font(display, fds[0]); + } + return null; + } + + private List<Resource> fillDevices( + Table table, + Font boldFont, + boolean isUser, + List<Device> devices1, + List<Device> devices2) { + List<Resource> disposables = new ArrayList<Resource>(); + Display display = table.getDisplay(); + + TextStyle boldStyle = new TextStyle(); + boldStyle.font = boldFont; + + + List<Device> devices = new ArrayList<Device>(devices1); + if (devices2 != null) { + devices.addAll(devices2); + } + + if (isUser) { + // Just sort user devices by alphabetical name. They will show up at the top. + Collections.sort(devices, new Comparator<Device>() { + @Override + public int compare(Device d1, Device d2) { + String s1 = d1 == null ? "" : d1.getName(); + String s2 = d2 == null ? "" : d2.getName(); + return s1.compareTo(s2); + }}); + } else { + // Sort non-user devices by descending "pretty name" + // TODO revisit. Doesn't perform as well as expected. + Collections.sort(devices, new Comparator<Device>() { + @Override + public int compare(Device d1, Device d2) { + String s1 = getPrettyName(d1, true /*leadZeroes*/); + String s2 = getPrettyName(d2, true /*leadZeroes*/); + return s2.compareTo(s1); + }}); + } + + // Generate a list of the AVD names using these devices + Map<Device, List<String>> device2avdMap = new HashMap<Device, List<String>>(); + for (AvdInfo avd : mUpdaterData.getAvdManager().getAllAvds()) { + String n = avd.getDeviceName(); + String m = avd.getDeviceManufacturer(); + if (n == null || m == null || n.isEmpty() || m.isEmpty()) { + continue; + } + for (Device device : devices) { + if (m.equals(device.getManufacturer()) && n.equals(device.getName())) { + List<String> list = device2avdMap.get(device); + if (list == null) { + list = new LinkedList<String>(); + device2avdMap.put(device, list); + } + list.add(avd.getName()); + } + } + } + + final String prefix = "\n "; + + for (Device device : devices) { + TableItem item = new TableItem(table, SWT.NONE); + TextLayout widget = new TextLayout(display); + CellInfo ci = new CellInfo(isUser, device, widget); + item.setData(ci); + + widget.setIndent(mImageWidth * 2); + widget.setFont(table.getFont()); + + StringBuilder sb = new StringBuilder(); + String name = getPrettyName(device, false /*leadZeroes*/); + sb.append(name); + int pos1 = sb.length(); + + String manu = device.getManufacturer(); + if (isUser) { + item.setImage(mUserImage); + } else if (GENERIC.equals(manu)) { + item.setImage(mGenericImage); + } else { + item.setImage(mOtherImage); + sb.append(" by ").append(device.getManufacturer()); + } + + Hardware hw = device.getDefaultHardware(); + Screen screen = hw.getScreen(); + sb.append(prefix); + sb.append(String.format(java.util.Locale.US, + "Screen: %1$.1f\", %2$d \u00D7 %3$d, %4$s %5$s", // U+00D7: Unicode multiplication sign + screen.getDiagonalLength(), + screen.getXDimension(), + screen.getYDimension(), + screen.getSize().getShortDisplayValue(), + screen.getPixelDensity().getResourceValue() + )); + + Storage sto = hw.getRam(); + Unit unit = sto.getSizeAsUnit(Unit.GiB) > 1 ? Unit.GiB : Unit.MiB; + sb.append(prefix); + sb.append(String.format(java.util.Locale.US, + "RAM: %1$d %2$s", + sto.getSizeAsUnit(unit), + unit)); + + List<String> avdList = device2avdMap.get(device); + if (avdList != null && !avdList.isEmpty()) { + sb.append(prefix); + sb.append("Used by: "); + boolean first = true; + for (String avd : avdList) { + if (!first) { + sb.append(", "); + } + sb.append(avd); + first = false; + } + } + + widget.setText(sb.toString()); + widget.setStyle(boldStyle, 0, pos1); + } + + return disposables; + } + + // Constants extracted from DeviceMenuListerner -- TODO refactor somewhere else. + @SuppressWarnings("unused") + private static final String NEXUS = "Nexus"; //$NON-NLS-1$ + private static final String GENERIC = "Generic"; //$NON-NLS-1$ + private static Pattern PATTERN = Pattern.compile( + "(\\d+\\.?\\d*)in (.+?)( \\(.*Nexus.*\\))?"); //$NON-NLS-1$ + /** + * Returns a pretty name for the device. + * + * Extracted from DeviceMenuListerner. + * Modified to remove the leading space insertion as it doesn't render + * neatly in the avd manager. Instead added the option to add leading + * zeroes to make the string names sort properly. + * + * Replace "'in'" with '"' (e.g. 2.7" QVGA instead of 2.7in QVGA) + * Use the same precision for all devices (all but one specify decimals) + * Add in screen resolution and density + */ + private static String getPrettyName(Device d, boolean leadZeroes) { + if (d == null) { + return ""; + } + String name = d.getName(); + if (name.equals("3.7 FWVGA slider")) { //$NON-NLS-1$ + // Fix metadata: this one entry doesn't have "in" like the rest of them + name = "3.7in FWVGA slider"; //$NON-NLS-1$ + } + + Matcher matcher = PATTERN.matcher(name); + if (matcher.matches()) { + String size = matcher.group(1); + String n = matcher.group(2); + int dot = size.indexOf('.'); + if (dot == -1) { + size = size + ".0"; + dot = size.length() - 2; + } + if (leadZeroes && dot < 3) { + // Pad to have at least 3 digits before the dot, for sorting purposes. + // We can revisit this once we get devices that are more than 999 inches wide. + size = "000".substring(dot) + size; + } + name = size + "\" " + n; + } + + return name; + } + + /** + * Returns the currently selected cell info in the table or null + */ + private CellInfo getTableSelection() { + if (mTable.isDisposed()) { + return null; + } + int selIndex = mTable.getSelectionIndex(); + if (selIndex >= 0) { + return (CellInfo) mTable.getItem(selIndex).getData(); + } + + return null; + } + + private void updateButtonStates() { + CellInfo ci = getTableSelection(); + + mNewButton.setEnabled(true); + mEditButton.setEnabled(ci != null); + mEditButton.setText((ci != null && !ci.mIsUser) ? "Clone..." : "Edit..."); + mDeleteButton.setEnabled(ci != null && ci.mIsUser); + mNewAvdButton.setEnabled(ci != null); + mRefreshButton.setEnabled(true); + } + + private void onNewDevice() { + DeviceCreationDialog dlg = new DeviceCreationDialog( + getShell(), + mDeviceManager, + mUpdaterData.getImageFactory(), + null /*device*/); + if (dlg.open() == Window.OK) { + onRefresh(); + + // Select the new device, if any. + selectCellByDevice(dlg.getCreatedDevice()); + updateButtonStates(); + } + } + + private void onEditDevice() { + CellInfo ci = getTableSelection(); + if (ci == null || ci.mDevice == null) { + return; + } + + DeviceCreationDialog dlg = new DeviceCreationDialog( + getShell(), + mDeviceManager, + mUpdaterData.getImageFactory(), + ci.mDevice); + if (dlg.open() == Window.OK) { + onRefresh(); + + // Select the new device, if any. + selectCellByDevice(dlg.getCreatedDevice()); + updateButtonStates(); + } + } + + private void onDeleteDevice() { + CellInfo ci = getTableSelection(); + if (ci == null || ci.mDevice == null || !ci.mIsUser) { + return; + } + + final String name = getPrettyName(ci.mDevice, false /*leadZeroes*/); + final AtomicBoolean result = new AtomicBoolean(false); + getDisplay().syncExec(new Runnable() { + @Override + public void run() { + Shell shell = getDisplay().getActiveShell(); + boolean ok = MessageDialog.openQuestion(shell, + "Delete Device Definition", + String.format( + "Please confirm that you want to delete the device definition named '%s'. This operation cannot be reverted.", + name)); + result.set(ok); + } + }); + + if (result.get()) { + mDeviceManager.removeUserDevice(ci.mDevice); + onRefresh(); + } + } + + private void onNewAvd() { + CellInfo ci = getTableSelection(); + if (ci == null || ci.mDevice == null) { + return; + } + + AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(), + mUpdaterData.getAvdManager(), + mImageFactory, + mUpdaterData.getSdkLog(), + null); + dlg.setlectInitialDevice(ci.mDevice); + + if (dlg.open() == Window.OK) { + onRefresh(); + } + } + + private void onRefresh() { + if (mDisableRefresh || mTable.isDisposed()) { + return; + } + int selIndex = mTable.getSelectionIndex(); + CellInfo selected = getTableSelection(); + + fillTable(mTable); + + if (selected != null) { + if (selectCellByName(selected)) { + updateButtonStates(); + return; + } + } + // If not found by name, use the position if available. + if (selIndex >= 0 && selIndex < mTable.getItemCount()) { + mTable.select(selIndex); + } + } + + private boolean selectCellByName(CellInfo selected) { + if (mTable.isDisposed() || selected == null || selected.mDevice == null) { + return false; + } + String name = selected.mDevice.getName(); + for (int n = mTable.getItemCount() - 1; n >= 0; n--) { + TableItem item = mTable.getItem(n); + Object data = item.getData(); + if (data instanceof CellInfo) { + CellInfo ci = (CellInfo) data; + if (ci != null && ci.mDevice != null && name.equals(ci.mDevice.getName())) { + // Same cell object. Select it. + mTable.select(n); + return true; + } + } + } + return false; + } + + private boolean selectCellByDevice(Device selected) { + if (mTable.isDisposed() || selected == null) { + return false; + } + for (int n = mTable.getItemCount() - 1; n >= 0; n--) { + TableItem item = mTable.getItem(n); + Object data = item.getData(); + if (data instanceof CellInfo) { + CellInfo ci = (CellInfo) data; + if (ci != null && ci.mDevice == selected) { + // Same device object. Select it. + mTable.select(n); + return true; + } + } + } + return false; + } + + // ------- + + + // --- Implementation of ISdkChangeListener --- + + @Override + public void onSdkLoaded() { + onSdkReload(); + } + + @Override + public void onSdkReload() { + onRefresh(); + } + + @Override + public void preInstallHook() { + // nothing to be done for now. + } + + @Override + public void postInstallHook() { + // nothing to be done for now. + } + + // --- Implementation of DevicesChangeListener + + @Override + public void onDevicesChange() { + onRefresh(); + } + + + // End of hiding from SWT Designer + //$hide<<$ +} diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java index 4006ac6..657dbda 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdCreationDialog.java @@ -133,6 +133,7 @@ public class AvdCreationDialog extends GridDialog { } } }; + private Device mInitWithDevice; public AvdCreationDialog(Shell shell, AvdManager avdManager, @@ -174,6 +175,8 @@ public class AvdCreationDialog extends GridDialog { if (mAvdInfo != null) { fillExistingAvdInfo(mAvdInfo); + } else if (mInitWithDevice != null) { + fillInitialDeviceInfo(mInitWithDevice); } validatePage(); @@ -433,7 +436,15 @@ public class AvdCreationDialog extends GridDialog { mStatusLabel = new Label(mStatusComposite, SWT.NONE); mStatusLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); mStatusLabel.setText(""); //$NON-NLS-1$ + } + /** + * Can be called after the constructor to set the default device for this AVD. + * Useful especially for new AVDs. + * @param device + */ + public void setlectInitialDevice(Device device) { + mInitWithDevice = device; } /** @@ -1135,6 +1146,38 @@ public class AvdCreationDialog extends GridDialog { } } + private void fillInitialDeviceInfo(Device device) { + String name = device.getManufacturer(); + if (!name.equals("Generic") && !name.equals("User")) { // TODO define & use constants + name = " by " + name; + } else { + name = ""; + } + name = "AVD for " + device.getName() + name; + // sanitize the name + name = name.replaceAll("[^0-9a-zA-Z_-]+", " ").trim().replaceAll("[ _]+", "_"); + mAvdName.setText(name); + + String manufacturer = device.getManufacturer(); + for (int i = 0; i < mDeviceManufacturer.getItemCount(); i++) { + if (mDeviceManufacturer.getItem(i).equals(manufacturer)) { + mDeviceManufacturer.select(i); + break; + } + } + reloadDeviceNameCombo(); + + String deviceName = device.getName(); + for (int i = 0; i < mDeviceName.getItemCount(); i++) { + if (mDeviceName.getItem(i).equals(deviceName)) { + mDeviceName.select(i); + break; + } + } + toggleCameras(); + + } + /** * Returns the list of system images of a target. * <p/> @@ -1161,5 +1204,4 @@ public class AvdCreationDialog extends GridDialog { return new ISystemImage[0]; } - } diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java index ab8e1c9..42d85eb 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/AvdSelector.java @@ -438,6 +438,7 @@ public final class AvdSelector { public void setSettingsController(SettingsController controller) { mController = controller; } + /** * Sets the table grid layout data. * diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/DeviceCreationDialog.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/DeviceCreationDialog.java index ed52999..82972cf 100644 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/DeviceCreationDialog.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/internal/widgets/DeviceCreationDialog.java @@ -118,12 +118,15 @@ public class DeviceCreationDialog extends GridDialog { private Button mOkButton; - // The hardware instance attached to each of the states of the created - // device + /** The hardware instance attached to each of the states of the created device. */ private Hardware mHardware; - // This contains the Software for the device. Since it has no effect on the - // emulator whatsoever, we just use a single instance with reasonable - // defaults. + /** The instance of the Device created by the dialog, if the user pressed {@code mOkButton}. */ + private Device mCreatedDevice; + + /** + * This contains the Software for the device. Since it has no effect on the + * emulator whatsoever, we just use a single instance with reasonable + * defaults. */ private static final Software mSoftware; static { @@ -143,6 +146,15 @@ public class DeviceCreationDialog extends GridDialog { mUserDevices = mManager.getUserDevices(); } + /** + * Returns the instance of the Device created by the dialog, + * if the user pressed the OK|create|edit|clone button. + * Typically only non-null if the dialog returns OK. + */ + public Device getCreatedDevice() { + return mCreatedDevice; + } + @Override protected Control createContents(Composite parent) { Control control = super.createContents(parent); @@ -1053,6 +1065,7 @@ public class DeviceCreationDialog extends GridDialog { mManager.addUserDevice(d); } mManager.saveUserDevices(); + mCreatedDevice = d; super.okPressed(); } } diff --git a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/GridLayoutBuilder.java b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/GridLayoutBuilder.java index fbb31ce..7e8c161 100755 --- a/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/GridLayoutBuilder.java +++ b/sdkmanager/libs/sdkuilib/src/com/android/sdkuilib/ui/GridLayoutBuilder.java @@ -30,7 +30,7 @@ import org.eclipse.swt.widgets.Composite; */ public final class GridLayoutBuilder { - private static GridLayout mGL; + private GridLayout mGL; private GridLayoutBuilder() { mGL = new GridLayout(); @@ -41,7 +41,7 @@ public final class GridLayoutBuilder { */ static public GridLayoutBuilder create(Composite parent) { GridLayoutBuilder glh = new GridLayoutBuilder(); - parent.setLayout(GridLayoutBuilder.mGL); + parent.setLayout(glh.mGL); return glh; } |