• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.sdkman2;
18 
19 import com.android.sdklib.internal.repository.Archive;
20 import com.android.sdklib.internal.repository.IDescription;
21 import com.android.sdklib.internal.repository.ITask;
22 import com.android.sdklib.internal.repository.ITaskMonitor;
23 import com.android.sdklib.internal.repository.Package;
24 import com.android.sdklib.internal.repository.SdkSource;
25 import com.android.sdkuilib.internal.repository.IPageListener;
26 import com.android.sdkuilib.internal.repository.UpdaterData;
27 import com.android.sdkuilib.internal.repository.UpdaterPage;
28 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
29 import com.android.sdkuilib.internal.repository.sdkman2.PackageLoader.ISourceLoadedCallback;
30 import com.android.sdkuilib.internal.repository.sdkman2.PkgItem.PkgState;
31 import com.android.sdkuilib.repository.ISdkChangeListener;
32 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
33 import com.android.sdkuilib.ui.GridDataBuilder;
34 import com.android.sdkuilib.ui.GridLayoutBuilder;
35 
36 import org.eclipse.jface.dialogs.MessageDialog;
37 import org.eclipse.jface.viewers.CellLabelProvider;
38 import org.eclipse.jface.viewers.CheckStateChangedEvent;
39 import org.eclipse.jface.viewers.CheckboxTreeViewer;
40 import org.eclipse.jface.viewers.ColumnLabelProvider;
41 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
42 import org.eclipse.jface.viewers.DoubleClickEvent;
43 import org.eclipse.jface.viewers.ICheckStateListener;
44 import org.eclipse.jface.viewers.IDoubleClickListener;
45 import org.eclipse.jface.viewers.ISelection;
46 import org.eclipse.jface.viewers.ITableFontProvider;
47 import org.eclipse.jface.viewers.ITreeContentProvider;
48 import org.eclipse.jface.viewers.ITreeSelection;
49 import org.eclipse.jface.viewers.TreeColumnViewerLabelProvider;
50 import org.eclipse.jface.viewers.TreePath;
51 import org.eclipse.jface.viewers.TreeViewerColumn;
52 import org.eclipse.jface.viewers.Viewer;
53 import org.eclipse.jface.viewers.ViewerFilter;
54 import org.eclipse.jface.window.ToolTip;
55 import org.eclipse.swt.SWT;
56 import org.eclipse.swt.events.DisposeEvent;
57 import org.eclipse.swt.events.DisposeListener;
58 import org.eclipse.swt.events.SelectionAdapter;
59 import org.eclipse.swt.events.SelectionEvent;
60 import org.eclipse.swt.graphics.Color;
61 import org.eclipse.swt.graphics.Font;
62 import org.eclipse.swt.graphics.FontData;
63 import org.eclipse.swt.graphics.Image;
64 import org.eclipse.swt.graphics.Point;
65 import org.eclipse.swt.widgets.Button;
66 import org.eclipse.swt.widgets.Composite;
67 import org.eclipse.swt.widgets.Control;
68 import org.eclipse.swt.widgets.Event;
69 import org.eclipse.swt.widgets.Group;
70 import org.eclipse.swt.widgets.Label;
71 import org.eclipse.swt.widgets.Link;
72 import org.eclipse.swt.widgets.MenuItem;
73 import org.eclipse.swt.widgets.Text;
74 import org.eclipse.swt.widgets.Tree;
75 import org.eclipse.swt.widgets.TreeColumn;
76 
77 import java.io.File;
78 import java.util.ArrayList;
79 import java.util.HashMap;
80 import java.util.List;
81 import java.util.Map;
82 import java.util.TreeMap;
83 import java.util.Map.Entry;
84 
85 /**
86  * Page that displays both locally installed packages as well as all known
87  * remote available packages. This gives an overview of what is installed
88  * vs what is available and allows the user to update or install packages.
89  */
90 public class PackagesPage extends UpdaterPage
91         implements ISdkChangeListener, IPageListener {
92 
93     static final String ICON_CAT_OTHER      = "pkgcat_other_16.png";    //$NON-NLS-1$
94     static final String ICON_CAT_PLATFORM   = "pkgcat_16.png";          //$NON-NLS-1$
95     static final String ICON_SORT_BY_SOURCE = "source_icon16.png";      //$NON-NLS-1$
96     static final String ICON_SORT_BY_API    = "platform_pkg_16.png";    //$NON-NLS-1$
97     static final String ICON_PKG_NEW        = "pkg_new_16.png";         //$NON-NLS-1$
98     static final String ICON_PKG_UPDATE     = "pkg_update_16.png";      //$NON-NLS-1$
99     static final String ICON_PKG_INSTALLED  = "pkg_installed_16.png";   //$NON-NLS-1$
100 
101     enum MenuAction {
102         RELOAD                      (SWT.NONE,  "Reload"),
103         SHOW_ADDON_SITES            (SWT.NONE,  "Manage Add-on Sites..."),
104         TOGGLE_SHOW_ARCHIVES        (SWT.CHECK, "Show Archives Details"),
105         TOGGLE_SHOW_INSTALLED_PKG   (SWT.CHECK, "Show Installed Packages"),
106         TOGGLE_SHOW_OBSOLETE_PKG    (SWT.CHECK, "Show Obsolete Packages"),
107         TOGGLE_SHOW_UPDATE_NEW_PKG  (SWT.CHECK, "Show Updates/New Packages"),
108         SORT_API_LEVEL              (SWT.RADIO, "Sort by API Level"),
109         SORT_SOURCE                 (SWT.RADIO, "Sort by Repository")
110         ;
111 
112         private final int mMenuStyle;
113         private final String mMenuTitle;
114 
MenuAction(int menuStyle, String menuTitle)115         MenuAction(int menuStyle, String menuTitle) {
116             mMenuStyle = menuStyle;
117             mMenuTitle = menuTitle;
118         }
119 
getMenuStyle()120         public int getMenuStyle() {
121             return mMenuStyle;
122         }
123 
getMenuTitle()124         public String getMenuTitle() {
125             return mMenuTitle;
126         }
127     };
128 
129     private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>();
130 
131     private final SdkInvocationContext mContext;
132     private final UpdaterData mUpdaterData;
133     private final PackagesDiffLogic mDiffLogic;
134     private boolean mDisplayArchives = false;
135     private boolean mOperationPending;
136 
137     private Text mTextSdkOsPath;
138     private Button mCheckSortSource;
139     private Button mCheckSortApi;
140     private Button mCheckFilterObsolete;
141     private Button mCheckFilterInstalled;
142     private Button mCheckFilterNew;
143     private Composite mGroupOptions;
144     private Composite mGroupSdk;
145     private Group mGroupPackages;
146     private Button mButtonDelete;
147     private Button mButtonInstall;
148     private Tree mTree;
149     private CheckboxTreeViewer mTreeViewer;
150     private TreeViewerColumn mColumnName;
151     private TreeViewerColumn mColumnApi;
152     private TreeViewerColumn mColumnRevision;
153     private TreeViewerColumn mColumnStatus;
154     private Font mTreeFontItalic;
155     private TreeColumn mTreeColumnName;
156 
PackagesPage( Composite parent, int swtStyle, UpdaterData updaterData, SdkInvocationContext context)157     public PackagesPage(
158             Composite parent,
159             int swtStyle,
160             UpdaterData updaterData,
161             SdkInvocationContext context) {
162         super(parent, swtStyle);
163         mUpdaterData = updaterData;
164         mContext = context;
165 
166         mDiffLogic = new PackagesDiffLogic(updaterData);
167 
168         createContents(this);
169         postCreate();  //$hide$
170     }
171 
onPageSelected()172     public void onPageSelected() {
173         List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
174         if (cats == null || cats.isEmpty()) {
175             // Initialize the package list the first time the page is shown.
176             loadPackages();
177         }
178     }
179 
createContents(Composite parent)180     private void createContents(Composite parent) {
181         GridLayoutBuilder.create(parent).noMargins().columns(2);
182 
183         mGroupSdk = new Composite(parent, SWT.NONE);
184         GridDataBuilder.create(mGroupSdk).hFill().vCenter().hGrab().hSpan(2);
185         GridLayoutBuilder.create(mGroupSdk).columns(2);
186 
187         Label label1 = new Label(mGroupSdk, SWT.NONE);
188         label1.setText("SDK Path:");
189 
190         mTextSdkOsPath = new Text(mGroupSdk, SWT.NONE);
191         GridDataBuilder.create(mTextSdkOsPath).hFill().vCenter().hGrab();
192         mTextSdkOsPath.setEnabled(false);
193 
194         mGroupPackages = new Group(parent, SWT.NONE);
195         GridDataBuilder.create(mGroupPackages).fill().grab().hSpan(2);
196         mGroupPackages.setText("Packages");
197         GridLayoutBuilder.create(mGroupPackages).columns(1);
198 
199         mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER);
200         mTreeViewer.addFilter(new ViewerFilter() {
201             @Override
202             public boolean select(Viewer viewer, Object parentElement, Object element) {
203                 return filterViewerItem(element);
204             }
205         });
206 
207         mTreeViewer.addCheckStateListener(new ICheckStateListener() {
208             public void checkStateChanged(CheckStateChangedEvent event) {
209                 onTreeCheckStateChanged(event); //$hide$
210             }
211         });
212 
213         mTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
214             public void doubleClick(DoubleClickEvent event) {
215                 onTreeDoubleClick(event); //$hide$
216             }
217         });
218 
219         mTree = mTreeViewer.getTree();
220         mTree.setLinesVisible(true);
221         mTree.setHeaderVisible(true);
222         GridDataBuilder.create(mTree).fill().grab();
223 
224         // column name icon is set when loading depending on the current filter type
225         // (e.g. API level or source)
226         mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE);
227         mTreeColumnName = mColumnName.getColumn();
228         mTreeColumnName.setText("Name");
229         mTreeColumnName.setWidth(340);
230 
231         mColumnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE);
232         TreeColumn treeColumn2 = mColumnApi.getColumn();
233         treeColumn2.setText("API");
234         treeColumn2.setAlignment(SWT.CENTER);
235         treeColumn2.setWidth(50);
236 
237         mColumnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE);
238         TreeColumn treeColumn3 = mColumnRevision.getColumn();
239         treeColumn3.setText("Rev.");
240         treeColumn3.setToolTipText("Revision currently installed");
241         treeColumn3.setAlignment(SWT.CENTER);
242         treeColumn3.setWidth(50);
243 
244 
245         mColumnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE);
246         TreeColumn treeColumn4 = mColumnStatus.getColumn();
247         treeColumn4.setText("Status");
248         treeColumn4.setAlignment(SWT.LEAD);
249         treeColumn4.setWidth(190);
250 
251         mGroupOptions = new Composite(mGroupPackages, SWT.NONE);
252         GridDataBuilder.create(mGroupOptions).hFill().vCenter().hGrab();
253         GridLayoutBuilder.create(mGroupOptions).columns(6).noMargins();
254 
255         // Options line 1, 6 columns
256 
257         Label label3 = new Label(mGroupOptions, SWT.NONE);
258         label3.setText("Show:");
259 
260         mCheckFilterNew = new Button(mGroupOptions, SWT.CHECK);
261         mCheckFilterNew.setText("Updates/New");
262         mCheckFilterNew.setToolTipText("Show Updates and New");
263         mCheckFilterNew.addSelectionListener(new SelectionAdapter() {
264             @Override
265             public void widgetSelected(SelectionEvent e) {
266                 refreshViewerInput();
267             }
268         });
269         mCheckFilterNew.setSelection(true);
270 
271         mCheckFilterInstalled = new Button(mGroupOptions, SWT.CHECK);
272         mCheckFilterInstalled.setToolTipText("Show Installed");
273         mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() {
274             @Override
275             public void widgetSelected(SelectionEvent e) {
276                 refreshViewerInput();
277             }
278         });
279         mCheckFilterInstalled.setSelection(true);
280         mCheckFilterInstalled.setText("Installed");
281 
282         mCheckFilterObsolete = new Button(mGroupOptions, SWT.CHECK);
283         mCheckFilterObsolete.setText("Obsolete");
284         mCheckFilterObsolete.setToolTipText("Also show obsolete packages");
285         mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() {
286             @Override
287             public void widgetSelected(SelectionEvent e) {
288                 refreshViewerInput();
289             }
290         });
291         mCheckFilterObsolete.setSelection(false);
292 
293         Link linkSelectNew = new Link(mGroupOptions, SWT.NONE);
294         // Note for i18n: we need to identify which link is used, and this is done by using the
295         // text itself so for translation purposes we want to keep the <a> link strings separate.
296         final String strLinkNew = "New";
297         final String strLinkUpdates = "Updates";
298         linkSelectNew.setText(
299                 String.format("Select <a>%1$s</a> or <a>%2$s</a>", strLinkNew, strLinkUpdates));
300         linkSelectNew.setToolTipText("Selects all items that are either new or updates.");
301         GridDataBuilder.create(linkSelectNew).hFill().hGrab();
302         linkSelectNew.addSelectionListener(new SelectionAdapter() {
303             @Override
304             public void widgetSelected(SelectionEvent e) {
305                 super.widgetSelected(e);
306                 boolean selectNew = e.text == null || e.text.equals(strLinkNew);
307                 onSelectNewUpdates(selectNew, !selectNew);
308             }
309         });
310 
311         mButtonInstall = new Button(mGroupOptions, SWT.NONE);
312         mButtonInstall.setText("");  //$NON-NLS-1$  placeholder, filled in updateButtonsState()
313         mButtonInstall.setToolTipText("Install one or more packages");
314         GridDataBuilder.create(mButtonInstall).hFill().vCenter().hGrab();
315         mButtonInstall.addSelectionListener(new SelectionAdapter() {
316             @Override
317             public void widgetSelected(SelectionEvent e) {
318                 onButtonInstall();  //$hide$
319             }
320         });
321 
322         // Options line 2, 6 columns
323 
324         Label label2 = new Label(mGroupOptions, SWT.NONE);
325         label2.setText("Sort by:");
326 
327         mCheckSortApi = new Button(mGroupOptions, SWT.RADIO);
328         mCheckSortApi.setToolTipText("Sort by API level");
329         mCheckSortApi.addSelectionListener(new SelectionAdapter() {
330             @Override
331             public void widgetSelected(SelectionEvent e) {
332                 if (mCheckSortApi.getSelection()) {
333                     refreshViewerInput();
334                     copySelection(true /*toApi*/);
335                     syncViewerSelection();
336                 }
337             }
338         });
339         mCheckSortApi.setText("API level");
340         mCheckSortApi.setSelection(true);
341 
342         mCheckSortSource = new Button(mGroupOptions, SWT.RADIO);
343         mCheckSortSource.setText("Repository");
344         mCheckSortSource.setToolTipText("Sort by Repository");
345         mCheckSortSource.addSelectionListener(new SelectionAdapter() {
346             @Override
347             public void widgetSelected(SelectionEvent e) {
348                 if (mCheckSortSource.getSelection()) {
349                     refreshViewerInput();
350                     copySelection(false /*toApi*/);
351                     syncViewerSelection();
352                 }
353             }
354         });
355 
356         new Label(mGroupOptions, SWT.NONE);
357 
358         Link linkDeselect = new Link(mGroupOptions, SWT.NONE);
359         linkDeselect.setText("<a>Deselect All</a>");
360         linkDeselect.setToolTipText("Deselects all the currently selected items");
361         GridDataBuilder.create(linkDeselect).hFill().hGrab();
362         linkDeselect.addSelectionListener(new SelectionAdapter() {
363             @Override
364             public void widgetSelected(SelectionEvent e) {
365                 super.widgetSelected(e);
366                 onDeselectAll();
367             }
368         });
369 
370         mButtonDelete = new Button(mGroupOptions, SWT.NONE);
371         mButtonDelete.setText("");  //$NON-NLS-1$  placeholder, filled in updateButtonsState()
372         mButtonDelete.setToolTipText("Delete one ore more installed packages");
373         GridDataBuilder.create(mButtonDelete).hFill().vCenter().hGrab();
374         mButtonDelete.addSelectionListener(new SelectionAdapter() {
375             @Override
376             public void widgetSelected(SelectionEvent e) {
377                 onButtonDelete();  //$hide$
378             }
379         });
380     }
381 
getImage(String filename)382     private Image getImage(String filename) {
383         if (mUpdaterData != null) {
384             ImageFactory imgFactory = mUpdaterData.getImageFactory();
385             if (imgFactory != null) {
386                 return imgFactory.getImageByName(filename);
387             }
388         }
389         return null;
390     }
391 
392 
393     // -- Start of internal part ----------
394     // Hide everything down-below from SWT designer
395     //$hide>>$
396 
397 
398     // --- menu interactions ---
399 
registerMenuAction(final MenuAction action, MenuItem item)400     public void registerMenuAction(final MenuAction action, MenuItem item) {
401         item.addSelectionListener(new SelectionAdapter() {
402             @Override
403             public void widgetSelected(SelectionEvent e) {
404                 Button button = null;
405 
406                 switch (action) {
407                 case RELOAD:
408                     fullReload();
409                     break;
410                 case SHOW_ADDON_SITES:
411                     AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData);
412                     if (d.open()) {
413                         loadPackages();
414                     }
415                     break;
416                 case TOGGLE_SHOW_ARCHIVES:
417                     mDisplayArchives = !mDisplayArchives;
418                     // Force the viewer to be refreshed
419                     mTreeViewer.setInput(null);
420                     refreshViewerInput();
421                     syncViewerSelection();
422                     break;
423                 case TOGGLE_SHOW_INSTALLED_PKG:
424                     button = mCheckFilterInstalled;
425                     break;
426                 case TOGGLE_SHOW_OBSOLETE_PKG:
427                     button = mCheckFilterObsolete;
428                     break;
429                 case TOGGLE_SHOW_UPDATE_NEW_PKG:
430                     button = mCheckFilterNew;
431                     break;
432                 case SORT_API_LEVEL:
433                     button = mCheckSortApi;
434                     break;
435                 case SORT_SOURCE:
436                     button = mCheckSortSource;
437                     break;
438                 }
439 
440                 if (button != null && !button.isDisposed()) {
441                     // Toggle this button (radio or checkbox)
442 
443                     boolean value = button.getSelection();
444 
445                     // SWT doesn't automatically switch radio buttons when using the
446                     // Widget#setSelection method, so we'll do it here manually.
447                     if (!value && (button.getStyle() & SWT.RADIO) != 0) {
448                         // we'll be selecting this radio button, so deselect all ther other ones
449                         // in the parent group.
450                         for (Control child : button.getParent().getChildren()) {
451                             if (child instanceof Button &&
452                                     child != button &&
453                                     (child.getStyle() & SWT.RADIO) != 0) {
454                                 ((Button) child).setSelection(value);
455                             }
456                         }
457                     }
458 
459                     button.setSelection(!value);
460 
461                     // SWT doesn't actually invoke the listeners when using Widget#setSelection
462                     // so let's run the actual action.
463                     button.notifyListeners(SWT.Selection, new Event());
464                 }
465 
466                 updateMenuCheckmarks();
467             }
468         });
469 
470         mMenuActions.put(action, item);
471     }
472 
473     // --- internal methods ---
474 
updateMenuCheckmarks()475     private void updateMenuCheckmarks() {
476 
477         for (Entry<MenuAction, MenuItem> entry : mMenuActions.entrySet()) {
478             MenuAction action = entry.getKey();
479             MenuItem item = entry.getValue();
480 
481             if (action.getMenuStyle() == SWT.NONE) {
482                 continue;
483             }
484 
485             boolean value = false;
486             Button button = null;
487 
488             switch (action) {
489             case TOGGLE_SHOW_ARCHIVES:
490                 value = mDisplayArchives;
491                 break;
492             case TOGGLE_SHOW_INSTALLED_PKG:
493                 button = mCheckFilterInstalled;
494                 break;
495             case TOGGLE_SHOW_OBSOLETE_PKG:
496                 button = mCheckFilterObsolete;
497                 break;
498             case TOGGLE_SHOW_UPDATE_NEW_PKG:
499                 button = mCheckFilterNew;
500                 break;
501             case SORT_API_LEVEL:
502                 button = mCheckSortApi;
503                 break;
504             case SORT_SOURCE:
505                 button = mCheckSortSource;
506                 break;
507             }
508 
509             if (button != null && !button.isDisposed()) {
510                 value = button.getSelection();
511             }
512 
513             item.setSelection(value);
514         }
515 
516     }
517 
postCreate()518     private void postCreate() {
519         if (mUpdaterData != null) {
520             mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
521         }
522 
523         mTreeViewer.setContentProvider(new PkgContentProvider());
524         ColumnViewerToolTipSupport.enableFor(mTreeViewer, ToolTip.NO_RECREATE);
525 
526         mColumnApi.setLabelProvider     (new PkgTreeColumnViewerLabelProvider(mColumnApi));
527         mColumnName.setLabelProvider    (new PkgTreeColumnViewerLabelProvider(mColumnName));
528         mColumnStatus.setLabelProvider  (new PkgTreeColumnViewerLabelProvider(mColumnStatus));
529         mColumnRevision.setLabelProvider(new PkgTreeColumnViewerLabelProvider(mColumnRevision));
530 
531         FontData fontData = mTree.getFont().getFontData()[0];
532         fontData.setStyle(SWT.ITALIC);
533         mTreeFontItalic = new Font(mTree.getDisplay(), fontData);
534 
535         mTree.addDisposeListener(new DisposeListener() {
536             public void widgetDisposed(DisposeEvent e) {
537                 mTreeFontItalic.dispose();
538                 mTreeFontItalic = null;
539             }
540         });
541     }
542 
543     /**
544      * Performs a full reload by removing all cached packages data, including the platforms
545      * and addons from the sdkmanager instance. This will perform a full local parsing
546      * as well as a full reload of the remote data (by fetching all sources again.)
547      */
fullReload()548     private void fullReload() {
549         // Clear all source information, forcing them to be refreshed.
550         mUpdaterData.getSources().clearAllPackages();
551         // Clear and reload all local data too.
552         localReload();
553     }
554 
555     /**
556      * Performs a full reload of all the local package information, including the platforms
557      * and addons from the sdkmanager instance. This will perform a full local parsing.
558      * <p/>
559      * This method does NOT force a new fetch of the remote sources.
560      *
561      * @see #fullReload()
562      */
localReload()563     private void localReload() {
564         // Clear all source caches, otherwise loading will use the cached data
565         mUpdaterData.getLocalSdkParser().clearPackages();
566         mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
567         loadPackages();
568     }
569 
loadPackages()570     private void loadPackages() {
571         if (mUpdaterData == null) {
572             return;
573         }
574 
575         // LoadPackage is synchronous but does not block the UI.
576         // Consequently it's entirely possible for the user
577         // to request the app to close whilst the packages are loading. Any
578         // action done after loadPackages must check the UI hasn't been
579         // disposed yet. Otherwise hilarity ensues.
580 
581         final boolean displaySortByApi = isSortByApi();
582 
583         if (!mTreeColumnName.isDisposed()) {
584             mTreeColumnName.setImage(
585                     getImage(displaySortByApi ? ICON_SORT_BY_API : ICON_SORT_BY_SOURCE));
586         }
587 
588         mDiffLogic.updateStart();
589         mDiffLogic.getPackageLoader().loadPackages(new ISourceLoadedCallback() {
590             public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
591                 // This runs in a thread and must not access UI directly.
592                 final boolean changed = mDiffLogic.updateSourcePackages(
593                         displaySortByApi, source, newPackages);
594 
595                 if (!mGroupPackages.isDisposed()) {
596                     mGroupPackages.getDisplay().syncExec(new Runnable() {
597                         public void run() {
598                             if (changed ||
599                                 mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
600                                 refreshViewerInput();
601                             }
602                         }
603                     });
604                 }
605 
606                 // Return true to tell the loader to continue with the next source.
607                 // Return false to stop the loader if any UI has been disposed, which can
608                 // happen if the user is trying to close the window during the load operation.
609                 return !mGroupPackages.isDisposed();
610             }
611 
612             public void onLoadCompleted() {
613                 // This runs in a thread and must not access UI directly.
614                 final boolean changed = mDiffLogic.updateEnd(displaySortByApi);
615 
616                 if (!mGroupPackages.isDisposed()) {
617                     mGroupPackages.getDisplay().syncExec(new Runnable() {
618                         public void run() {
619                             if (changed ||
620                                 mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
621                                 refreshViewerInput();
622                             }
623 
624                             if (mDiffLogic.isFirstLoadComplete() && !mGroupPackages.isDisposed()) {
625                                 // At the end of the first load, if nothing is selected then
626                                 // automatically select all new and update packages.
627                                 Object[] checked = mTreeViewer.getCheckedElements();
628                                 if (checked == null || checked.length == 0) {
629                                     onSelectNewUpdates(true, true);
630                                 }
631                             }
632                         }
633                     });
634                 }
635             }
636         });
637     }
638 
refreshViewerInput()639     private void refreshViewerInput() {
640         // Dynamically update the table while we load after each source.
641         // Since the official Android source gets loaded first, it makes the
642         // window look non-empty a lot sooner.
643         if (!mGroupPackages.isDisposed()) {
644 
645             List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
646             if (mTreeViewer.getInput() != cats) {
647                 // set initial input
648                 mTreeViewer.setInput(cats);
649             } else {
650                 // refresh existing, which preserves the expanded state, the selection
651                 // and the checked state.
652                 mTreeViewer.refresh();
653             }
654 
655             // set the initial expanded state
656             expandInitial(mTreeViewer.getInput());
657 
658             updateButtonsState();
659             updateMenuCheckmarks();
660         }
661     }
662 
isSortByApi()663     private boolean isSortByApi() {
664         return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection();
665     }
666 
667     /**
668      * Decide whether to keep an item in the current tree based on user-chosen filter options.
669      */
filterViewerItem(Object treeElement)670     private boolean filterViewerItem(Object treeElement) {
671         if (treeElement instanceof PkgCategory) {
672             PkgCategory cat = (PkgCategory) treeElement;
673 
674             if (!cat.getItems().isEmpty()) {
675                 // A category is hidden if all of its content is hidden.
676                 // However empty categories are always visible.
677                 for (PkgItem item : cat.getItems()) {
678                     if (filterViewerItem(item)) {
679                         // We found at least one element that is visible.
680                         return true;
681                     }
682                 }
683                 return false;
684             }
685         }
686 
687         if (treeElement instanceof PkgItem) {
688             PkgItem item = (PkgItem) treeElement;
689 
690             if (!mCheckFilterObsolete.getSelection()) {
691                 if (item.isObsolete()) {
692                     return false;
693                 }
694             }
695 
696             if (!mCheckFilterInstalled.getSelection()) {
697                 if (item.getState() == PkgState.INSTALLED) {
698                     return false;
699                 }
700             }
701 
702             if (!mCheckFilterNew.getSelection()) {
703                 if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
704                     return false;
705                 }
706             }
707         }
708 
709         return true;
710     }
711 
712     /**
713      * Performs the initial expansion of the tree. This expands categories that contain
714      * at least one installed item and collapses the ones with nothing installed.
715      *
716      * TODO: change this to only change the expanded state on categories that have not
717      * been touched by the user yet. Once we do that, call this every time a new source
718      * is added or the list is reloaded.
719      */
expandInitial(Object elem)720     private void expandInitial(Object elem) {
721         if (mTreeViewer != null && !mTreeViewer.getTree().isDisposed()) {
722             mTreeViewer.setExpandedState(elem, true);
723             for (Object pkg :
724                     ((ITreeContentProvider) mTreeViewer.getContentProvider()).getChildren(elem)) {
725                 if (pkg instanceof PkgCategory) {
726                     PkgCategory cat = (PkgCategory) pkg;
727                     for (PkgItem item : cat.getItems()) {
728                         if (item.getState() == PkgState.INSTALLED) {
729                             expandInitial(pkg);
730                             break;
731                         }
732                     }
733                 }
734             }
735         }
736     }
737 
738     /**
739      * Handle checking and unchecking of the tree items.
740      *
741      * When unchecking, all sub-tree items checkboxes are cleared too.
742      * When checking a source, all of its packages are checked too.
743      * When checking a package, only its compatible archives are checked.
744      */
onTreeCheckStateChanged(CheckStateChangedEvent event)745     private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
746         boolean checked = event.getChecked();
747         Object elem = event.getElement();
748 
749         assert event.getSource() == mTreeViewer;
750 
751         // When selecting, we want to only select compatible archives and expand the super nodes.
752         checkAndExpandItem(elem, checked, true/*fixChildren*/, true/*fixParent*/);
753         updateButtonsState();
754     }
755 
onTreeDoubleClick(DoubleClickEvent event)756     private void onTreeDoubleClick(DoubleClickEvent event) {
757         assert event.getSource() == mTreeViewer;
758         ISelection sel = event.getSelection();
759         if (sel.isEmpty() || !(sel instanceof ITreeSelection)) {
760             return;
761         }
762         ITreeSelection tsel = (ITreeSelection) sel;
763         Object elem = tsel.getFirstElement();
764         if (elem == null) {
765             return;
766         }
767 
768         ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
769         Object[] children = provider.getElements(elem);
770         if (children == null) {
771             return;
772         }
773 
774         if (children.length > 0) {
775             // If the element has children, expand/collapse it.
776             if (mTreeViewer.getExpandedState(elem)) {
777                 mTreeViewer.collapseToLevel(elem, 1);
778             } else {
779                 mTreeViewer.expandToLevel(elem, 1);
780             }
781         } else {
782             // If the element is a terminal one, select/deselect it.
783             checkAndExpandItem(
784                     elem,
785                     !mTreeViewer.getChecked(elem),
786                     false /*fixChildren*/,
787                     true /*fixParent*/);
788             updateButtonsState();
789         }
790     }
791 
checkAndExpandItem( Object elem, boolean checked, boolean fixChildren, boolean fixParent)792     private void checkAndExpandItem(
793             Object elem,
794             boolean checked,
795             boolean fixChildren,
796             boolean fixParent) {
797         ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
798 
799         // fix the item itself
800         if (checked != mTreeViewer.getChecked(elem)) {
801             mTreeViewer.setChecked(elem, checked);
802         }
803         if (elem instanceof PkgItem) {
804             // update the PkgItem to reflect the selection
805             ((PkgItem) elem).setChecked(checked);
806         }
807 
808         if (!checked) {
809             if (fixChildren) {
810                 // when de-selecting, we deselect all children too
811                 mTreeViewer.setSubtreeChecked(elem, checked);
812                 for (Object child : provider.getChildren(elem)) {
813                     checkAndExpandItem(child, checked, fixChildren, false/*fixParent*/);
814                 }
815             }
816 
817             // fix the parent when deselecting
818             if (fixParent) {
819                 Object parent = provider.getParent(elem);
820                 if (parent != null && mTreeViewer.getChecked(parent)) {
821                     mTreeViewer.setChecked(parent, false);
822                 }
823             }
824             return;
825         }
826 
827         // When selecting, we also select sub-items (for a category)
828         if (fixChildren) {
829             if (elem instanceof PkgCategory || elem instanceof PkgItem) {
830                 Object[] children = provider.getChildren(elem);
831                 for (Object child : children) {
832                     checkAndExpandItem(child, true, fixChildren, false/*fixParent*/);
833                 }
834                 // only fix the parent once the last sub-item is set
835                 if (elem instanceof PkgCategory) {
836                     if (children.length > 0) {
837                         checkAndExpandItem(
838                                 children[0], true, false/*fixChildren*/, true/*fixParent*/);
839                     } else {
840                         mTreeViewer.setChecked(elem, false);
841                     }
842                 }
843             } else if (elem instanceof Package) {
844                 // in details mode, we auto-select compatible packages
845                 selectCompatibleArchives(elem, provider);
846             }
847         }
848 
849         if (fixParent && checked && elem instanceof PkgItem) {
850             Object parent = provider.getParent(elem);
851             if (!mTreeViewer.getChecked(parent)) {
852                 Object[] children = provider.getChildren(parent);
853                 boolean allChecked = children.length > 0;
854                 for (Object e : children) {
855                     if (!mTreeViewer.getChecked(e)) {
856                         allChecked = false;
857                         break;
858                     }
859                 }
860                 if (allChecked) {
861                     mTreeViewer.setChecked(parent, true);
862                 }
863             }
864         }
865     }
866 
selectCompatibleArchives(Object pkg, ITreeContentProvider provider)867     private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
868         for (Object archive : provider.getChildren(pkg)) {
869             if (archive instanceof Archive) {
870                 mTreeViewer.setChecked(archive, ((Archive) archive).isCompatible());
871             }
872         }
873     }
874 
875     /**
876      * Checks all PkgItems that are either new or have updates.
877      */
onSelectNewUpdates(boolean selectNew, boolean selectUpdates)878     private void onSelectNewUpdates(boolean selectNew, boolean selectUpdates) {
879         // This does not update the tree itself, syncViewerSelection does it below.
880         mDiffLogic.checkNewUpdateItems(selectNew, selectUpdates);
881         syncViewerSelection();
882         updateButtonsState();
883     }
884 
885     /**
886      * Deselect all checked PkgItems.
887      */
onDeselectAll()888     private void onDeselectAll() {
889         // This does not update the tree itself, syncViewerSelection does it below.
890         mDiffLogic.uncheckAllItems();
891         syncViewerSelection();
892         updateButtonsState();
893     }
894 
895     /**
896      * When switching between the tree-by-api and the tree-by-source, copy the selection
897      * (aka the checked items) from one list to the other.
898      * This does not update the tree itself.
899      */
copySelection(boolean fromSourceToApi)900     private void copySelection(boolean fromSourceToApi) {
901         List<PkgItem> fromItems = mDiffLogic.getAllPkgItems(!fromSourceToApi, fromSourceToApi);
902         List<PkgItem> toItems = mDiffLogic.getAllPkgItems(fromSourceToApi, !fromSourceToApi);
903 
904         // deselect all targets
905         for (PkgItem item : toItems) {
906             item.setChecked(false);
907         }
908 
909         // mark new one from the source
910         for (PkgItem source : fromItems) {
911             if (source.isChecked()) {
912                 // There should typically be a corresponding item in the target side
913                 for (PkgItem target : toItems) {
914                     if (target.isSameMainPackageAs(source.getMainPackage())) {
915                         target.setChecked(true);
916                         break;
917                     }
918                 }
919             }
920         }
921     }
922 
923     /**
924      * Synchronize the 'checked' state of PkgItems in the tree with their internal isChecked state.
925      */
syncViewerSelection()926     private void syncViewerSelection() {
927         ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
928 
929         for (Object cat : provider.getElements(mTreeViewer.getInput())) {
930             Object[] children = provider.getElements(cat);
931             boolean allChecked = children.length > 0;
932             for (Object child : children) {
933                 if (child instanceof PkgItem) {
934                     PkgItem item = (PkgItem) child;
935                     boolean checked = item.isChecked();
936                     allChecked &= checked;
937 
938                     if (checked != mTreeViewer.getChecked(item)) {
939                         if (checked) {
940                             if (!mTreeViewer.getExpandedState(cat)) {
941                                 mTreeViewer.setExpandedState(cat, true);
942                             }
943                         }
944                         checkAndExpandItem(item, checked, true/*fixChildren*/, false/*fixParent*/);
945                     }
946                 }
947             }
948 
949             if (allChecked != mTreeViewer.getChecked(cat)) {
950                 mTreeViewer.setChecked(cat, allChecked);
951             }
952         }
953     }
954 
955     /**
956      * Indicate an install/delete operation is pending.
957      * This disables the install/delete buttons.
958      * Use {@link #endOperationPending()} to revert, typically in a {@code try..finally} block.
959      */
beginOperationPending()960     private void beginOperationPending() {
961         mOperationPending = true;
962         updateButtonsState();
963     }
964 
endOperationPending()965     private void endOperationPending() {
966         mOperationPending = false;
967         updateButtonsState();
968     }
969 
updateButtonsState()970     private void updateButtonsState() {
971         boolean canInstall = false;
972         int numPackages = 0;
973 
974         if (mDisplayArchives) {
975             // In detail mode, we display archives so we can install if at
976             // least one archive is selected.
977             // Note that in this mode we allow the user to install an archive
978             // even if it's not "compatible" with the current platform.
979 
980             Object[] checked = mTreeViewer.getCheckedElements();
981             if (checked != null) {
982                 for (Object c : checked) {
983                     if (c instanceof Archive) {
984                         Archive a = (Archive) c;
985                         if (!a.isLocal()) {
986                             canInstall = true;
987                             numPackages++;
988                         }
989                     }
990                 }
991             }
992         } else {
993             // In non-detail mode, we need to check if there are any packages
994             // or pkgitems selected with at least one compatible archive to be
995             // installed.
996 
997             Object[] checked = mTreeViewer.getCheckedElements();
998             if (checked != null) {
999                 for (Object c : checked) {
1000                     Package p = null;
1001 
1002                     if (c instanceof Package) {
1003                         p = (Package) c;
1004                     } else if (c instanceof PkgItem) {
1005                         p = ((PkgItem) c).getMainPackage();
1006                     }
1007                     if (p != null && !p.isLocal() && p.hasCompatibleArchive()) {
1008                         canInstall = true;
1009                         numPackages++;
1010                     }
1011                 }
1012             }
1013         }
1014 
1015         mButtonInstall.setEnabled(canInstall && !mOperationPending);
1016         mButtonInstall.setText(
1017                 numPackages == 0 ? "Install packages..." :
1018                     numPackages == 1 ? "Install 1 package..." :
1019                         String.format("Install %d packages...", numPackages));
1020 
1021         // We can only delete local archives
1022         boolean canDelete = false;
1023         numPackages = 0;
1024 
1025         Object[] checked = mTreeViewer.getCheckedElements();
1026         if (checked != null) {
1027             for (Object c : checked) {
1028                 if (c instanceof PkgItem) {
1029                     PkgState state = ((PkgItem) c).getState();
1030                     if (state == PkgState.INSTALLED) {
1031                         canDelete = true;
1032                         numPackages++;
1033                     }
1034                 }
1035             }
1036         }
1037 
1038         mButtonDelete.setEnabled(canDelete && !mOperationPending);
1039         mButtonDelete.setText(
1040                 numPackages == 0 ? "Delete packages..." :
1041                     numPackages == 1 ? "Delete 1 package..." :
1042                         String.format("Delete %d packages...", numPackages));
1043     }
1044 
onButtonInstall()1045     private void onButtonInstall() {
1046         ArrayList<Archive> archives = new ArrayList<Archive>();
1047 
1048         if (mDisplayArchives) {
1049             // In detail mode, we display archives so we can install only the
1050             // archives that are actually selected.
1051             // Note that in this mode we allow the user to install an archive
1052             // even if it's not "compatible" with the current platform.
1053 
1054             Object[] checked = mTreeViewer.getCheckedElements();
1055             if (checked != null) {
1056                 for (Object c : checked) {
1057                     if (c instanceof Archive) {
1058                         Archive a = (Archive) c;
1059                         if (!a.isLocal()) {
1060                             archives.add((Archive) c);
1061                         }
1062                     }
1063                 }
1064             }
1065         } else {
1066             // In non-detail mode, we install all the compatible archives
1067             // found in the selected pkg items. We also automatically
1068             // select update packages rather than the root package if any.
1069 
1070             Object[] checked = mTreeViewer.getCheckedElements();
1071             if (checked != null) {
1072                 for (Object c : checked) {
1073                     Package p = null;
1074                     if (c instanceof Package) {
1075                         // This is an update package
1076                         p = (Package) c;
1077                     } else if (c instanceof PkgItem) {
1078                         p = ((PkgItem) c).getMainPackage();
1079 
1080                         PkgItem pi = (PkgItem) c;
1081                         if (pi.getState() == PkgState.INSTALLED) {
1082                             Package updPkg = pi.getUpdatePkg();
1083                             if (updPkg != null) {
1084                             // If there's one and only one update, auto-select it instead.
1085                                 p = updPkg;
1086                             }
1087                         }
1088                     }
1089                     if (p != null) {
1090                         for (Archive a : p.getArchives()) {
1091                             if (!a.isLocal() && a.isCompatible()) {
1092                                 archives.add(a);
1093                             }
1094                         }
1095                     }
1096                 }
1097             }
1098         }
1099 
1100         if (mUpdaterData != null) {
1101             boolean needsRefresh = false;
1102             try {
1103                 beginOperationPending();
1104 
1105                 List<Archive> installed = mUpdaterData.updateOrInstallAll_WithGUI(
1106                     archives,
1107                     mCheckFilterObsolete.getSelection() /* includeObsoletes */,
1108                     mContext == SdkInvocationContext.IDE ?
1109                             UpdaterData.TOOLS_MSG_UPDATED_FROM_ADT :
1110                                 UpdaterData.TOOLS_MSG_UPDATED_FROM_SDKMAN);
1111                 needsRefresh = installed != null && !installed.isEmpty();
1112             } finally {
1113                 endOperationPending();
1114 
1115                 if (needsRefresh) {
1116                     // The local package list has changed, make sure to refresh it
1117                     localReload();
1118                 }
1119             }
1120         }
1121     }
1122 
onButtonDelete()1123     private void onButtonDelete() {
1124         // Find selected local packages to be delete
1125         Object[] checked = mTreeViewer.getCheckedElements();
1126         if (checked == null) {
1127             // This should not happen since the button should be disabled
1128             return;
1129         }
1130 
1131         final String title = "Delete SDK Package";
1132         String msg = "Are you sure you want to delete:";
1133 
1134         // A map of archives to deleted versus their internal PkgItem representation
1135         final Map<Archive, PkgItem> archives = new TreeMap<Archive, PkgItem>();
1136 
1137         for (Object c : checked) {
1138             if (c instanceof PkgItem) {
1139                 PkgItem pi = (PkgItem) c;
1140                 PkgState state = pi.getState();
1141                 if (state == PkgState.INSTALLED) {
1142                     Package p = pi.getMainPackage();
1143 
1144                     Archive[] as = p.getArchives();
1145                     if (as.length == 1 && as[0] != null && as[0].isLocal()) {
1146                         Archive archive = as[0];
1147                         String osPath = archive.getLocalOsPath();
1148 
1149                         File dir = new File(osPath);
1150                         if (dir.isDirectory()) {
1151                             msg += "\n - " + p.getShortDescription();
1152                             archives.put(archive, pi);
1153                         }
1154                     }
1155                 }
1156             }
1157         }
1158 
1159         if (!archives.isEmpty()) {
1160             msg += "\n" + "This cannot be undone.";
1161             if (MessageDialog.openQuestion(getShell(), title, msg)) {
1162                 try {
1163                     beginOperationPending();
1164 
1165                     mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
1166                         public void run(ITaskMonitor monitor) {
1167                             monitor.setProgressMax(archives.size() + 1);
1168                             for (Entry<Archive, PkgItem> entry : archives.entrySet()) {
1169                                 Archive a = entry.getKey();
1170 
1171                                 monitor.setDescription("Deleting '%1$s' (%2$s)",
1172                                         a.getParentPackage().getShortDescription(),
1173                                         a.getLocalOsPath());
1174 
1175                                 // Delete the actual package
1176                                 a.deleteLocal();
1177 
1178                                 monitor.incProgress(1);
1179                                 if (monitor.isCancelRequested()) {
1180                                     break;
1181                                 }
1182                             }
1183 
1184                             monitor.incProgress(1);
1185                             monitor.setDescription("Done");
1186                         }
1187                     });
1188                 } finally {
1189                     endOperationPending();
1190 
1191                     // The local package list has changed, make sure to refresh it
1192                     localReload();
1193                 }
1194             }
1195         }
1196     }
1197 
1198     // ----------------------
1199 
1200     /**
1201      * A custom version of {@link TreeColumnViewerLabelProvider} which
1202      * handles {@link TreePath}s and delegates content to a base
1203      * {@link PkgCellLabelProvider} for the given {@link TreeViewerColumn}.
1204      * <p/>
1205      * The implementation handles a variety of providers (table label, table
1206      * color, table font) but does not implement a tooltip provider, so we
1207      * delegate the calls here to the appropriate {@link PkgCellLabelProvider}.
1208      * <p/>
1209      * Only {@link #getToolTipText(Object)} is really useful for us but we
1210      * delegate all the tooltip calls for completeness and avoid surprises later
1211      * if we ever decide to override more things in the label provider.
1212      */
1213     public class PkgTreeColumnViewerLabelProvider extends TreeColumnViewerLabelProvider {
1214 
1215         private CellLabelProvider mTooltipProvider;
1216 
PkgTreeColumnViewerLabelProvider(TreeViewerColumn column)1217         public PkgTreeColumnViewerLabelProvider(TreeViewerColumn column) {
1218             super(new PkgCellLabelProvider(column));
1219         }
1220 
1221         @Override
setProviders(Object provider)1222         public void setProviders(Object provider) {
1223             super.setProviders(provider);
1224             if (provider instanceof CellLabelProvider) {
1225                 mTooltipProvider = (CellLabelProvider) provider;
1226             }
1227         }
1228 
1229         @Override
getToolTipImage(Object object)1230         public Image getToolTipImage(Object object) {
1231             if (mTooltipProvider != null) {
1232                 return mTooltipProvider.getToolTipImage(object);
1233             }
1234             return super.getToolTipImage(object);
1235         }
1236 
1237         @Override
getToolTipText(Object element)1238         public String getToolTipText(Object element) {
1239             if (mTooltipProvider != null) {
1240                 return mTooltipProvider.getToolTipText(element);
1241             }
1242             return super.getToolTipText(element);
1243         }
1244 
1245         @Override
getToolTipBackgroundColor(Object object)1246         public Color getToolTipBackgroundColor(Object object) {
1247             if (mTooltipProvider != null) {
1248                 return mTooltipProvider.getToolTipBackgroundColor(object);
1249             }
1250             return super.getToolTipBackgroundColor(object);
1251         }
1252 
1253         @Override
getToolTipForegroundColor(Object object)1254         public Color getToolTipForegroundColor(Object object) {
1255             if (mTooltipProvider != null) {
1256                 return mTooltipProvider.getToolTipForegroundColor(object);
1257             }
1258             return super.getToolTipForegroundColor(object);
1259         }
1260 
1261         @Override
getToolTipFont(Object object)1262         public Font getToolTipFont(Object object) {
1263             if (mTooltipProvider != null) {
1264                 return mTooltipProvider.getToolTipFont(object);
1265             }
1266             return super.getToolTipFont(object);
1267         }
1268 
1269         @Override
getToolTipShift(Object object)1270         public Point getToolTipShift(Object object) {
1271             if (mTooltipProvider != null) {
1272                 return mTooltipProvider.getToolTipShift(object);
1273             }
1274             return super.getToolTipShift(object);
1275         }
1276 
1277         @Override
useNativeToolTip(Object object)1278         public boolean useNativeToolTip(Object object) {
1279             if (mTooltipProvider != null) {
1280                 return mTooltipProvider.useNativeToolTip(object);
1281             }
1282             return super.useNativeToolTip(object);
1283         }
1284 
1285         @Override
getToolTipTimeDisplayed(Object object)1286         public int getToolTipTimeDisplayed(Object object) {
1287             if (mTooltipProvider != null) {
1288                 return mTooltipProvider.getToolTipTimeDisplayed(object);
1289             }
1290             return super.getToolTipTimeDisplayed(object);
1291         }
1292 
1293         @Override
getToolTipDisplayDelayTime(Object object)1294         public int getToolTipDisplayDelayTime(Object object) {
1295             if (mTooltipProvider != null) {
1296                 return mTooltipProvider.getToolTipDisplayDelayTime(object);
1297             }
1298             return super.getToolTipDisplayDelayTime(object);
1299         }
1300 
1301         @Override
getToolTipStyle(Object object)1302         public int getToolTipStyle(Object object) {
1303             if (mTooltipProvider != null) {
1304                 return mTooltipProvider.getToolTipStyle(object);
1305             }
1306             return super.getToolTipStyle(object);
1307         }
1308     }
1309 
1310     public class PkgCellLabelProvider extends ColumnLabelProvider implements ITableFontProvider {
1311 
1312         private final TreeViewerColumn mColumn;
1313 
PkgCellLabelProvider(TreeViewerColumn column)1314         public PkgCellLabelProvider(TreeViewerColumn column) {
1315             super();
1316             mColumn = column;
1317         }
1318 
1319         @Override
getText(Object element)1320         public String getText(Object element) {
1321 
1322             if (mColumn == mColumnName) {
1323 
1324                 if (element instanceof PkgCategory) {
1325                     return ((PkgCategory) element).getLabel();
1326                 } else if (element instanceof PkgItem) {
1327                     return getPkgItemName((PkgItem) element);
1328                 } else if (element instanceof IDescription) {
1329                     return ((IDescription) element).getShortDescription();
1330                 }
1331 
1332             } else if (mColumn == mColumnApi) {
1333 
1334                 int api = -1;
1335                 if (element instanceof PkgItem) {
1336                     api = ((PkgItem) element).getApi();
1337                 }
1338                 if (api >= 1) {
1339                     return Integer.toString(api);
1340                 }
1341 
1342             } else if (mColumn == mColumnRevision) {
1343 
1344                 if (element instanceof PkgItem) {
1345                     PkgItem pkg = (PkgItem) element;
1346 
1347                     if (pkg.getState() == PkgState.INSTALLED) {
1348                         return Integer.toString(pkg.getRevision());
1349                     }
1350                 }
1351 
1352             } else if (mColumn == mColumnStatus) {
1353 
1354                 if (element instanceof PkgItem) {
1355                     PkgItem pkg = (PkgItem) element;
1356 
1357                     switch(pkg.getState()) {
1358                     case INSTALLED:
1359                         Package update = pkg.getUpdatePkg();
1360                         if (update != null) {
1361                             return String.format(
1362                                     "Update available: rev. %1$s",
1363                                     update.getRevision());
1364                         }
1365                         return "Installed";
1366 
1367                     case NEW:
1368                         return "Not installed";
1369                     }
1370                     return pkg.getState().toString();
1371 
1372                 } else if (element instanceof Package) {
1373                     // This is an update package.
1374                     return "New revision " + Integer.toString(((Package) element).getRevision());
1375                 }
1376             }
1377 
1378             return "";
1379         }
1380 
getPkgItemName(PkgItem item)1381         private String getPkgItemName(PkgItem item) {
1382             String name = item.getName().trim();
1383 
1384             if (isSortByApi()) {
1385                 // When sorting by API, the package name might contains the API number
1386                 // or the platform name at the end. If we find it, cut it out since it's
1387                 // redundant.
1388 
1389                 PkgCategoryApi cat = (PkgCategoryApi) findCategoryForItem(item);
1390                 String apiLabel = cat.getApiLabel();
1391                 String platLabel = cat.getPlatformName();
1392 
1393                 if (platLabel != null && name.endsWith(platLabel)) {
1394                     return name.substring(0, name.length() - platLabel.length());
1395 
1396                 } else if (apiLabel != null && name.endsWith(apiLabel)) {
1397                     return name.substring(0, name.length() - apiLabel.length());
1398 
1399                 } else if (platLabel != null && item.isObsolete() && name.indexOf(platLabel) > 0) {
1400                     // For obsolete items, the format is "<base name> <platform name> (Obsolete)"
1401                     // so in this case only accept removing a platform name that is not at
1402                     // the end.
1403                     name = name.replace(platLabel, ""); //$NON-NLS-1$
1404                 }
1405             }
1406 
1407             // Collapse potential duplicated spacing
1408             name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
1409 
1410             return name;
1411         }
1412 
findCategoryForItem(PkgItem item)1413         private PkgCategory findCategoryForItem(PkgItem item) {
1414             List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
1415             for (PkgCategory cat : cats) {
1416                 for (PkgItem i : cat.getItems()) {
1417                     if (i == item) {
1418                         return cat;
1419                     }
1420                 }
1421             }
1422 
1423             return null;
1424         }
1425 
1426         @Override
getImage(Object element)1427         public Image getImage(Object element) {
1428             ImageFactory imgFactory = mUpdaterData.getImageFactory();
1429 
1430             if (imgFactory != null) {
1431                 if (mColumn == mColumnName) {
1432                     if (element instanceof PkgCategory) {
1433                         return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
1434                     } else if (element instanceof PkgItem) {
1435                         return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
1436                     }
1437                     return imgFactory.getImageForObject(element);
1438 
1439                 } else if (mColumn == mColumnStatus && element instanceof PkgItem) {
1440                     PkgItem pi = (PkgItem) element;
1441                     switch(pi.getState()) {
1442                     case INSTALLED:
1443                         if (pi.hasUpdatePkg()) {
1444                             return imgFactory.getImageByName(ICON_PKG_UPDATE);
1445                         } else {
1446                             return imgFactory.getImageByName(ICON_PKG_INSTALLED);
1447                         }
1448                     case NEW:
1449                         return imgFactory.getImageByName(ICON_PKG_NEW);
1450                     }
1451                 }
1452             }
1453             return super.getImage(element);
1454         }
1455 
1456         // -- ITableFontProvider
1457 
getFont(Object element, int columnIndex)1458         public Font getFont(Object element, int columnIndex) {
1459             if (element instanceof PkgItem) {
1460                 if (((PkgItem) element).getState() == PkgState.NEW) {
1461                     return mTreeFontItalic;
1462                 }
1463             } else if (element instanceof Package) {
1464                 // update package
1465                 return mTreeFontItalic;
1466             }
1467             return super.getFont(element);
1468         }
1469 
1470         // -- Tooltip support
1471 
1472         @Override
getToolTipText(Object element)1473         public String getToolTipText(Object element) {
1474             if (element instanceof PkgItem) {
1475                 element = ((PkgItem) element).getMainPackage();
1476             }
1477             if (element instanceof IDescription) {
1478                 return ((IDescription) element).getLongDescription();
1479             }
1480             return super.getToolTipText(element);
1481         }
1482 
1483         @Override
getToolTipShift(Object object)1484         public Point getToolTipShift(Object object) {
1485             return new Point(15, 5);
1486         }
1487 
1488         @Override
getToolTipDisplayDelayTime(Object object)1489         public int getToolTipDisplayDelayTime(Object object) {
1490             return 500;
1491         }
1492     }
1493 
1494     private class PkgContentProvider implements ITreeContentProvider {
1495 
getChildren(Object parentElement)1496         public Object[] getChildren(Object parentElement) {
1497             if (parentElement instanceof ArrayList<?>) {
1498                 return ((ArrayList<?>) parentElement).toArray();
1499 
1500             } else if (parentElement instanceof PkgCategory) {
1501                 return ((PkgCategory) parentElement).getItems().toArray();
1502 
1503             } else if (parentElement instanceof PkgItem) {
1504                 if (mDisplayArchives) {
1505 
1506                     Package pkg = ((PkgItem) parentElement).getUpdatePkg();
1507 
1508                     // Display update packages as sub-items if the details mode is activated.
1509                     if (pkg != null) {
1510                         return new Object[] { pkg };
1511                     }
1512 
1513                     return ((PkgItem) parentElement).getArchives();
1514                 }
1515 
1516             } else if (parentElement instanceof Package) {
1517                 if (mDisplayArchives) {
1518                     return ((Package) parentElement).getArchives();
1519                 }
1520 
1521             }
1522 
1523             return new Object[0];
1524         }
1525 
1526         @SuppressWarnings("unchecked")
getParent(Object element)1527         public Object getParent(Object element) {
1528             // This operation is a tad expensive, so we do the minimum
1529             // and don't try to cover all cases.
1530 
1531             if (element instanceof PkgItem) {
1532                 for (PkgCategory cat : (List<PkgCategory>) mTreeViewer.getInput()) {
1533                     if (cat.getItems().contains(element)) {
1534                         return cat;
1535                     }
1536                 }
1537             }
1538 
1539             return null;
1540         }
1541 
hasChildren(Object parentElement)1542         public boolean hasChildren(Object parentElement) {
1543             if (parentElement instanceof ArrayList<?>) {
1544                 return true;
1545 
1546             } else if (parentElement instanceof PkgCategory) {
1547                 return true;
1548 
1549             } else if (parentElement instanceof PkgItem) {
1550                 if (mDisplayArchives) {
1551                     Package pkg = ((PkgItem) parentElement).getUpdatePkg();
1552 
1553                     // Display update packages as sub-items if the details mode is activated.
1554                     if (pkg != null) {
1555                         return true;
1556                     }
1557 
1558                     Archive[] archives = ((PkgItem) parentElement).getArchives();
1559                     return archives.length > 0;
1560                 }
1561             } else if (parentElement instanceof Package) {
1562                 if (mDisplayArchives) {
1563                     return ((Package) parentElement).getArchives().length > 0;
1564                 }
1565             }
1566 
1567             return false;
1568         }
1569 
getElements(Object inputElement)1570         public Object[] getElements(Object inputElement) {
1571             return getChildren(inputElement);
1572         }
1573 
dispose()1574         public void dispose() {
1575             // unused
1576 
1577         }
1578 
inputChanged(Viewer arg0, Object arg1, Object arg2)1579         public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
1580             // unused
1581         }
1582     }
1583 
1584     // --- Implementation of ISdkChangeListener ---
1585 
onSdkLoaded()1586     public void onSdkLoaded() {
1587         onSdkReload();
1588     }
1589 
onSdkReload()1590     public void onSdkReload() {
1591         // The sdkmanager finished reloading its data. We must not call localReload() from here
1592         // since we don't want to alter the sdkmanager's data that just finished loading.
1593         loadPackages();
1594     }
1595 
preInstallHook()1596     public void preInstallHook() {
1597         // nothing to be done for now.
1598     }
1599 
postInstallHook()1600     public void postInstallHook() {
1601         // nothing to be done for now.
1602     }
1603 
1604 
1605     // --- End of hiding from SWT Designer ---
1606     //$hide<<$
1607 }
1608