Home | History | Annotate | Download | only in repository
      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.repository;
     18 
     19 import com.android.sdklib.AndroidVersion;
     20 import com.android.sdklib.SdkConstants;
     21 import com.android.sdklib.internal.repository.Archive;
     22 import com.android.sdklib.internal.repository.IPackageVersion;
     23 import com.android.sdklib.internal.repository.Package;
     24 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     25 import com.android.sdkuilib.ui.GridDialog;
     26 
     27 import org.eclipse.jface.dialogs.IDialogConstants;
     28 import org.eclipse.jface.viewers.ISelection;
     29 import org.eclipse.jface.viewers.IStructuredContentProvider;
     30 import org.eclipse.jface.viewers.IStructuredSelection;
     31 import org.eclipse.jface.viewers.LabelProvider;
     32 import org.eclipse.jface.viewers.TableViewer;
     33 import org.eclipse.jface.viewers.Viewer;
     34 import org.eclipse.jface.window.Window;
     35 import org.eclipse.swt.SWT;
     36 import org.eclipse.swt.custom.SashForm;
     37 import org.eclipse.swt.custom.StyleRange;
     38 import org.eclipse.swt.custom.StyledText;
     39 import org.eclipse.swt.events.ControlAdapter;
     40 import org.eclipse.swt.events.ControlEvent;
     41 import org.eclipse.swt.events.SelectionAdapter;
     42 import org.eclipse.swt.events.SelectionEvent;
     43 import org.eclipse.swt.graphics.Image;
     44 import org.eclipse.swt.graphics.Point;
     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.Control;
     51 import org.eclipse.swt.widgets.Group;
     52 import org.eclipse.swt.widgets.Label;
     53 import org.eclipse.swt.widgets.Shell;
     54 import org.eclipse.swt.widgets.Table;
     55 import org.eclipse.swt.widgets.TableColumn;
     56 
     57 import java.util.ArrayList;
     58 import java.util.Collection;
     59 
     60 
     61 /**
     62  * Implements an {@link SdkUpdaterChooserDialog}.
     63  */
     64 final class SdkUpdaterChooserDialog extends GridDialog {
     65 
     66     /** Last dialog size for this session. */
     67     private static Point sLastSize;
     68     private boolean mLicenseAcceptAll;
     69     private boolean mInternalLicenseRadioUpdate;
     70 
     71     // UI fields
     72     private SashForm mSashForm;
     73     private Composite mPackageRootComposite;
     74     private TableViewer mTableViewPackage;
     75     private Table mTablePackage;
     76     private TableColumn mTableColum;
     77     private StyledText mPackageText;
     78     private Button mLicenseRadioAccept;
     79     private Button mLicenseRadioReject;
     80     private Button mLicenseRadioAcceptAll;
     81     private Group mPackageTextGroup;
     82     private final UpdaterData mUpdaterData;
     83     private Group mTableGroup;
     84     private Label mErrorLabel;
     85 
     86     /**
     87      * List of all archives to be installed with dependency information.
     88      * <p/>
     89      * Note: in a lot of cases, we need to find the archive info for a given archive. This
     90      * is currently done using a simple linear search, which is fine since we only have a very
     91      * limited number of archives to deal with (e.g. < 10 now). We might want to revisit
     92      * this later if it becomes an issue. Right now just do the simple thing.
     93      *<p/>
     94      * Typically we could add a map Archive=>ArchiveInfo later.
     95      */
     96     private final Collection<ArchiveInfo> mArchives;
     97 
     98 
     99 
    100     /**
    101      * Create the dialog.
    102      * @param parentShell The shell to use, typically updaterData.getWindowShell()
    103      * @param updaterData The updater data
    104      * @param archives The archives to be installed
    105      */
    106     public SdkUpdaterChooserDialog(Shell parentShell,
    107             UpdaterData updaterData,
    108             Collection<ArchiveInfo> archives) {
    109         super(parentShell, 3, false/*makeColumnsEqual*/);
    110         mUpdaterData = updaterData;
    111         mArchives = archives;
    112     }
    113 
    114     @Override
    115     protected boolean isResizable() {
    116         return true;
    117     }
    118 
    119     /**
    120      * Returns the results, i.e. the list of selected new archives to install.
    121      * This is similar to the {@link ArchiveInfo} list instance given to the constructor
    122      * except only accepted archives are present.
    123      *
    124      * An empty list is returned if cancel was choosen.
    125      */
    126     public ArrayList<ArchiveInfo> getResult() {
    127         ArrayList<ArchiveInfo> ais = new ArrayList<ArchiveInfo>();
    128 
    129         if (getReturnCode() == Window.OK) {
    130             for (ArchiveInfo ai : mArchives) {
    131                 if (ai.isAccepted()) {
    132                     ais.add(ai);
    133                 }
    134             }
    135         }
    136 
    137         return ais;
    138     }
    139 
    140     /**
    141      * Create the main content of the dialog.
    142      * See also {@link #createButtonBar(Composite)} below.
    143      */
    144     @Override
    145     public void createDialogContent(Composite parent) {
    146         // Sash form
    147         mSashForm = new SashForm(parent, SWT.NONE);
    148         mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
    149 
    150 
    151         // Left part of Sash Form
    152 
    153         mTableGroup = new Group(mSashForm, SWT.NONE);
    154         mTableGroup.setText("Packages");
    155         mTableGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
    156 
    157         mTableViewPackage = new TableViewer(mTableGroup, SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE);
    158         mTablePackage = mTableViewPackage.getTable();
    159         mTablePackage.setHeaderVisible(false);
    160         mTablePackage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
    161 
    162         mTablePackage.addSelectionListener(new SelectionAdapter() {
    163             @Override
    164             public void widgetSelected(SelectionEvent e) {
    165                 onPackageSelected();  //$hide$
    166             }
    167             @Override
    168             public void widgetDefaultSelected(SelectionEvent e) {
    169                 onPackageDoubleClick();
    170             }
    171         });
    172 
    173         mTableColum = new TableColumn(mTablePackage, SWT.NONE);
    174         mTableColum.setWidth(100);
    175         mTableColum.setText("Packages");
    176 
    177 
    178         // Right part of Sash form
    179         mPackageRootComposite = new Composite(mSashForm, SWT.NONE);
    180         mPackageRootComposite.setLayout(new GridLayout(4, false/*makeColumnsEqual*/));
    181         mPackageRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
    182 
    183         mPackageTextGroup = new Group(mPackageRootComposite, SWT.NONE);
    184         mPackageTextGroup.setText("Package Description && License");
    185         mPackageTextGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
    186         mPackageTextGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
    187 
    188         mPackageText = new StyledText(mPackageTextGroup,
    189                         SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
    190         mPackageText.setBackground(
    191                 getParentShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
    192         mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
    193 
    194         mLicenseRadioAccept = new Button(mPackageRootComposite, SWT.RADIO);
    195         mLicenseRadioAccept.setText("Accept");
    196         mLicenseRadioAccept.addSelectionListener(new SelectionAdapter() {
    197             @Override
    198             public void widgetSelected(SelectionEvent e) {
    199                 onLicenseRadioSelected();
    200             }
    201         });
    202 
    203         mLicenseRadioReject = new Button(mPackageRootComposite, SWT.RADIO);
    204         mLicenseRadioReject.setText("Reject");
    205         mLicenseRadioReject.addSelectionListener(new SelectionAdapter() {
    206             @Override
    207             public void widgetSelected(SelectionEvent e) {
    208                 onLicenseRadioSelected();
    209             }
    210         });
    211 
    212         Label placeholder = new Label(mPackageRootComposite, SWT.NONE);
    213         placeholder.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
    214 
    215         mLicenseRadioAcceptAll = new Button(mPackageRootComposite, SWT.RADIO);
    216         mLicenseRadioAcceptAll.setText("Accept All");
    217         mLicenseRadioAcceptAll.addSelectionListener(new SelectionAdapter() {
    218             @Override
    219             public void widgetSelected(SelectionEvent e) {
    220                 onLicenseRadioSelected();
    221             }
    222         });
    223 
    224         mSashForm.setWeights(new int[] {200, 300});
    225     }
    226 
    227     /**
    228      * Creates and returns the contents of this dialog's button bar.
    229      * <p/>
    230      * This reimplements most of the code from the base class with a few exceptions:
    231      * <ul>
    232      * <li>Enforces 3 columns.
    233      * <li>Inserts a full-width error label.
    234      * <li>Inserts a help label on the left of the first button.
    235      * <li>Renames the OK button into "Install"
    236      * </ul>
    237      */
    238     @Override
    239     protected Control createButtonBar(Composite parent) {
    240         Composite composite = new Composite(parent, SWT.NONE);
    241         GridLayout layout = new GridLayout();
    242         layout.numColumns = 0; // this is incremented by createButton
    243         layout.makeColumnsEqualWidth = false;
    244         layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
    245         layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
    246         layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
    247         layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
    248         composite.setLayout(layout);
    249         GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1);
    250         composite.setLayoutData(data);
    251         composite.setFont(parent.getFont());
    252 
    253         // Error message area
    254         mErrorLabel = new Label(composite, SWT.NONE);
    255         mErrorLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1));
    256 
    257         // Label at the left of the install/cancel buttons
    258         Label label = new Label(composite, SWT.NONE);
    259         label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
    260         label.setText("[*] Something depends on this package");
    261         label.setEnabled(false);
    262         layout.numColumns++;
    263 
    264         // Add the ok/cancel to the button bar.
    265         createButtonsForButtonBar(composite);
    266 
    267         // the ok button should be an "install" button
    268         Button button = getButton(IDialogConstants.OK_ID);
    269         button.setText("Install");
    270 
    271         return composite;
    272     }
    273 
    274     // -- End of UI, Start of internal logic ----------
    275     // Hide everything down-below from SWT designer
    276     //$hide>>$
    277 
    278     @Override
    279     public void create() {
    280         super.create();
    281 
    282         // set window title
    283         getShell().setText("Choose Packages to Install");
    284 
    285         setWindowImage();
    286 
    287         // Automatically accept those with an empty license or no license
    288         for (ArchiveInfo ai : mArchives) {
    289             Archive a = ai.getNewArchive();
    290             if (a != null) {
    291                 String license = a.getParentPackage().getLicense();
    292                 ai.setAccepted(license == null || license.trim().length() == 0);
    293             }
    294         }
    295 
    296         // Fill the list with the replacement packages
    297         mTableViewPackage.setLabelProvider(new NewArchivesLabelProvider());
    298         mTableViewPackage.setContentProvider(new NewArchivesContentProvider());
    299         mTableViewPackage.setInput(mArchives);
    300 
    301         adjustColumnsWidth();
    302 
    303         // select first item
    304         mTablePackage.select(0);
    305         onPackageSelected();
    306     }
    307 
    308     /**
    309      * Creates the icon of the window shell.
    310      */
    311     private void setWindowImage() {
    312         String imageName = "android_icon_16.png"; //$NON-NLS-1$
    313         if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
    314             imageName = "android_icon_128.png"; //$NON-NLS-1$
    315         }
    316 
    317         if (mUpdaterData != null) {
    318             ImageFactory imgFactory = mUpdaterData.getImageFactory();
    319             if (imgFactory != null) {
    320                 getShell().setImage(imgFactory.getImageByName(imageName));
    321             }
    322         }
    323     }
    324 
    325     /**
    326      * Adds a listener to adjust the columns width when the parent is resized.
    327      * <p/>
    328      * If we need something more fancy, we might want to use this:
    329      * http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet77.java?view=co
    330      */
    331     private void adjustColumnsWidth() {
    332         // Add a listener to resize the column to the full width of the table
    333         ControlAdapter resizer = new ControlAdapter() {
    334             @Override
    335             public void controlResized(ControlEvent e) {
    336                 Rectangle r = mTablePackage.getClientArea();
    337                 mTableColum.setWidth(r.width);
    338             }
    339         };
    340         mTablePackage.addControlListener(resizer);
    341         resizer.controlResized(null);
    342     }
    343 
    344     /**
    345      * Captures the window size before closing this.
    346      * @see #getInitialSize()
    347      */
    348     @Override
    349     public boolean close() {
    350         sLastSize = getShell().getSize();
    351         return super.close();
    352     }
    353 
    354     /**
    355      * Tries to reuse the last window size during this session.
    356      * <p/>
    357      * Note: the alternative would be to implement {@link #getDialogBoundsSettings()}
    358      * since the default {@link #getDialogBoundsStrategy()} is to persist both location
    359      * and size.
    360      */
    361     @Override
    362     protected Point getInitialSize() {
    363         if (sLastSize != null) {
    364             return sLastSize;
    365         } else {
    366             // Arbitrary values that look good on my screen and fit on 800x600
    367             return new Point(740, 370);
    368         }
    369     }
    370 
    371     /**
    372      * Callback invoked when a package item is selected in the list.
    373      */
    374     private void onPackageSelected() {
    375         ArchiveInfo ai = getSelectedArchive();
    376         displayInformation(ai);
    377         displayMissingDependency(ai);
    378         updateLicenceRadios(ai);
    379     }
    380 
    381     /** Returns the currently selected {@link ArchiveInfo} or null. */
    382     private ArchiveInfo getSelectedArchive() {
    383         ISelection sel = mTableViewPackage.getSelection();
    384         if (sel instanceof IStructuredSelection) {
    385             Object elem = ((IStructuredSelection) sel).getFirstElement();
    386             if (elem instanceof ArchiveInfo) {
    387                 return (ArchiveInfo) elem;
    388             }
    389         }
    390         return null;
    391     }
    392 
    393     /**
    394      * Updates the package description and license text depending on the selected package.
    395      * <p/>
    396      * Note that right now there is no logic to support more than one level of dependencies
    397      * (e.g. A <- B <- C and A is disabled so C should be disabled; currently C's state depends
    398      * solely on B's state). We currently don't need this. It would be straightforward to add
    399      * if we had a need for it, though. This would require changes to {@link ArchiveInfo} and
    400      * {@link SdkUpdaterLogic}.
    401      */
    402     private void displayInformation(ArchiveInfo ai) {
    403         if (ai == null) {
    404             mPackageText.setText("Please select a package.");
    405             return;
    406         }
    407 
    408         Archive aNew = ai.getNewArchive();
    409         if (aNew == null) {
    410             // Only missing archives have a null archive, so we shouldn't be here.
    411             return;
    412         }
    413 
    414         Package pNew = aNew.getParentPackage();
    415 
    416         mPackageText.setText("");                   //$NON-NLS-1$
    417 
    418         addSectionTitle("Package Description\n");
    419         addText(pNew.getLongDescription(), "\n\n"); //$NON-NLS-1$
    420 
    421         Archive aOld = ai.getReplaced();
    422         if (aOld != null) {
    423             Package pOld = aOld.getParentPackage();
    424 
    425             int rOld = pOld.getRevision();
    426             int rNew = pNew.getRevision();
    427 
    428             boolean showRev = true;
    429 
    430             if (pNew instanceof IPackageVersion && pOld instanceof IPackageVersion) {
    431                 AndroidVersion vOld = ((IPackageVersion) pOld).getVersion();
    432                 AndroidVersion vNew = ((IPackageVersion) pNew).getVersion();
    433 
    434                 if (!vOld.equals(vNew)) {
    435                     // Versions are different, so indicate more than just the revision.
    436                     addText(String.format("This update will replace API %1$s revision %2$d with API %3$s revision %4$d.\n\n",
    437                             vOld.getApiString(), rOld,
    438                             vNew.getApiString(), rNew));
    439                     showRev = false;
    440                 }
    441             }
    442 
    443             if (showRev) {
    444                 addText(String.format("This update will replace revision %1$d with revision %2$d.\n\n",
    445                         rOld,
    446                         rNew));
    447             }
    448         }
    449 
    450         ArchiveInfo[] aDeps = ai.getDependsOn();
    451         if ((aDeps != null && aDeps.length > 0) || ai.isDependencyFor()) {
    452             addSectionTitle("Dependencies\n");
    453 
    454             if (aDeps != null && aDeps.length > 0) {
    455                 addText("Installing this package also requires installing:");
    456                 for (ArchiveInfo aDep : aDeps) {
    457                     addText(String.format("\n- %1$s",
    458                             aDep.getShortDescription()));
    459                 }
    460                 addText("\n\n");
    461             }
    462 
    463             if (ai.isDependencyFor()) {
    464                 addText("This package is a dependency for:");
    465                 for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
    466                     addText(String.format("\n- %1$s",
    467                             ai2.getShortDescription()));
    468                 }
    469                 addText("\n\n");
    470             }
    471         }
    472 
    473         addSectionTitle("Archive Description\n");
    474         addText(aNew.getLongDescription(), "\n\n");                             //$NON-NLS-1$
    475 
    476         String license = pNew.getLicense();
    477         if (license != null) {
    478             addSectionTitle("License\n");
    479             addText(license.trim(), "\n\n");                                       //$NON-NLS-1$
    480         }
    481 
    482         addSectionTitle("Site\n");
    483         addText(pNew.getParentSource().getShortDescription());
    484     }
    485 
    486     /**
    487      * Computes and displays missing dependencies.
    488      *
    489      * If there's a selected package, check the dependency for that one.
    490      * Otherwise display the first missing dependency of any other package.
    491      */
    492     private void displayMissingDependency(ArchiveInfo ai) {
    493         String error = null;
    494 
    495         try {
    496             if (ai != null) {
    497                 if (ai.isAccepted()) {
    498                     // Case where this package is accepted but blocked by another non-accepted one
    499                     ArchiveInfo[] adeps = ai.getDependsOn();
    500                     if (adeps != null) {
    501                         for (ArchiveInfo adep : adeps) {
    502                             if (!adep.isAccepted()) {
    503                                 error = String.format("This package depends on '%1$s'.",
    504                                         adep.getShortDescription());
    505                                 return;
    506                             }
    507                         }
    508                     }
    509                 } else {
    510                     // Case where this package blocks another one when not accepted
    511                     for (ArchiveInfo adep : ai.getDependenciesFor()) {
    512                         // It only matters if the blocked one is accepted
    513                         if (adep.isAccepted()) {
    514                             error = String.format("Package '%1$s' depends on this one.",
    515                                     adep.getShortDescription());
    516                             return;
    517                         }
    518                     }
    519                 }
    520             }
    521 
    522             // If there is no missing dependency on the current selection,
    523             // just find the first missing dependency of any other package.
    524             for (ArchiveInfo ai2 : mArchives) {
    525                 if (ai2 == ai) {
    526                     // We already processed that one above.
    527                     continue;
    528                 }
    529                 if (ai2.isAccepted()) {
    530                     // The user requested to install this package.
    531                     // Check if all its dependencies are met.
    532                     ArchiveInfo[] adeps = ai2.getDependsOn();
    533                     if (adeps != null) {
    534                         for (ArchiveInfo adep : adeps) {
    535                             if (!adep.isAccepted()) {
    536                                 error = String.format("Package '%1$s' depends on '%2$s'",
    537                                         ai2.getShortDescription(),
    538                                         adep.getShortDescription());
    539                                 return;
    540                             }
    541                         }
    542                     }
    543                 } else {
    544                     // The user did not request to install this package.
    545                     // Check whether this package blocks another one when not accepted.
    546                     for (ArchiveInfo adep : ai2.getDependenciesFor()) {
    547                         // It only matters if the blocked one is accepted
    548                         // or if it's a local archive that is already installed (these
    549                         // are marked as implicitly accepted, so it's the same test.)
    550                         if (adep.isAccepted()) {
    551                             error = String.format("Package '%1$s' depends on '%2$s'",
    552                                     adep.getShortDescription(),
    553                                     ai2.getShortDescription());
    554                             return;
    555                         }
    556                     }
    557                 }
    558             }
    559         } finally {
    560             mErrorLabel.setText(error == null ? "" : error);        //$NON-NLS-1$
    561         }
    562     }
    563 
    564     private void addText(String...string) {
    565         for (String s : string) {
    566             mPackageText.append(s);
    567         }
    568     }
    569 
    570     private void addSectionTitle(String string) {
    571         String s = mPackageText.getText();
    572         int start = (s == null ? 0 : s.length());
    573         mPackageText.append(string);
    574 
    575         StyleRange sr = new StyleRange();
    576         sr.start = start;
    577         sr.length = string.length();
    578         sr.fontStyle = SWT.BOLD;
    579         sr.underline = true;
    580         mPackageText.setStyleRange(sr);
    581     }
    582 
    583     private void updateLicenceRadios(ArchiveInfo ai) {
    584         if (mInternalLicenseRadioUpdate) {
    585             return;
    586         }
    587         mInternalLicenseRadioUpdate = true;
    588 
    589         boolean oneAccepted = false;
    590 
    591         if (mLicenseAcceptAll) {
    592             mLicenseRadioAcceptAll.setSelection(true);
    593             mLicenseRadioAccept.setEnabled(true);
    594             mLicenseRadioReject.setEnabled(true);
    595             mLicenseRadioAccept.setSelection(false);
    596             mLicenseRadioReject.setSelection(false);
    597         } else {
    598             mLicenseRadioAcceptAll.setSelection(false);
    599             oneAccepted = ai != null && ai.isAccepted();
    600             mLicenseRadioAccept.setEnabled(ai != null);
    601             mLicenseRadioReject.setEnabled(ai != null);
    602             mLicenseRadioAccept.setSelection(oneAccepted);
    603             mLicenseRadioReject.setSelection(ai != null && ai.isRejected());
    604         }
    605 
    606         // The install button is enabled if there's at least one package accepted.
    607         // If the current one isn't, look for another one.
    608         boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;
    609         if (!missing && !oneAccepted) {
    610             for(ArchiveInfo ai2 : mArchives) {
    611                 if (ai2.isAccepted()) {
    612                     oneAccepted = true;
    613                     break;
    614                 }
    615             }
    616         }
    617 
    618         getButton(IDialogConstants.OK_ID).setEnabled(!missing && oneAccepted);
    619 
    620         mInternalLicenseRadioUpdate = false;
    621     }
    622 
    623     /**
    624      * Callback invoked when one of the radio license buttons is selected.
    625      *
    626      * - accept/refuse: toggle, update item checkbox
    627      * - accept all: set accept-all, check all items
    628      */
    629     private void onLicenseRadioSelected() {
    630         if (mInternalLicenseRadioUpdate) {
    631             return;
    632         }
    633         mInternalLicenseRadioUpdate = true;
    634 
    635         ArchiveInfo ai = getSelectedArchive();
    636 
    637         if (ai == null) {
    638             // Should never happen.
    639             return;
    640         }
    641 
    642         boolean needUpdate = true;
    643 
    644         if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
    645             // Accept all has been switched on. Mark all packages as accepted
    646             mLicenseAcceptAll = true;
    647             for(ArchiveInfo ai2 : mArchives) {
    648                 ai2.setAccepted(true);
    649                 ai2.setRejected(false);
    650             }
    651 
    652         } else if (mLicenseRadioAccept.getSelection()) {
    653             // Accept only this one
    654             mLicenseAcceptAll = false;
    655             ai.setAccepted(true);
    656             ai.setRejected(false);
    657 
    658         } else if (mLicenseRadioReject.getSelection()) {
    659             // Reject only this one
    660             mLicenseAcceptAll = false;
    661             ai.setAccepted(false);
    662             ai.setRejected(true);
    663 
    664         } else {
    665             needUpdate = false;
    666         }
    667 
    668         mInternalLicenseRadioUpdate = false;
    669 
    670         if (needUpdate) {
    671             if (mLicenseAcceptAll) {
    672                 mTableViewPackage.refresh();
    673             } else {
    674                mTableViewPackage.refresh(ai);
    675             }
    676             displayMissingDependency(ai);
    677             updateLicenceRadios(ai);
    678         }
    679     }
    680 
    681     /**
    682      * Callback invoked when a package item is double-clicked in the list.
    683      */
    684     private void onPackageDoubleClick() {
    685         ArchiveInfo ai = getSelectedArchive();
    686 
    687         if (ai == null) {
    688             // Should never happen.
    689             return;
    690         }
    691 
    692         boolean wasAccepted = ai.isAccepted();
    693         ai.setAccepted(!wasAccepted);
    694         ai.setRejected(wasAccepted);
    695 
    696         // update state
    697         mLicenseAcceptAll = false;
    698         mTableViewPackage.refresh(ai);
    699         displayMissingDependency(ai);
    700         updateLicenceRadios(ai);
    701     }
    702 
    703     private class NewArchivesLabelProvider extends LabelProvider {
    704         @Override
    705         public Image getImage(Object element) {
    706             assert element instanceof ArchiveInfo;
    707             ArchiveInfo ai = (ArchiveInfo) element;
    708 
    709             ImageFactory imgFactory = mUpdaterData.getImageFactory();
    710             if (imgFactory != null) {
    711                 if (ai.isAccepted()) {
    712                     return imgFactory.getImageByName("accept_icon16.png");
    713                 } else if (ai.isRejected()) {
    714                     return imgFactory.getImageByName("reject_icon16.png");
    715                 }
    716                 return imgFactory.getImageByName("unknown_icon16.png");
    717             }
    718             return super.getImage(element);
    719         }
    720 
    721         @Override
    722         public String getText(Object element) {
    723             assert element instanceof ArchiveInfo;
    724             ArchiveInfo ai = (ArchiveInfo) element;
    725 
    726             String desc = ai.getShortDescription();
    727 
    728             if (ai.isDependencyFor()) {
    729                 desc += " [*]";
    730             }
    731 
    732             return desc;
    733         }
    734     }
    735 
    736     private class NewArchivesContentProvider implements IStructuredContentProvider {
    737 
    738         public void dispose() {
    739             // pass
    740         }
    741 
    742         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    743             // Ignore. The input is always mArchives
    744         }
    745 
    746         public Object[] getElements(Object inputElement) {
    747             return mArchives.toArray();
    748         }
    749     }
    750 
    751     // End of hiding from SWT Designer
    752     //$hide<<$
    753 }
    754