Home | History | Annotate | Download | only in widgets
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.sdkuilib.internal.widgets;
     18 
     19 import com.android.prefs.AndroidLocation.AndroidLocationException;
     20 import com.android.sdklib.IAndroidTarget;
     21 import com.android.sdklib.ISdkLog;
     22 import com.android.sdklib.NullSdkLog;
     23 import com.android.sdklib.SdkConstants;
     24 import com.android.sdklib.internal.avd.AvdManager;
     25 import com.android.sdklib.internal.avd.AvdManager.AvdInfo;
     26 import com.android.sdklib.internal.avd.AvdManager.AvdInfo.AvdStatus;
     27 import com.android.sdklib.internal.repository.ITask;
     28 import com.android.sdklib.internal.repository.ITaskMonitor;
     29 import com.android.sdkuilib.internal.repository.SettingsController;
     30 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     31 import com.android.sdkuilib.internal.tasks.ProgressTask;
     32 import com.android.sdkuilib.repository.UpdaterWindow;
     33 
     34 import org.eclipse.jface.dialogs.MessageDialog;
     35 import org.eclipse.jface.window.Window;
     36 import org.eclipse.swt.SWT;
     37 import org.eclipse.swt.events.ControlAdapter;
     38 import org.eclipse.swt.events.ControlEvent;
     39 import org.eclipse.swt.events.DisposeEvent;
     40 import org.eclipse.swt.events.DisposeListener;
     41 import org.eclipse.swt.events.SelectionAdapter;
     42 import org.eclipse.swt.events.SelectionEvent;
     43 import org.eclipse.swt.events.SelectionListener;
     44 import org.eclipse.swt.graphics.Image;
     45 import org.eclipse.swt.graphics.Rectangle;
     46 import org.eclipse.swt.layout.GridData;
     47 import org.eclipse.swt.layout.GridLayout;
     48 import org.eclipse.swt.widgets.Button;
     49 import org.eclipse.swt.widgets.Composite;
     50 import org.eclipse.swt.widgets.Display;
     51 import org.eclipse.swt.widgets.Label;
     52 import org.eclipse.swt.widgets.Shell;
     53 import org.eclipse.swt.widgets.Table;
     54 import org.eclipse.swt.widgets.TableColumn;
     55 import org.eclipse.swt.widgets.TableItem;
     56 
     57 import java.io.BufferedReader;
     58 import java.io.File;
     59 import java.io.IOException;
     60 import java.io.InputStreamReader;
     61 import java.util.ArrayList;
     62 import java.util.Arrays;
     63 import java.util.Comparator;
     64 import java.util.Formatter;
     65 import java.util.Locale;
     66 
     67 
     68 /**
     69  * The AVD selector is a table that is added to the given parent composite.
     70  * <p/>
     71  * After using one of the constructors, call {@link #setSelection(AvdInfo)},
     72  * {@link #setSelectionListener(SelectionListener)} and finally use
     73  * {@link #getSelected()} to retrieve the selection.
     74  */
     75 public final class AvdSelector {
     76     private static int NUM_COL = 2;
     77 
     78     private final DisplayMode mDisplayMode;
     79 
     80     private AvdManager mAvdManager;
     81     private final String mOsSdkPath;
     82 
     83     private Table mTable;
     84     private Button mDeleteButton;
     85     private Button mDetailsButton;
     86     private Button mNewButton;
     87     private Button mRefreshButton;
     88     private Button mManagerButton;
     89     private Button mRepairButton;
     90     private Button mStartButton;
     91 
     92     private SelectionListener mSelectionListener;
     93     private IAvdFilter mTargetFilter;
     94 
     95     /** Defaults to true. Changed by the {@link #setEnabled(boolean)} method to represent the
     96      * "global" enabled state on this composite. */
     97     private boolean mIsEnabled = true;
     98 
     99     private ImageFactory mImageFactory;
    100     private Image mOkImage;
    101     private Image mBrokenImage;
    102     private Image mInvalidImage;
    103 
    104     private SettingsController mController;
    105 
    106     private final ISdkLog mSdkLog;
    107 
    108 
    109     /**
    110      * The display mode of the AVD Selector.
    111      */
    112     public static enum DisplayMode {
    113         /**
    114          * Manager mode. Invalid AVDs are displayed. Buttons to create/delete AVDs
    115          */
    116         MANAGER,
    117 
    118         /**
    119          * Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
    120          * there is a button to open the AVD Manager.
    121          * In the "check" selection mode, checkboxes are displayed on each line
    122          * and {@link AvdSelector#getSelected()} returns the line that is checked
    123          * even if it is not the currently selected line. Only one line can
    124          * be checked at once.
    125          */
    126         SIMPLE_CHECK,
    127 
    128         /**
    129          * Non manager mode. Only valid AVDs are displayed. Cannot create/delete AVDs, but
    130          * there is a button to open the AVD Manager.
    131          * In the "select" selection mode, there are no checkboxes and
    132          * {@link AvdSelector#getSelected()} returns the line currently selected.
    133          * Only one line can be selected at once.
    134          */
    135         SIMPLE_SELECTION,
    136     }
    137 
    138     /**
    139      * A filter to control the whether or not an AVD should be displayed by the AVD Selector.
    140      */
    141     public interface IAvdFilter {
    142         /**
    143          * Called before {@link #accept(AvdInfo)} is called for any AVD.
    144          */
    145         void prepare();
    146 
    147         /**
    148          * Called to decided whether an AVD should be displayed.
    149          * @param avd the AVD to test.
    150          * @return true if the AVD should be displayed.
    151          */
    152         boolean accept(AvdInfo avd);
    153 
    154         /**
    155          * Called after {@link #accept(AvdInfo)} has been called on all the AVDs.
    156          */
    157         void cleanup();
    158     }
    159 
    160     /**
    161      * Internal implementation of {@link IAvdFilter} to filter out the AVDs that are not
    162      * running an image compatible with a specific target.
    163      */
    164     private final static class TargetBasedFilter implements IAvdFilter {
    165         private final IAndroidTarget mTarget;
    166 
    167         TargetBasedFilter(IAndroidTarget target) {
    168             mTarget = target;
    169         }
    170 
    171         public void prepare() {
    172             // nothing to prepare
    173         }
    174 
    175         public boolean accept(AvdInfo avd) {
    176             if (avd != null) {
    177                 return mTarget.canRunOn(avd.getTarget());
    178             }
    179 
    180             return false;
    181         }
    182 
    183         public void cleanup() {
    184             // nothing to clean up
    185         }
    186     }
    187 
    188     /**
    189      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
    190      * by a {@link IAndroidTarget}.
    191      * <p/>Only the {@link AvdInfo} able to run application developed for the given
    192      * {@link IAndroidTarget} will be displayed.
    193      *
    194      * @param parent The parent composite where the selector will be added.
    195      * @param osSdkPath The SDK root path. When not null, enables the start button to start
    196      *                  an emulator on a given AVD.
    197      * @param manager the AVD manager.
    198      * @param filter When non-null, will allow filtering the AVDs to display.
    199      * @param displayMode The display mode ({@link DisplayMode}).
    200      * @param sdkLog The logger. Cannot be null.
    201      */
    202     public AvdSelector(Composite parent,
    203             String osSdkPath,
    204             AvdManager manager,
    205             IAvdFilter filter,
    206             DisplayMode displayMode,
    207             ISdkLog sdkLog) {
    208         mOsSdkPath = osSdkPath;
    209         mAvdManager = manager;
    210         mTargetFilter = filter;
    211         mDisplayMode = displayMode;
    212         mSdkLog = sdkLog;
    213 
    214         // get some bitmaps.
    215         mImageFactory = new ImageFactory(parent.getDisplay());
    216         mOkImage = mImageFactory.getImageByName("accept_icon16.png");
    217         mBrokenImage = mImageFactory.getImageByName("broken_16.png");
    218         mInvalidImage = mImageFactory.getImageByName("reject_icon16.png");
    219 
    220         // Layout has 2 columns
    221         Composite group = new Composite(parent, SWT.NONE);
    222         GridLayout gl;
    223         group.setLayout(gl = new GridLayout(NUM_COL, false /*makeColumnsEqualWidth*/));
    224         gl.marginHeight = gl.marginWidth = 0;
    225         group.setLayoutData(new GridData(GridData.FILL_BOTH));
    226         group.setFont(parent.getFont());
    227         group.addDisposeListener(new DisposeListener() {
    228             public void widgetDisposed(DisposeEvent arg0) {
    229                 mImageFactory.dispose();
    230             }
    231         });
    232 
    233         int style = SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER;
    234         if (displayMode == DisplayMode.SIMPLE_CHECK) {
    235             style |= SWT.CHECK;
    236         }
    237         mTable = new Table(group, style);
    238         mTable.setHeaderVisible(true);
    239         mTable.setLinesVisible(false);
    240         setTableHeightHint(0);
    241 
    242         Composite buttons = new Composite(group, SWT.NONE);
    243         buttons.setLayout(gl = new GridLayout(1, false /*makeColumnsEqualWidth*/));
    244         gl.marginHeight = gl.marginWidth = 0;
    245         buttons.setLayoutData(new GridData(GridData.FILL_VERTICAL));
    246         buttons.setFont(group.getFont());
    247 
    248         if (displayMode == DisplayMode.MANAGER) {
    249             mNewButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    250             mNewButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    251             mNewButton.setText("New...");
    252             mNewButton.setToolTipText("Creates a new AVD.");
    253             mNewButton.addSelectionListener(new SelectionAdapter() {
    254                 @Override
    255                 public void widgetSelected(SelectionEvent arg0) {
    256                     onNew();
    257                 }
    258             });
    259 
    260             mDeleteButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    261             mDeleteButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    262             mDeleteButton.setText("Delete...");
    263             mDeleteButton.setToolTipText("Deletes the selected AVD.");
    264             mDeleteButton.addSelectionListener(new SelectionAdapter() {
    265                 @Override
    266                 public void widgetSelected(SelectionEvent arg0) {
    267                     onDelete();
    268                 }
    269             });
    270 
    271             mRepairButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    272             mRepairButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    273             mRepairButton.setText("Repair...");
    274             mRepairButton.setToolTipText("Repairs the selected AVD.");
    275             mRepairButton.addSelectionListener(new SelectionAdapter() {
    276                 @Override
    277                 public void widgetSelected(SelectionEvent arg0) {
    278                     onRepair();
    279                 }
    280             });
    281 
    282             Label l = new Label(buttons, SWT.SEPARATOR | SWT.HORIZONTAL);
    283             l.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    284         }
    285 
    286         mDetailsButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    287         mDetailsButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    288         mDetailsButton.setText("Details...");
    289         mDetailsButton.setToolTipText("Diplays details of the selected AVD.");
    290         mDetailsButton.addSelectionListener(new SelectionAdapter() {
    291             @Override
    292             public void widgetSelected(SelectionEvent arg0) {
    293                 onDetails();
    294             }
    295         });
    296 
    297         mStartButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    298         mStartButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    299         mStartButton.setText("Start...");
    300         mStartButton.setToolTipText("Starts the selected AVD.");
    301         mStartButton.addSelectionListener(new SelectionAdapter() {
    302             @Override
    303             public void widgetSelected(SelectionEvent arg0) {
    304                 onStart();
    305             }
    306         });
    307 
    308         Composite padding = new Composite(buttons, SWT.NONE);
    309         padding.setLayoutData(new GridData(GridData.FILL_VERTICAL));
    310 
    311         mRefreshButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    312         mRefreshButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    313         mRefreshButton.setText("Refresh");
    314         mRefreshButton.setToolTipText("Reloads the list of AVD.\nUse this if you create AVDs from the command line.");
    315         mRefreshButton.addSelectionListener(new SelectionAdapter() {
    316             @Override
    317             public void widgetSelected(SelectionEvent arg0) {
    318                 refresh(true);
    319             }
    320         });
    321 
    322         if (displayMode != DisplayMode.MANAGER) {
    323             mManagerButton = new Button(buttons, SWT.PUSH | SWT.FLAT);
    324             mManagerButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    325             mManagerButton.setText("Manager...");
    326             mManagerButton.setToolTipText("Launches the AVD manager.");
    327             mManagerButton.addSelectionListener(new SelectionAdapter() {
    328                 @Override
    329                 public void widgetSelected(SelectionEvent e) {
    330                     onManager();
    331                 }
    332             });
    333         } else {
    334             Composite legend = new Composite(group, SWT.NONE);
    335             legend.setLayout(gl = new GridLayout(4, false /*makeColumnsEqualWidth*/));
    336             gl.marginHeight = gl.marginWidth = 0;
    337             legend.setLayoutData(new GridData(GridData.FILL, GridData.BEGINNING, true, false,
    338                     NUM_COL, 1));
    339             legend.setFont(group.getFont());
    340 
    341             new Label(legend, SWT.NONE).setImage(mOkImage);
    342             new Label(legend, SWT.NONE).setText("A valid Android Virtual Device.");
    343             new Label(legend, SWT.NONE).setImage(mBrokenImage);
    344             new Label(legend, SWT.NONE).setText(
    345                     "A repairable Android Virtual Device.");
    346             new Label(legend, SWT.NONE).setImage(mInvalidImage);
    347             Label l = new Label(legend, SWT.NONE);
    348             l.setText("An Android Virtual Device that failed to load. Click 'Details' to see the error.");
    349             GridData gd;
    350             l.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
    351             gd.horizontalSpan = 3;
    352         }
    353 
    354         // create the table columns
    355         final TableColumn column0 = new TableColumn(mTable, SWT.NONE);
    356         column0.setText("AVD Name");
    357         final TableColumn column1 = new TableColumn(mTable, SWT.NONE);
    358         column1.setText("Target Name");
    359         final TableColumn column2 = new TableColumn(mTable, SWT.NONE);
    360         column2.setText("Platform");
    361         final TableColumn column3 = new TableColumn(mTable, SWT.NONE);
    362         column3.setText("API Level");
    363 
    364         adjustColumnsWidth(mTable, column0, column1, column2, column3);
    365         setupSelectionListener(mTable);
    366         fillTable(mTable);
    367         setEnabled(true);
    368     }
    369 
    370     /**
    371      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}.
    372      *
    373      * @param parent The parent composite where the selector will be added.
    374      * @param manager the AVD manager.
    375      * @param displayMode The display mode ({@link DisplayMode}).
    376      * @param sdkLog The logger. Cannot be null.
    377      */
    378     public AvdSelector(Composite parent,
    379             String osSdkPath,
    380             AvdManager manager,
    381             DisplayMode displayMode,
    382             ISdkLog sdkLog) {
    383         this(parent, osSdkPath, manager, (IAvdFilter)null /* filter */, displayMode, sdkLog);
    384     }
    385 
    386     /**
    387      * Creates a new SDK Target Selector, and fills it with a list of {@link AvdInfo}, filtered
    388      * by an {@link IAndroidTarget}.
    389      * <p/>Only the {@link AvdInfo} able to run applications developed for the given
    390      * {@link IAndroidTarget} will be displayed.
    391      *
    392      * @param parent The parent composite where the selector will be added.
    393      * @param manager the AVD manager.
    394      * @param filter Only shows the AVDs matching this target (must not be null).
    395      * @param displayMode The display mode ({@link DisplayMode}).
    396      * @param sdkLog The logger. Cannot be null.
    397      */
    398     public AvdSelector(Composite parent,
    399             String osSdkPath,
    400             AvdManager manager,
    401             IAndroidTarget filter,
    402             DisplayMode displayMode,
    403             ISdkLog sdkLog) {
    404         this(parent, osSdkPath, manager, new TargetBasedFilter(filter), displayMode, sdkLog);
    405     }
    406 
    407     /**
    408      * Sets an optional SettingsController.
    409      * @param controller the controller.
    410      */
    411     public void setSettingsController(SettingsController controller) {
    412         mController = controller;
    413     }
    414     /**
    415      * Sets the table grid layout data.
    416      *
    417      * @param heightHint If > 0, the height hint is set to the requested value.
    418      */
    419     public void setTableHeightHint(int heightHint) {
    420         GridData data = new GridData();
    421         if (heightHint > 0) {
    422             data.heightHint = heightHint;
    423         }
    424         data.grabExcessVerticalSpace = true;
    425         data.grabExcessHorizontalSpace = true;
    426         data.horizontalAlignment = GridData.FILL;
    427         data.verticalAlignment = GridData.FILL;
    428         mTable.setLayoutData(data);
    429     }
    430 
    431     /**
    432      * Refresh the display of Android Virtual Devices.
    433      * Tries to keep the selection.
    434      * <p/>
    435      * This must be called from the UI thread.
    436      *
    437      * @param reload if true, the AVD manager will reload the AVD from the disk.
    438      * @return false if the reloading failed. This is always true if <var>reload</var> is
    439      * <code>false</code>.
    440      */
    441     public boolean refresh(boolean reload) {
    442         if (reload) {
    443             try {
    444                 mAvdManager.reloadAvds(NullSdkLog.getLogger());
    445             } catch (AndroidLocationException e) {
    446                 return false;
    447             }
    448         }
    449 
    450         AvdInfo selected = getSelected();
    451 
    452         fillTable(mTable);
    453 
    454         setSelection(selected);
    455 
    456         return true;
    457     }
    458 
    459     /**
    460      * Sets a new AVD manager
    461      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
    462      * @param manager the AVD manager.
    463      */
    464     public void setManager(AvdManager manager) {
    465         mAvdManager = manager;
    466     }
    467 
    468     /**
    469      * Sets a new AVD filter.
    470      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
    471      * @param filter An IAvdFilter. If non-null, this will filter out the AVD to not display.
    472      */
    473     public void setFilter(IAvdFilter filter) {
    474         mTargetFilter = filter;
    475     }
    476 
    477     /**
    478      * Sets a new Android Target-based AVD filter.
    479      * This does not refresh the display. Call {@link #refresh(boolean)} to do so.
    480      * @param target An IAndroidTarget. If non-null, only AVD whose target are compatible with the
    481      * filter target will displayed an available for selection.
    482      */
    483     public void setFilter(IAndroidTarget target) {
    484         if (target != null) {
    485             mTargetFilter = new TargetBasedFilter(target);
    486         } else {
    487             mTargetFilter = null;
    488         }
    489     }
    490 
    491     /**
    492      * Sets a selection listener. Set it to null to remove it.
    493      * The listener will be called <em>after</em> this table processed its selection
    494      * events so that the caller can see the updated state.
    495      * <p/>
    496      * The event's item contains a {@link TableItem}.
    497      * The {@link TableItem#getData()} contains an {@link IAndroidTarget}.
    498      * <p/>
    499      * It is recommended that the caller uses the {@link #getSelected()} method instead.
    500      * <p/>
    501      * The default behavior for double click (when not in {@link DisplayMode#SIMPLE_CHECK}) is to
    502      * display the details of the selected AVD.<br>
    503      * To disable it (when you provide your own double click action), set
    504      * {@link SelectionEvent#doit} to false in
    505      * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)}
    506      *
    507      * @param selectionListener The new listener or null to remove it.
    508      */
    509     public void setSelectionListener(SelectionListener selectionListener) {
    510         mSelectionListener = selectionListener;
    511     }
    512 
    513     /**
    514      * Sets the current target selection.
    515      * <p/>
    516      * If the selection is actually changed, this will invoke the selection listener
    517      * (if any) with a null event.
    518      *
    519      * @param target the target to be selected. Use null to deselect everything.
    520      * @return true if the target could be selected, false otherwise.
    521      */
    522     public boolean setSelection(AvdInfo target) {
    523         boolean found = false;
    524         boolean modified = false;
    525 
    526         int selIndex = mTable.getSelectionIndex();
    527         int index = 0;
    528         for (TableItem i : mTable.getItems()) {
    529             if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
    530                 if ((AvdInfo) i.getData() == target) {
    531                     found = true;
    532                     if (!i.getChecked()) {
    533                         modified = true;
    534                         i.setChecked(true);
    535                     }
    536                 } else if (i.getChecked()) {
    537                     modified = true;
    538                     i.setChecked(false);
    539                 }
    540             } else {
    541                 if ((AvdInfo) i.getData() == target) {
    542                     found = true;
    543                     if (index != selIndex) {
    544                         mTable.setSelection(index);
    545                         modified = true;
    546                     }
    547                     break;
    548                 }
    549 
    550                 index++;
    551             }
    552         }
    553 
    554         if (modified && mSelectionListener != null) {
    555             mSelectionListener.widgetSelected(null);
    556         }
    557 
    558         enableActionButtons();
    559 
    560         return found;
    561     }
    562 
    563     /**
    564      * Returns the currently selected item. In {@link DisplayMode#SIMPLE_CHECK} mode this will
    565      * return the {@link AvdInfo} that is checked instead of the list selection.
    566      *
    567      * @return The currently selected item or null.
    568      */
    569     public AvdInfo getSelected() {
    570         if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
    571             for (TableItem i : mTable.getItems()) {
    572                 if (i.getChecked()) {
    573                     return (AvdInfo) i.getData();
    574                 }
    575             }
    576         } else {
    577             int selIndex = mTable.getSelectionIndex();
    578             if (selIndex >= 0) {
    579                 return (AvdInfo) mTable.getItem(selIndex).getData();
    580             }
    581         }
    582 
    583         return null;
    584     }
    585 
    586     /**
    587      * Enables the receiver if the argument is true, and disables it otherwise.
    588      * A disabled control is typically not selectable from the user interface
    589      * and draws with an inactive or "grayed" look.
    590      *
    591      * @param enabled the new enabled state.
    592      */
    593     public void setEnabled(boolean enabled) {
    594         // We can only enable widgets if the AVD Manager is defined.
    595         mIsEnabled = enabled && mAvdManager != null;
    596 
    597         mTable.setEnabled(mIsEnabled);
    598         mRefreshButton.setEnabled(mIsEnabled);
    599 
    600         if (mNewButton != null) {
    601             mNewButton.setEnabled(mIsEnabled);
    602         }
    603         if (mManagerButton != null) {
    604             mManagerButton.setEnabled(mIsEnabled);
    605         }
    606 
    607         enableActionButtons();
    608     }
    609 
    610     public boolean isEnabled() {
    611         return mIsEnabled;
    612     }
    613 
    614     /**
    615      * Adds a listener to adjust the columns width when the parent is resized.
    616      * <p/>
    617      * If we need something more fancy, we might want to use this:
    618      * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
    619      */
    620     private void adjustColumnsWidth(final Table table,
    621             final TableColumn column0,
    622             final TableColumn column1,
    623             final TableColumn column2,
    624             final TableColumn column3) {
    625         // Add a listener to resize the column to the full width of the table
    626         table.addControlListener(new ControlAdapter() {
    627             @Override
    628             public void controlResized(ControlEvent e) {
    629                 Rectangle r = table.getClientArea();
    630                 column0.setWidth(r.width * 25 / 100); // 25%
    631                 column1.setWidth(r.width * 45 / 100); // 45%
    632                 column2.setWidth(r.width * 15 / 100); // 15%
    633                 column3.setWidth(r.width * 15 / 100); // 15%
    634             }
    635         });
    636     }
    637 
    638     /**
    639      * Creates a selection listener that will check or uncheck the whole line when
    640      * double-clicked (aka "the default selection").
    641      */
    642     private void setupSelectionListener(final Table table) {
    643         // Add a selection listener that will check/uncheck items when they are double-clicked
    644         table.addSelectionListener(new SelectionListener() {
    645 
    646             /**
    647              * Handles single-click selection on the table.
    648              * {@inheritDoc}
    649              */
    650             public void widgetSelected(SelectionEvent e) {
    651                 if (e.item instanceof TableItem) {
    652                     TableItem i = (TableItem) e.item;
    653                     enforceSingleSelection(i);
    654                 }
    655 
    656                 if (mSelectionListener != null) {
    657                     mSelectionListener.widgetSelected(e);
    658                 }
    659 
    660                 enableActionButtons();
    661             }
    662 
    663             /**
    664              * Handles double-click selection on the table.
    665              * Note that the single-click handler will probably already have been called.
    666              *
    667              * On double-click, <em>always</em> check the table item.
    668              *
    669              * {@inheritDoc}
    670              */
    671             public void widgetDefaultSelected(SelectionEvent e) {
    672                 if (e.item instanceof TableItem) {
    673                     TableItem i = (TableItem) e.item;
    674                     if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
    675                         i.setChecked(true);
    676                     }
    677                     enforceSingleSelection(i);
    678 
    679                 }
    680 
    681                 // whether or not we display details. default: true when not in SIMPLE_CHECK mode.
    682                 boolean showDetails = mDisplayMode != DisplayMode.SIMPLE_CHECK;
    683 
    684                 if (mSelectionListener != null) {
    685                     mSelectionListener.widgetDefaultSelected(e);
    686                     showDetails &= e.doit; // enforce false in SIMPLE_CHECK
    687                 }
    688 
    689                 if (showDetails) {
    690                     onDetails();
    691                 }
    692 
    693                 enableActionButtons();
    694             }
    695 
    696             /**
    697              * To ensure single selection, uncheck all other items when this one is selected.
    698              * This makes the chekboxes act as radio buttons.
    699              */
    700             private void enforceSingleSelection(TableItem item) {
    701                 if (mDisplayMode == DisplayMode.SIMPLE_CHECK) {
    702                     if (item.getChecked()) {
    703                         Table parentTable = item.getParent();
    704                         for (TableItem i2 : parentTable.getItems()) {
    705                             if (i2 != item && i2.getChecked()) {
    706                                 i2.setChecked(false);
    707                             }
    708                         }
    709                     }
    710                 } else {
    711                     // pass
    712                 }
    713             }
    714         });
    715     }
    716 
    717     /**
    718      * Fills the table with all AVD.
    719      * The table columns are:
    720      * <ul>
    721      * <li>column 0: sdk name
    722      * <li>column 1: sdk vendor
    723      * <li>column 2: sdk api name
    724      * <li>column 3: sdk version
    725      * </ul>
    726      */
    727     private void fillTable(final Table table) {
    728         table.removeAll();
    729 
    730         // get the AVDs
    731         AvdInfo avds[] = null;
    732         if (mAvdManager != null) {
    733             if (mDisplayMode == DisplayMode.MANAGER) {
    734                 avds = mAvdManager.getAllAvds();
    735             } else {
    736                 avds = mAvdManager.getValidAvds();
    737             }
    738         }
    739 
    740         if (avds != null && avds.length > 0) {
    741             Arrays.sort(avds, new Comparator<AvdInfo>() {
    742                 public int compare(AvdInfo o1, AvdInfo o2) {
    743                     return o1.compareTo(o2);
    744                 }
    745             });
    746 
    747             table.setEnabled(true);
    748 
    749             if (mTargetFilter != null) {
    750                 mTargetFilter.prepare();
    751             }
    752 
    753             for (AvdInfo avd : avds) {
    754                 if (mTargetFilter == null || mTargetFilter.accept(avd)) {
    755                     TableItem item = new TableItem(table, SWT.NONE);
    756                     item.setData(avd);
    757                     item.setText(0, avd.getName());
    758                     if (mDisplayMode == DisplayMode.MANAGER) {
    759                         AvdStatus status = avd.getStatus();
    760                         item.setImage(0, status == AvdStatus.OK ? mOkImage :
    761                             isAvdRepairable(status) ? mBrokenImage : mInvalidImage);
    762                     }
    763                     IAndroidTarget target = avd.getTarget();
    764                     if (target != null) {
    765                         item.setText(1, target.getFullName());
    766                         item.setText(2, target.getVersionName());
    767                         item.setText(3, target.getVersion().getApiString());
    768                     } else {
    769                         item.setText(1, "?");
    770                         item.setText(2, "?");
    771                         item.setText(3, "?");
    772                     }
    773                 }
    774             }
    775 
    776             if (mTargetFilter != null) {
    777                 mTargetFilter.cleanup();
    778             }
    779         }
    780 
    781         if (table.getItemCount() == 0) {
    782             table.setEnabled(false);
    783             TableItem item = new TableItem(table, SWT.NONE);
    784             item.setData(null);
    785             item.setText(0, "--");
    786             item.setText(1, "No AVD available");
    787             item.setText(2, "--");
    788             item.setText(3, "--");
    789         }
    790     }
    791 
    792     /**
    793      * Returns the currently selected AVD in the table.
    794      * <p/>
    795      * Unlike {@link #getSelected()} this will always return the item being selected
    796      * in the list, ignoring the check boxes state in {@link DisplayMode#SIMPLE_CHECK} mode.
    797      */
    798     private AvdInfo getTableSelection() {
    799         int selIndex = mTable.getSelectionIndex();
    800         if (selIndex >= 0) {
    801             return (AvdInfo) mTable.getItem(selIndex).getData();
    802         }
    803 
    804         return null;
    805     }
    806 
    807     /**
    808      * Updates the enable state of the Details, Start, Delete and Update buttons.
    809      */
    810     private void enableActionButtons() {
    811         if (mIsEnabled == false) {
    812             mDetailsButton.setEnabled(false);
    813             mStartButton.setEnabled(false);
    814 
    815             if (mDeleteButton != null) {
    816                 mDeleteButton.setEnabled(false);
    817             }
    818             if (mRepairButton != null) {
    819                 mRepairButton.setEnabled(false);
    820             }
    821         } else {
    822             AvdInfo selection = getTableSelection();
    823             boolean hasSelection = selection != null;
    824 
    825             mDetailsButton.setEnabled(hasSelection);
    826             mStartButton.setEnabled(mOsSdkPath != null &&
    827                     hasSelection &&
    828                     selection.getStatus() == AvdStatus.OK);
    829 
    830             if (mDeleteButton != null) {
    831                 mDeleteButton.setEnabled(hasSelection);
    832             }
    833             if (mRepairButton != null) {
    834                 mRepairButton.setEnabled(hasSelection && isAvdRepairable(selection.getStatus()));
    835             }
    836         }
    837     }
    838 
    839     private void onNew() {
    840         AvdCreationDialog dlg = new AvdCreationDialog(mTable.getShell(),
    841                 mAvdManager,
    842                 mImageFactory,
    843                 mSdkLog);
    844 
    845         if (dlg.open() == Window.OK) {
    846             refresh(false /*reload*/);
    847         }
    848     }
    849 
    850     private void onDetails() {
    851         final AvdInfo avdInfo = getTableSelection();
    852 
    853         AvdDetailsDialog dlg = new AvdDetailsDialog(mTable.getShell(), avdInfo);
    854         dlg.open();
    855     }
    856 
    857     private void onDelete() {
    858         final AvdInfo avdInfo = getTableSelection();
    859 
    860         // get the current Display
    861         final Display display = mTable.getDisplay();
    862 
    863         // check if the AVD is running
    864         if (avdInfo.isRunning()) {
    865             display.asyncExec(new Runnable() {
    866                 public void run() {
    867                     Shell shell = display.getActiveShell();
    868                     MessageDialog.openError(shell,
    869                             "Delete Android Virtual Device",
    870                             String.format(
    871                                     "The Android Virtual Device '%1$s' is currently running in an emulator and cannot be deleted.",
    872                                     avdInfo.getName()));
    873                 }
    874             });
    875             return;
    876         }
    877 
    878         // Confirm you want to delete this AVD
    879         final boolean[] result = new boolean[1];
    880         display.syncExec(new Runnable() {
    881             public void run() {
    882                 Shell shell = display.getActiveShell();
    883                 result[0] = MessageDialog.openQuestion(shell,
    884                         "Delete Android Virtual Device",
    885                         String.format(
    886                                 "Please confirm that you want to delete the Android Virtual Device named '%s'. This operation cannot be reverted.",
    887                                 avdInfo.getName()));
    888             }
    889         });
    890 
    891         if (result[0] == false) {
    892             return;
    893         }
    894 
    895         // log for this action.
    896         ISdkLog log = mSdkLog;
    897         if (log == null || log instanceof MessageBoxLog) {
    898             // If the current logger is a message box, we use our own (to make sure
    899             // to display errors right away and customize the title).
    900             log = new MessageBoxLog(
    901                 String.format("Result of deleting AVD '%s':", avdInfo.getName()),
    902                 display,
    903                 false /*logErrorsOnly*/);
    904         }
    905 
    906         // delete the AVD
    907         boolean success = mAvdManager.deleteAvd(avdInfo, log);
    908 
    909         // display the result
    910         if (log instanceof MessageBoxLog) {
    911             ((MessageBoxLog) log).displayResult(success);
    912         }
    913 
    914         if (success) {
    915             refresh(false /*reload*/);
    916         }
    917     }
    918 
    919     /**
    920      * Repairs the selected AVD.
    921      * <p/>
    922      * For now this only supports fixing the wrong value in image.sysdir.*
    923      */
    924     private void onRepair() {
    925         final AvdInfo avdInfo = getTableSelection();
    926 
    927         // get the current Display
    928         final Display display = mTable.getDisplay();
    929 
    930         // log for this action.
    931         ISdkLog log = mSdkLog;
    932         if (log == null || log instanceof MessageBoxLog) {
    933             // If the current logger is a message box, we use our own (to make sure
    934             // to display errors right away and customize the title).
    935             log = new MessageBoxLog(
    936                 String.format("Result of updating AVD '%s':", avdInfo.getName()),
    937                 display,
    938                 false /*logErrorsOnly*/);
    939         }
    940 
    941         // delete the AVD
    942         try {
    943             mAvdManager.updateAvd(avdInfo, log);
    944 
    945             // display the result
    946             if (log instanceof MessageBoxLog) {
    947                 ((MessageBoxLog) log).displayResult(true /* success */);
    948             }
    949             refresh(false /*reload*/);
    950 
    951         } catch (IOException e) {
    952             log.error(e, null);
    953             if (log instanceof MessageBoxLog) {
    954                 ((MessageBoxLog) log).displayResult(false /* success */);
    955             }
    956         }
    957     }
    958 
    959     private void onManager() {
    960 
    961         // get the current Display
    962         Display display = mTable.getDisplay();
    963 
    964         // log for this action.
    965         ISdkLog log = mSdkLog;
    966         if (log == null || log instanceof MessageBoxLog) {
    967             // If the current logger is a message box, we use our own (to make sure
    968             // to display errors right away and customize the title).
    969             log = new MessageBoxLog("Result of SDK Manager", display, true /*logErrorsOnly*/);
    970         }
    971 
    972         UpdaterWindow window = new UpdaterWindow(
    973                 mTable.getShell(),
    974                 log,
    975                 mAvdManager.getSdkManager().getLocation(),
    976                 false /*userCanChangeSdkRoot*/);
    977         window.open();
    978         refresh(true /*reload*/); // UpdaterWindow uses its own AVD manager so this one must reload.
    979 
    980         if (log instanceof MessageBoxLog) {
    981             ((MessageBoxLog) log).displayResult(true);
    982         }
    983     }
    984 
    985     private void onStart() {
    986         AvdInfo avdInfo = getTableSelection();
    987 
    988         if (avdInfo == null || mOsSdkPath == null) {
    989             return;
    990         }
    991 
    992         AvdStartDialog dialog = new AvdStartDialog(mTable.getShell(), avdInfo, mOsSdkPath,
    993                 mController);
    994         if (dialog.open() == Window.OK) {
    995             String path = mOsSdkPath +
    996                 File.separator +
    997                 SdkConstants.OS_SDK_TOOLS_FOLDER +
    998                 SdkConstants.FN_EMULATOR;
    999 
   1000             final String avdName = avdInfo.getName();
   1001 
   1002             // build the command line based on the available parameters.
   1003             ArrayList<String> list = new ArrayList<String>();
   1004             list.add(path);
   1005             list.add("-avd");                             //$NON-NLS-1$
   1006             list.add(avdName);
   1007             if (dialog.getWipeData()) {
   1008                 list.add("-wipe-data");                   //$NON-NLS-1$
   1009             }
   1010             float scale = dialog.getScale();
   1011             if (scale != 0.f) {
   1012                 // do the rounding ourselves. This is because %.1f will write .4899 as .4
   1013                 scale = Math.round(scale * 100);
   1014                 scale /=  100.f;
   1015                 list.add("-scale");                       //$NON-NLS-1$
   1016                 // because the emulator expects English decimal values, don't use String.format
   1017                 // but a Formatter.
   1018                 Formatter formatter = new Formatter(Locale.US);
   1019                 formatter.format("%.2f", scale);   //$NON-NLS-1$
   1020                 list.add(formatter.toString());
   1021             }
   1022 
   1023             // convert the list into an array for the call to exec.
   1024             final String[] command = list.toArray(new String[list.size()]);
   1025 
   1026             // launch the emulator
   1027             new ProgressTask(mTable.getShell(),
   1028                     "Starting Android Emulator",
   1029                     new ITask() {
   1030                         public void run(ITaskMonitor monitor) {
   1031                             try {
   1032                                 monitor.setDescription("Starting emulator for AVD '%1$s'",
   1033                                         avdName);
   1034                                 int n = 10;
   1035                                 monitor.setProgressMax(n);
   1036                                 Process process = Runtime.getRuntime().exec(command);
   1037                                 grabEmulatorOutput(process, monitor);
   1038 
   1039                                 // This small wait prevents the dialog from closing too fast:
   1040                                 // When it works, the emulator returns immediately, even if
   1041                                 // no UI is shown yet. And when it fails (because the AVD is
   1042                                 // locked/running)
   1043                                 // if we don't have a wait we don't capture the error for
   1044                                 // some reason.
   1045                                 for (int i = 0; i < n; i++) {
   1046                                     try {
   1047                                         Thread.sleep(100);
   1048                                         monitor.incProgress(1);
   1049                                     } catch (InterruptedException e) {
   1050                                         // ignore
   1051                                     }
   1052                                 }
   1053                             } catch (IOException e) {
   1054                                 monitor.setResult("Failed to start emulator: %1$s",
   1055                                         e.getMessage());
   1056                             }
   1057                         }
   1058             });
   1059         }
   1060     }
   1061 
   1062     /**
   1063      * Get the stderr/stdout outputs of a process and return when the process is done.
   1064      * Both <b>must</b> be read or the process will block on windows.
   1065      * @param process The process to get the output from.
   1066      * @param monitor An {@link ITaskMonitor} to capture errors. Cannot be null.
   1067      */
   1068     private void grabEmulatorOutput(final Process process, final ITaskMonitor monitor) {
   1069         // read the lines as they come. if null is returned, it's because the process finished
   1070         new Thread("emu-stderr") { //$NON-NLS-1$
   1071             @Override
   1072             public void run() {
   1073                 // create a buffer to read the stderr output
   1074                 InputStreamReader is = new InputStreamReader(process.getErrorStream());
   1075                 BufferedReader errReader = new BufferedReader(is);
   1076 
   1077                 try {
   1078                     while (true) {
   1079                         String line = errReader.readLine();
   1080                         if (line != null) {
   1081                             monitor.setResult("%1$s", line);    //$NON-NLS-1$
   1082                         } else {
   1083                             break;
   1084                         }
   1085                     }
   1086                 } catch (IOException e) {
   1087                     // do nothing.
   1088                 }
   1089             }
   1090         }.start();
   1091 
   1092         new Thread("emu-stdout") { //$NON-NLS-1$
   1093             @Override
   1094             public void run() {
   1095                 InputStreamReader is = new InputStreamReader(process.getInputStream());
   1096                 BufferedReader outReader = new BufferedReader(is);
   1097 
   1098                 try {
   1099                     while (true) {
   1100                         String line = outReader.readLine();
   1101                         if (line != null) {
   1102                             monitor.setResult("%1$s", line);    //$NON-NLS-1$
   1103                         } else {
   1104                             break;
   1105                         }
   1106                     }
   1107                 } catch (IOException e) {
   1108                     // do nothing.
   1109                 }
   1110             }
   1111         }.start();
   1112     }
   1113 
   1114     private boolean isAvdRepairable(AvdStatus avdStatus) {
   1115         return avdStatus == AvdStatus.ERROR_IMAGE_DIR;
   1116     }
   1117 }
   1118