• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
59 
60 /**
61  * Implements an {@link UpdateChooserDialog}.
62  */
63 final class UpdateChooserDialog extends GridDialog {
64 
65     /** Last dialog size for this session. */
66     private static Point sLastSize;
67     private boolean mLicenseAcceptAll;
68     private boolean mInternalLicenseRadioUpdate;
69 
70     // UI fields
71     private SashForm mSashForm;
72     private Composite mPackageRootComposite;
73     private TableViewer mTableViewPackage;
74     private Table mTablePackage;
75     private TableColumn mTableColum;
76     private StyledText mPackageText;
77     private Button mLicenseRadioAccept;
78     private Button mLicenseRadioReject;
79     private Button mLicenseRadioAcceptAll;
80     private Group mPackageTextGroup;
81     private final UpdaterData mUpdaterData;
82     private Group mTableGroup;
83     private Label mErrorLabel;
84 
85     /**
86      * List of all archives to be installed with dependency information.
87      *
88      * Note: in a lot of cases, we need to find the archive info for a given archive. This
89      * is currently done using a simple linear search, which is fine since we only have a very
90      * limited number of archives to deal with (e.g. < 10 now). We might want to revisit
91      * this later if it becomes an issue. Right now just do the simple thing.
92      *
93      * Typically we could add a map Archive=>ArchiveInfo later.
94      */
95     private final ArrayList<ArchiveInfo> mArchives;
96 
97 
98 
99     /**
100      * Create the dialog.
101      * @param parentShell The shell to use, typically updaterData.getWindowShell()
102      * @param updaterData The updater data
103      * @param archives The archives to be installed
104      */
UpdateChooserDialog(Shell parentShell, UpdaterData updaterData, ArrayList<ArchiveInfo> archives)105     public UpdateChooserDialog(Shell parentShell,
106             UpdaterData updaterData,
107             ArrayList<ArchiveInfo> archives) {
108         super(parentShell, 3, false/*makeColumnsEqual*/);
109         mUpdaterData = updaterData;
110         mArchives = archives;
111     }
112 
113     @Override
isResizable()114     protected boolean isResizable() {
115         return true;
116     }
117 
118     /**
119      * Returns the results, i.e. the list of selected new archives to install.
120      * This is similar to the {@link ArchiveInfo} list instance given to the constructor
121      * except only accepted archives are present.
122      *
123      * An empty list is returned if cancel was choosen.
124      */
getResult()125     public ArrayList<ArchiveInfo> getResult() {
126         ArrayList<ArchiveInfo> ais = new ArrayList<ArchiveInfo>();
127 
128         if (getReturnCode() == Window.OK) {
129             for (ArchiveInfo ai : mArchives) {
130                 if (ai.isAccepted()) {
131                     ais.add(ai);
132                 }
133             }
134         }
135 
136         return ais;
137     }
138 
139     /**
140      * Create the main content of the dialog.
141      * See also {@link #createButtonBar(Composite)} below.
142      */
143     @Override
createDialogContent(Composite parent)144     public void createDialogContent(Composite parent) {
145         // Sash form
146         mSashForm = new SashForm(parent, SWT.NONE);
147         mSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
148 
149 
150         // Left part of Sash Form
151 
152         mTableGroup = new Group(mSashForm, SWT.NONE);
153         mTableGroup.setText("Packages");
154         mTableGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
155 
156         mTableViewPackage = new TableViewer(mTableGroup, SWT.BORDER | SWT.V_SCROLL | SWT.SINGLE);
157         mTablePackage = mTableViewPackage.getTable();
158         mTablePackage.setHeaderVisible(false);
159         mTablePackage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
160 
161         mTablePackage.addSelectionListener(new SelectionAdapter() {
162             @Override
163             public void widgetSelected(SelectionEvent e) {
164                 onPackageSelected();  //$hide$
165             }
166             @Override
167             public void widgetDefaultSelected(SelectionEvent e) {
168                 onPackageDoubleClick();
169             }
170         });
171 
172         mTableColum = new TableColumn(mTablePackage, SWT.NONE);
173         mTableColum.setWidth(100);
174         mTableColum.setText("Packages");
175 
176 
177         // Right part of Sash form
178         mPackageRootComposite = new Composite(mSashForm, SWT.NONE);
179         mPackageRootComposite.setLayout(new GridLayout(4, false/*makeColumnsEqual*/));
180         mPackageRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
181 
182         mPackageTextGroup = new Group(mPackageRootComposite, SWT.NONE);
183         mPackageTextGroup.setText("Package Description && License");
184         mPackageTextGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
185         mPackageTextGroup.setLayout(new GridLayout(1, false/*makeColumnsEqual*/));
186 
187         mPackageText = new StyledText(mPackageTextGroup,
188                         SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
189         mPackageText.setBackground(
190                 getParentShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
191         mPackageText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
192 
193         mLicenseRadioAccept = new Button(mPackageRootComposite, SWT.RADIO);
194         mLicenseRadioAccept.setText("Accept");
195         mLicenseRadioAccept.addSelectionListener(new SelectionAdapter() {
196             @Override
197             public void widgetSelected(SelectionEvent e) {
198                 onLicenseRadioSelected();
199             }
200         });
201 
202         mLicenseRadioReject = new Button(mPackageRootComposite, SWT.RADIO);
203         mLicenseRadioReject.setText("Reject");
204         mLicenseRadioReject.addSelectionListener(new SelectionAdapter() {
205             @Override
206             public void widgetSelected(SelectionEvent e) {
207                 onLicenseRadioSelected();
208             }
209         });
210 
211         Label placeholder = new Label(mPackageRootComposite, SWT.NONE);
212         placeholder.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
213 
214         mLicenseRadioAcceptAll = new Button(mPackageRootComposite, SWT.RADIO);
215         mLicenseRadioAcceptAll.setText("Accept All");
216         mLicenseRadioAcceptAll.addSelectionListener(new SelectionAdapter() {
217             @Override
218             public void widgetSelected(SelectionEvent e) {
219                 onLicenseRadioSelected();
220             }
221         });
222 
223         mSashForm.setWeights(new int[] {200, 300});
224     }
225 
226     /**
227      * Creates and returns the contents of this dialog's button bar.
228      * <p/>
229      * This reimplements most of the code from the base class with a few exceptions:
230      * <ul>
231      * <li>Enforces 3 columns.
232      * <li>Inserts a full-width error label.
233      * <li>Inserts a help label on the left of the first button.
234      * <li>Renames the OK button into "Install"
235      * </ul>
236      */
237     @Override
createButtonBar(Composite parent)238     protected Control createButtonBar(Composite parent) {
239 
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
create()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      */
setWindowImage()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      */
adjustColumnsWidth()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
close()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
getInitialSize()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      */
onPackageSelected()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. */
getSelectedArchive()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 UpdaterLogic}.
401      */
displayInformation(ArchiveInfo ai)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 display missing dependency.
488      * If there's a selected package, check the dependency for that one.
489      * Otherwise display the first missing dependency.
490      */
displayMissingDependency(ArchiveInfo ai)491     private void displayMissingDependency(ArchiveInfo ai) {
492         String error = null;
493 
494         try {
495             if (ai != null) {
496 
497                 if (!ai.isAccepted()) {
498                     // Case where this package blocks another one when not accepted
499                     for (ArchiveInfo ai2 : ai.getDependenciesFor()) {
500                         // It only matters if the blocked one is accepted
501                         if (ai2.isAccepted()) {
502                             error = String.format("Package '%1$s' depends on this one.",
503                                     ai2.getShortDescription());
504                             return;
505                         }
506                     }
507                 } else {
508                     // Case where this package is accepted but blocked by another non-accepted one
509                     ArchiveInfo[] adeps = ai.getDependsOn();
510                     if (adeps != null) {
511                         for (ArchiveInfo adep : adeps) {
512                             if (!adep.isAccepted()) {
513                                 error = String.format("This package depends on '%1$s'.",
514                                         adep.getShortDescription());
515                                 return;
516                             }
517                         }
518                     }
519                 }
520             }
521 
522             // If there's no selection, just find the first missing dependency of any accepted
523             // package.
524             for (ArchiveInfo ai2 : mArchives) {
525                 if (ai2.isAccepted()) {
526                     ArchiveInfo[] adeps = ai2.getDependsOn();
527                     if (adeps != null) {
528                         for (ArchiveInfo adep : adeps) {
529                             if (!adep.isAccepted()) {
530                                 error = String.format("Package '%1$s' depends on '%2$s'",
531                                         ai2.getShortDescription(),
532                                         adep.getShortDescription());
533                                 return;
534                             }
535                         }
536                     }
537                 }
538             }
539         } finally {
540             mErrorLabel.setText(error == null ? "" : error);        //$NON-NLS-1$
541         }
542     }
543 
addText(String...string)544     private void addText(String...string) {
545         for (String s : string) {
546             mPackageText.append(s);
547         }
548     }
549 
addSectionTitle(String string)550     private void addSectionTitle(String string) {
551         String s = mPackageText.getText();
552         int start = (s == null ? 0 : s.length());
553         mPackageText.append(string);
554 
555         StyleRange sr = new StyleRange();
556         sr.start = start;
557         sr.length = string.length();
558         sr.fontStyle = SWT.BOLD;
559         sr.underline = true;
560         mPackageText.setStyleRange(sr);
561     }
562 
updateLicenceRadios(ArchiveInfo ai)563     private void updateLicenceRadios(ArchiveInfo ai) {
564         if (mInternalLicenseRadioUpdate) {
565             return;
566         }
567         mInternalLicenseRadioUpdate = true;
568 
569         boolean oneAccepted = false;
570 
571         if (mLicenseAcceptAll) {
572             mLicenseRadioAcceptAll.setSelection(true);
573             mLicenseRadioAccept.setEnabled(true);
574             mLicenseRadioReject.setEnabled(true);
575             mLicenseRadioAccept.setSelection(false);
576             mLicenseRadioReject.setSelection(false);
577         } else {
578             mLicenseRadioAcceptAll.setSelection(false);
579             oneAccepted = ai != null && ai.isAccepted();
580             mLicenseRadioAccept.setEnabled(ai != null);
581             mLicenseRadioReject.setEnabled(ai != null);
582             mLicenseRadioAccept.setSelection(oneAccepted);
583             mLicenseRadioReject.setSelection(ai != null && ai.isRejected());
584         }
585 
586         // The install button is enabled if there's at least one package accepted.
587         // If the current one isn't, look for another one.
588         boolean missing = mErrorLabel.getText() != null && mErrorLabel.getText().length() > 0;
589         if (!missing && !oneAccepted) {
590             for(ArchiveInfo ai2 : mArchives) {
591                 if (ai2.isAccepted()) {
592                     oneAccepted = true;
593                     break;
594                 }
595             }
596         }
597 
598         getButton(IDialogConstants.OK_ID).setEnabled(!missing && oneAccepted);
599 
600         mInternalLicenseRadioUpdate = false;
601     }
602 
603     /**
604      * Callback invoked when one of the radio license buttons is selected.
605      *
606      * - accept/refuse: toggle, update item checkbox
607      * - accept all: set accept-all, check all items
608      */
onLicenseRadioSelected()609     private void onLicenseRadioSelected() {
610         if (mInternalLicenseRadioUpdate) {
611             return;
612         }
613         mInternalLicenseRadioUpdate = true;
614 
615         ArchiveInfo ai = getSelectedArchive();
616 
617         if (ai == null) {
618             // Should never happen.
619             return;
620         }
621 
622         boolean needUpdate = true;
623 
624         if (!mLicenseAcceptAll && mLicenseRadioAcceptAll.getSelection()) {
625             // Accept all has been switched on. Mark all packages as accepted
626             mLicenseAcceptAll = true;
627             for(ArchiveInfo ai2 : mArchives) {
628                 ai2.setAccepted(true);
629                 ai2.setRejected(false);
630             }
631 
632         } else if (mLicenseRadioAccept.getSelection()) {
633             // Accept only this one
634             mLicenseAcceptAll = false;
635             ai.setAccepted(true);
636             ai.setRejected(false);
637 
638         } else if (mLicenseRadioReject.getSelection()) {
639             // Reject only this one
640             mLicenseAcceptAll = false;
641             ai.setAccepted(false);
642             ai.setRejected(true);
643 
644         } else {
645             needUpdate = false;
646         }
647 
648         mInternalLicenseRadioUpdate = false;
649 
650         if (needUpdate) {
651             if (mLicenseAcceptAll) {
652                 mTableViewPackage.refresh();
653             } else {
654                mTableViewPackage.refresh(ai);
655             }
656             displayMissingDependency(ai);
657             updateLicenceRadios(ai);
658         }
659     }
660 
661     /**
662      * Callback invoked when a package item is double-clicked in the list.
663      */
onPackageDoubleClick()664     private void onPackageDoubleClick() {
665         ArchiveInfo ai = getSelectedArchive();
666 
667         if (ai == null) {
668             // Should never happen.
669             return;
670         }
671 
672         boolean wasAccepted = ai.isAccepted();
673         ai.setAccepted(!wasAccepted);
674         ai.setRejected(wasAccepted);
675 
676         // update state
677         mLicenseAcceptAll = false;
678         mTableViewPackage.refresh(ai);
679         updateLicenceRadios(ai);
680     }
681 
682     private class NewArchivesLabelProvider extends LabelProvider {
683         @Override
getImage(Object element)684         public Image getImage(Object element) {
685             assert element instanceof ArchiveInfo;
686             ArchiveInfo ai = (ArchiveInfo) element;
687 
688             ImageFactory imgFactory = mUpdaterData.getImageFactory();
689             if (imgFactory != null) {
690                 if (ai.isAccepted()) {
691                     return imgFactory.getImageByName("accept_icon16.png");
692                 } else if (ai.isRejected()) {
693                     return imgFactory.getImageByName("reject_icon16.png");
694                 }
695                 return imgFactory.getImageByName("unknown_icon16.png");
696             }
697             return super.getImage(element);
698         }
699 
700         @Override
getText(Object element)701         public String getText(Object element) {
702             assert element instanceof ArchiveInfo;
703             ArchiveInfo ai = (ArchiveInfo) element;
704 
705             String desc = ai.getShortDescription();
706 
707             if (ai.isDependencyFor()) {
708                 desc += " [*]";
709             }
710 
711             return desc;
712         }
713     }
714 
715     private class NewArchivesContentProvider implements IStructuredContentProvider {
716 
dispose()717         public void dispose() {
718             // pass
719         }
720 
inputChanged(Viewer viewer, Object oldInput, Object newInput)721         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
722             // Ignore. The input is always mArchives
723         }
724 
getElements(Object inputElement)725         public Object[] getElements(Object inputElement) {
726             return mArchives.toArray();
727         }
728     }
729 
730     // End of hiding from SWT Designer
731     //$hide<<$
732 }
733