| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php |
| * |
| * 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; |
| |
| import com.android.sdklib.IAndroidTarget; |
| import com.android.sdklib.avd.AvdManager.AvdInfo; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.TableItem; |
| |
| |
| /** |
| * The AVD selector is a table that is added to the given parent composite. |
| * <p/> |
| * To use, create it using {@link #AvdSelector(Composite, AvdInfo[])} then |
| * call {@link #setSelection(AvdInfo)}, {@link #setSelectionListener(SelectionListener)} |
| * and finally use {@link #getFirstSelected()} to retrieve the selection. |
| */ |
| public final class AvdSelector { |
| |
| private AvdInfo[] mAvds; |
| private SelectionListener mSelectionListener; |
| private Table mTable; |
| private Label mDescription; |
| |
| /** |
| * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered |
| * by a {@link IAndroidTarget}. |
| * <p/>Only the {@link AvdInfo} able to run application developed for the given |
| * {@link IAndroidTarget} will be displayed. |
| * |
| * @param parent The parent composite where the selector will be added. |
| * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify. |
| */ |
| public AvdSelector(Composite parent, AvdInfo[] avds, IAndroidTarget filter) { |
| mAvds = avds; |
| |
| // Layout has 1 column |
| Composite group = new Composite(parent, SWT.NONE); |
| group.setLayout(new GridLayout()); |
| group.setLayoutData(new GridData(GridData.FILL_BOTH)); |
| group.setFont(parent.getFont()); |
| |
| mTable = new Table(group, SWT.CHECK | SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER); |
| mTable.setHeaderVisible(true); |
| mTable.setLinesVisible(false); |
| |
| GridData data = new GridData(); |
| data.grabExcessVerticalSpace = true; |
| data.grabExcessHorizontalSpace = true; |
| data.horizontalAlignment = GridData.FILL; |
| data.verticalAlignment = GridData.FILL; |
| mTable.setLayoutData(data); |
| |
| mDescription = new Label(group, SWT.WRAP); |
| mDescription.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); |
| |
| // create the table columns |
| final TableColumn column0 = new TableColumn(mTable, SWT.NONE); |
| column0.setText("AVD Name"); |
| final TableColumn column1 = new TableColumn(mTable, SWT.NONE); |
| column1.setText("Target Name"); |
| final TableColumn column2 = new TableColumn(mTable, SWT.NONE); |
| column2.setText("SDK"); |
| final TableColumn column3 = new TableColumn(mTable, SWT.NONE); |
| column3.setText("API Level"); |
| |
| adjustColumnsWidth(mTable, column0, column1, column2, column3); |
| setupSelectionListener(mTable); |
| fillTable(mTable, filter); |
| setupTooltip(mTable); |
| } |
| |
| /** |
| * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}. |
| * |
| * @param parent The parent composite where the selector will be added. |
| * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify. |
| */ |
| public AvdSelector(Composite parent, AvdInfo[] avds) { |
| this(parent, avds, null /* filter */); |
| } |
| |
| |
| public void setTableHeightHint(int heightHint) { |
| GridData data = new GridData(); |
| data.heightHint = heightHint; |
| data.grabExcessVerticalSpace = true; |
| data.grabExcessHorizontalSpace = true; |
| data.horizontalAlignment = GridData.FILL; |
| data.verticalAlignment = GridData.FILL; |
| mTable.setLayoutData(data); |
| } |
| |
| /** |
| * Sets a new set of AVD, with an optional filter. |
| * <p/>This must be called from the UI thread. |
| * |
| * @param avds The list of AVDs. This is <em>not</em> copied, the caller must not modify. |
| * @param filter An IAndroidTarget. If non-null, only AVD whose target are compatible with the |
| * filter target will displayed an available for selection. |
| */ |
| public void setAvds(AvdInfo[] avds, IAndroidTarget filter) { |
| mAvds = avds; |
| fillTable(mTable, filter); |
| } |
| |
| /** |
| * Returns the list of known AVDs. |
| * <p/> |
| * This is not a copy. Callers must <em>not</em> modify this array. |
| */ |
| public AvdInfo[] getAvds() { |
| return mAvds; |
| } |
| |
| /** |
| * Sets a selection listener. Set it to null to remove it. |
| * The listener will be called <em>after</em> this table processed its selection |
| * events so that the caller can see the updated state. |
| * <p/> |
| * The event's item contains a {@link TableItem}. |
| * The {@link TableItem#getData()} contains an {@link IAndroidTarget}. |
| * <p/> |
| * It is recommended that the caller uses the {@link #getFirstSelected()} method instead. |
| * |
| * @param selectionListener The new listener or null to remove it. |
| */ |
| public void setSelectionListener(SelectionListener selectionListener) { |
| mSelectionListener = selectionListener; |
| } |
| |
| /** |
| * Sets the current target selection. |
| * <p/> |
| * If the selection is actually changed, this will invoke the selection listener |
| * (if any) with a null event. |
| * |
| * @param target the target to be selection |
| * @return true if the target could be selected, false otherwise. |
| */ |
| public boolean setSelection(AvdInfo target) { |
| boolean found = false; |
| boolean modified = false; |
| for (TableItem i : mTable.getItems()) { |
| if ((AvdInfo) i.getData() == target) { |
| found = true; |
| if (!i.getChecked()) { |
| modified = true; |
| i.setChecked(true); |
| } |
| } else if (i.getChecked()) { |
| modified = true; |
| i.setChecked(false); |
| } |
| } |
| |
| if (modified && mSelectionListener != null) { |
| mSelectionListener.widgetSelected(null); |
| } |
| |
| return found; |
| } |
| |
| /** |
| * Returns the first selected item. |
| * This is useful when the table is in single-selection mode. |
| * |
| * @return The first selected item or null. |
| */ |
| public AvdInfo getFirstSelected() { |
| for (TableItem i : mTable.getItems()) { |
| if (i.getChecked()) { |
| return (AvdInfo) i.getData(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Enables the receiver if the argument is true, and disables it otherwise. |
| * A disabled control is typically not selectable from the user interface |
| * and draws with an inactive or "grayed" look. |
| * |
| * @param enabled the new enabled state. |
| */ |
| public void setEnabled(boolean enabled) { |
| mTable.setEnabled(enabled); |
| mDescription.setEnabled(enabled); |
| } |
| |
| /** |
| * Adds a listener to adjust the columns width when the parent is resized. |
| * <p/> |
| * If we need something more fancy, we might want to use this: |
| * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co |
| */ |
| private void adjustColumnsWidth(final Table table, |
| final TableColumn column0, |
| final TableColumn column1, |
| final TableColumn column2, |
| final TableColumn column3) { |
| // 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 * 30 / 100); // 30% |
| column1.setWidth(r.width * 45 / 100); // 45% |
| column2.setWidth(r.width * 10 / 100); // 10% |
| column3.setWidth(r.width * 15 / 100); // 15% |
| } |
| }); |
| } |
| |
| |
| /** |
| * Creates a selection listener that will check or uncheck the whole line when |
| * double-clicked (aka "the default selection"). |
| */ |
| private void setupSelectionListener(final Table table) { |
| // Add a selection listener that will check/uncheck items when they are double-clicked |
| table.addSelectionListener(new SelectionListener() { |
| |
| /** |
| * Handles single-click selection on the table. |
| * {@inheritDoc} |
| */ |
| public void widgetSelected(SelectionEvent e) { |
| if (e.item instanceof TableItem) { |
| TableItem i = (TableItem) e.item; |
| enforceSingleSelection(i); |
| updateDescription(i); |
| } |
| |
| if (mSelectionListener != null) { |
| mSelectionListener.widgetSelected(e); |
| } |
| } |
| |
| /** |
| * Handles double-click selection on the table. |
| * Note that the single-click handler will probably already have been called. |
| * |
| * On double-click, <em>always</em> check the table item. |
| * |
| * {@inheritDoc} |
| */ |
| public void widgetDefaultSelected(SelectionEvent e) { |
| if (e.item instanceof TableItem) { |
| TableItem i = (TableItem) e.item; |
| i.setChecked(true); |
| enforceSingleSelection(i); |
| updateDescription(i); |
| } |
| |
| if (mSelectionListener != null) { |
| mSelectionListener.widgetDefaultSelected(e); |
| } |
| } |
| |
| /** |
| * To ensure single selection, uncheck all other items when this one is selected. |
| * This makes the chekboxes act as radio buttons. |
| */ |
| private void enforceSingleSelection(TableItem item) { |
| if (item.getChecked()) { |
| Table parentTable = item.getParent(); |
| for (TableItem i2 : parentTable.getItems()) { |
| if (i2 != item && i2.getChecked()) { |
| i2.setChecked(false); |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Fills the table with all AVD. |
| * The table columns are: |
| * <ul> |
| * <li>column 0: sdk name |
| * <li>column 1: sdk vendor |
| * <li>column 2: sdk api name |
| * <li>column 3: sdk version |
| * </ul> |
| */ |
| private void fillTable(final Table table, IAndroidTarget filter) { |
| table.removeAll(); |
| if (mAvds != null && mAvds.length > 0) { |
| table.setEnabled(true); |
| for (AvdInfo avd : mAvds) { |
| if (filter == null || filter.isCompatibleBaseFor(avd.getTarget())) { |
| TableItem item = new TableItem(table, SWT.NONE); |
| item.setData(avd); |
| item.setText(0, avd.getName()); |
| IAndroidTarget target = avd.getTarget(); |
| item.setText(1, target.getFullName()); |
| item.setText(2, target.getApiVersionName()); |
| item.setText(3, Integer.toString(target.getApiVersionNumber())); |
| } |
| } |
| } |
| |
| if (table.getItemCount() == 0) { |
| table.setEnabled(false); |
| TableItem item = new TableItem(table, SWT.NONE); |
| item.setData(null); |
| item.setText(0, "--"); |
| item.setText(1, "No AVD available"); |
| item.setText(2, "--"); |
| item.setText(3, "--"); |
| } |
| } |
| |
| /** |
| * Sets up a tooltip that displays the current item description. |
| * <p/> |
| * Displaying a tooltip over the table looks kind of odd here. Instead we actually |
| * display the description in a label under the table. |
| */ |
| private void setupTooltip(final Table table) { |
| /* |
| * Reference: |
| * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet125.java?view=markup |
| */ |
| |
| final Listener listener = new Listener() { |
| public void handleEvent(Event event) { |
| |
| switch(event.type) { |
| case SWT.KeyDown: |
| case SWT.MouseExit: |
| case SWT.MouseDown: |
| return; |
| |
| case SWT.MouseHover: |
| updateDescription(table.getItem(new Point(event.x, event.y))); |
| break; |
| |
| case SWT.Selection: |
| if (event.item instanceof TableItem) { |
| updateDescription((TableItem) event.item); |
| } |
| break; |
| |
| default: |
| return; |
| } |
| |
| } |
| }; |
| |
| table.addListener(SWT.Dispose, listener); |
| table.addListener(SWT.KeyDown, listener); |
| table.addListener(SWT.MouseMove, listener); |
| table.addListener(SWT.MouseHover, listener); |
| } |
| |
| /** |
| * Updates the description label with the path of the item's AVD, if any. |
| */ |
| private void updateDescription(TableItem item) { |
| if (item != null) { |
| Object data = item.getData(); |
| if (data instanceof AvdInfo) { |
| String newTooltip = ((AvdInfo) data).getPath(); |
| mDescription.setText(newTooltip == null ? "" : newTooltip); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |