2 * Copyright (C) 2011 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.sdkuilib.internal.repository.sdkman2;
19 import com.android.sdklib.SdkConstants;
20 import com.android.sdklib.internal.repository.Archive;
21 import com.android.sdklib.internal.repository.IDescription;
22 import com.android.sdklib.internal.repository.ITask;
23 import com.android.sdklib.internal.repository.ITaskMonitor;
24 import com.android.sdklib.internal.repository.Package;
25 import com.android.sdklib.internal.repository.SdkSource;
26 import com.android.sdkuilib.internal.repository.IPageListener;
27 import com.android.sdkuilib.internal.repository.UpdaterData;
28 import com.android.sdkuilib.internal.repository.UpdaterPage;
29 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
30 import com.android.sdkuilib.internal.repository.sdkman2.PackageLoader.ISourceLoadedCallback;
31 import com.android.sdkuilib.internal.repository.sdkman2.PkgItem.PkgState;
32 import com.android.sdkuilib.repository.ISdkChangeListener;
33 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
34 import com.android.sdkuilib.ui.GridDataBuilder;
35 import com.android.sdkuilib.ui.GridLayoutBuilder;
37 import org.eclipse.jface.dialogs.MessageDialog;
38 import org.eclipse.jface.viewers.CellLabelProvider;
39 import org.eclipse.jface.viewers.CheckStateChangedEvent;
40 import org.eclipse.jface.viewers.CheckboxTreeViewer;
41 import org.eclipse.jface.viewers.ColumnLabelProvider;
42 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
43 import org.eclipse.jface.viewers.DoubleClickEvent;
44 import org.eclipse.jface.viewers.ICheckStateListener;
45 import org.eclipse.jface.viewers.IDoubleClickListener;
46 import org.eclipse.jface.viewers.ISelection;
47 import org.eclipse.jface.viewers.ITableFontProvider;
48 import org.eclipse.jface.viewers.ITreeContentProvider;
49 import org.eclipse.jface.viewers.ITreeSelection;
50 import org.eclipse.jface.viewers.TreeColumnViewerLabelProvider;
51 import org.eclipse.jface.viewers.TreePath;
52 import org.eclipse.jface.viewers.TreeViewerColumn;
53 import org.eclipse.jface.viewers.Viewer;
54 import org.eclipse.jface.viewers.ViewerFilter;
55 import org.eclipse.jface.window.ToolTip;
56 import org.eclipse.swt.SWT;
57 import org.eclipse.swt.events.DisposeEvent;
58 import org.eclipse.swt.events.DisposeListener;
59 import org.eclipse.swt.events.SelectionAdapter;
60 import org.eclipse.swt.events.SelectionEvent;
61 import org.eclipse.swt.graphics.Color;
62 import org.eclipse.swt.graphics.Font;
63 import org.eclipse.swt.graphics.FontData;
64 import org.eclipse.swt.graphics.Image;
65 import org.eclipse.swt.graphics.Point;
66 import org.eclipse.swt.widgets.Button;
67 import org.eclipse.swt.widgets.Composite;
68 import org.eclipse.swt.widgets.Control;
69 import org.eclipse.swt.widgets.Event;
70 import org.eclipse.swt.widgets.Group;
71 import org.eclipse.swt.widgets.Label;
72 import org.eclipse.swt.widgets.Link;
73 import org.eclipse.swt.widgets.MenuItem;
74 import org.eclipse.swt.widgets.Text;
75 import org.eclipse.swt.widgets.Tree;
76 import org.eclipse.swt.widgets.TreeColumn;
79 import java.net.MalformedURLException;
81 import java.util.ArrayList;
82 import java.util.HashMap;
83 import java.util.List;
85 import java.util.Map.Entry;
88 * Page that displays both locally installed packages as well as all known
89 * remote available packages. This gives an overview of what is installed
90 * vs what is available and allows the user to update or install packages.
92 public class PackagesPage extends UpdaterPage
93 implements ISdkChangeListener, IPageListener {
95 static final String ICON_CAT_OTHER = "pkgcat_other_16.png"; //$NON-NLS-1$
96 static final String ICON_CAT_PLATFORM = "pkgcat_16.png"; //$NON-NLS-1$
97 static final String ICON_SORT_BY_SOURCE = "source_icon16.png"; //$NON-NLS-1$
98 static final String ICON_SORT_BY_API = "platform_pkg_16.png"; //$NON-NLS-1$
99 static final String ICON_PKG_NEW = "pkg_new_16.png"; //$NON-NLS-1$
100 static final String ICON_PKG_UPDATE = "pkg_update_16.png"; //$NON-NLS-1$
101 static final String ICON_PKG_INSTALLED = "pkg_installed_16.png"; //$NON-NLS-1$
104 RELOAD (SWT.NONE, "Reload"),
105 SHOW_ADDON_SITES (SWT.NONE, "Manage Add-on Sites..."),
106 TOGGLE_SHOW_ARCHIVES (SWT.CHECK, "Show Archives Details"),
107 TOGGLE_SHOW_INSTALLED_PKG (SWT.CHECK, "Show Installed Packages"),
108 TOGGLE_SHOW_OBSOLETE_PKG (SWT.CHECK, "Show Obsolete Packages"),
109 TOGGLE_SHOW_UPDATE_NEW_PKG (SWT.CHECK, "Show Updates/New Packages"),
110 SORT_API_LEVEL (SWT.RADIO, "Sort by API Level"),
111 SORT_SOURCE (SWT.RADIO, "Sort by Repository")
114 private final int mMenuStyle;
115 private final String mMenuTitle;
117 MenuAction(int menuStyle, String menuTitle) {
118 mMenuStyle = menuStyle;
119 mMenuTitle = menuTitle;
122 public int getMenuStyle() {
126 public String getMenuTitle() {
131 private final Map<MenuAction, MenuItem> mMenuActions = new HashMap<MenuAction, MenuItem>();
133 private final SdkInvocationContext mContext;
134 private final UpdaterData mUpdaterData;
135 private final PackagesDiffLogic mDiffLogic;
136 private boolean mDisplayArchives = false;
137 private boolean mOperationPending;
139 private Text mTextSdkOsPath;
140 private Button mCheckSortSource;
141 private Button mCheckSortApi;
142 private Button mCheckFilterObsolete;
143 private Button mCheckFilterInstalled;
144 private Button mCheckFilterNew;
145 private Composite mGroupOptions;
146 private Composite mGroupSdk;
147 private Group mGroupPackages;
148 private Button mButtonDelete;
149 private Button mButtonInstall;
151 private CheckboxTreeViewer mTreeViewer;
152 private TreeViewerColumn mColumnName;
153 private TreeViewerColumn mColumnApi;
154 private TreeViewerColumn mColumnRevision;
155 private TreeViewerColumn mColumnStatus;
156 private Font mTreeFontItalic;
157 private TreeColumn mTreeColumnName;
162 UpdaterData updaterData,
163 SdkInvocationContext context) {
164 super(parent, swtStyle);
165 mUpdaterData = updaterData;
168 mDiffLogic = new PackagesDiffLogic(updaterData);
170 createContents(this);
171 postCreate(); //$hide$
174 public void onPageSelected() {
175 List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
176 if (cats == null || cats.isEmpty()) {
177 // Initialize the package list the first time the page is shown.
182 @SuppressWarnings("unused")
183 private void createContents(Composite parent) {
184 GridLayoutBuilder.create(parent).noMargins().columns(2);
186 mGroupSdk = new Composite(parent, SWT.NONE);
187 GridDataBuilder.create(mGroupSdk).hFill().vCenter().hGrab().hSpan(2);
188 GridLayoutBuilder.create(mGroupSdk).columns(2);
190 Label label1 = new Label(mGroupSdk, SWT.NONE);
191 label1.setText("SDK Path:");
193 mTextSdkOsPath = new Text(mGroupSdk, SWT.NONE);
194 GridDataBuilder.create(mTextSdkOsPath).hFill().vCenter().hGrab();
195 mTextSdkOsPath.setEnabled(false);
197 mGroupPackages = new Group(parent, SWT.NONE);
198 GridDataBuilder.create(mGroupPackages).fill().grab().hSpan(2);
199 mGroupPackages.setText("Packages");
200 GridLayoutBuilder.create(mGroupPackages).columns(1);
202 mTreeViewer = new CheckboxTreeViewer(mGroupPackages, SWT.BORDER);
203 mTreeViewer.addFilter(new ViewerFilter() {
205 public boolean select(Viewer viewer, Object parentElement, Object element) {
206 return filterViewerItem(element);
210 mTreeViewer.addCheckStateListener(new ICheckStateListener() {
211 public void checkStateChanged(CheckStateChangedEvent event) {
212 onTreeCheckStateChanged(event); //$hide$
216 mTreeViewer.addDoubleClickListener(new IDoubleClickListener() {
217 public void doubleClick(DoubleClickEvent event) {
218 onTreeDoubleClick(event); //$hide$
222 mTree = mTreeViewer.getTree();
223 mTree.setLinesVisible(true);
224 mTree.setHeaderVisible(true);
225 GridDataBuilder.create(mTree).fill().grab();
227 // column name icon is set when loading depending on the current filter type
228 // (e.g. API level or source)
229 mColumnName = new TreeViewerColumn(mTreeViewer, SWT.NONE);
230 mTreeColumnName = mColumnName.getColumn();
231 mTreeColumnName.setText("Name");
232 mTreeColumnName.setWidth(340);
234 mColumnApi = new TreeViewerColumn(mTreeViewer, SWT.NONE);
235 TreeColumn treeColumn2 = mColumnApi.getColumn();
236 treeColumn2.setText("API");
237 treeColumn2.setAlignment(SWT.CENTER);
238 treeColumn2.setWidth(50);
240 mColumnRevision = new TreeViewerColumn(mTreeViewer, SWT.NONE);
241 TreeColumn treeColumn3 = mColumnRevision.getColumn();
242 treeColumn3.setText("Rev.");
243 treeColumn3.setToolTipText("Revision currently installed");
244 treeColumn3.setAlignment(SWT.CENTER);
245 treeColumn3.setWidth(50);
248 mColumnStatus = new TreeViewerColumn(mTreeViewer, SWT.NONE);
249 TreeColumn treeColumn4 = mColumnStatus.getColumn();
250 treeColumn4.setText("Status");
251 treeColumn4.setAlignment(SWT.LEAD);
252 treeColumn4.setWidth(190);
254 mGroupOptions = new Composite(mGroupPackages, SWT.NONE);
255 GridDataBuilder.create(mGroupOptions).hFill().vCenter().hGrab();
256 GridLayoutBuilder.create(mGroupOptions).columns(6).noMargins();
258 // Options line 1, 6 columns
260 Label label3 = new Label(mGroupOptions, SWT.NONE);
261 label3.setText("Show:");
263 mCheckFilterNew = new Button(mGroupOptions, SWT.CHECK);
264 mCheckFilterNew.setText("Updates/New");
265 mCheckFilterNew.setToolTipText("Show Updates and New");
266 mCheckFilterNew.addSelectionListener(new SelectionAdapter() {
268 public void widgetSelected(SelectionEvent e) {
269 refreshViewerInput();
272 mCheckFilterNew.setSelection(true);
274 mCheckFilterInstalled = new Button(mGroupOptions, SWT.CHECK);
275 mCheckFilterInstalled.setToolTipText("Show Installed");
276 mCheckFilterInstalled.addSelectionListener(new SelectionAdapter() {
278 public void widgetSelected(SelectionEvent e) {
279 refreshViewerInput();
282 mCheckFilterInstalled.setSelection(true);
283 mCheckFilterInstalled.setText("Installed");
285 mCheckFilterObsolete = new Button(mGroupOptions, SWT.CHECK);
286 mCheckFilterObsolete.setText("Obsolete");
287 mCheckFilterObsolete.setToolTipText("Also show obsolete packages");
288 mCheckFilterObsolete.addSelectionListener(new SelectionAdapter() {
290 public void widgetSelected(SelectionEvent e) {
291 refreshViewerInput();
294 mCheckFilterObsolete.setSelection(false);
296 Link linkSelectNew = new Link(mGroupOptions, SWT.NONE);
297 // Note for i18n: we need to identify which link is used, and this is done by using the
298 // text itself so for translation purposes we want to keep the <a> link strings separate.
299 final String strLinkNew = "New";
300 final String strLinkUpdates = "Updates";
301 linkSelectNew.setText(
302 String.format("Select <a>%1$s</a> or <a>%2$s</a>", strLinkNew, strLinkUpdates));
303 linkSelectNew.setToolTipText("Selects all items that are either new or updates.");
304 GridDataBuilder.create(linkSelectNew).hFill().hGrab();
305 linkSelectNew.addSelectionListener(new SelectionAdapter() {
307 public void widgetSelected(SelectionEvent e) {
308 super.widgetSelected(e);
309 boolean selectNew = e.text == null || e.text.equals(strLinkNew);
310 onSelectNewUpdates(selectNew, !selectNew, false/*selectTop*/);
314 mButtonInstall = new Button(mGroupOptions, SWT.NONE);
315 mButtonInstall.setText(""); //$NON-NLS-1$ placeholder, filled in updateButtonsState()
316 mButtonInstall.setToolTipText("Install one or more packages");
317 GridDataBuilder.create(mButtonInstall).hFill().vCenter().hGrab();
318 mButtonInstall.addSelectionListener(new SelectionAdapter() {
320 public void widgetSelected(SelectionEvent e) {
321 onButtonInstall(); //$hide$
325 // Options line 2, 6 columns
327 Label label2 = new Label(mGroupOptions, SWT.NONE);
328 label2.setText("Sort by:");
330 mCheckSortApi = new Button(mGroupOptions, SWT.RADIO);
331 mCheckSortApi.setToolTipText("Sort by API level");
332 mCheckSortApi.addSelectionListener(new SelectionAdapter() {
334 public void widgetSelected(SelectionEvent e) {
335 if (mCheckSortApi.getSelection()) {
336 refreshViewerInput();
337 copySelection(true /*toApi*/);
338 syncViewerSelection();
342 mCheckSortApi.setText("API level");
343 mCheckSortApi.setSelection(true);
345 mCheckSortSource = new Button(mGroupOptions, SWT.RADIO);
346 mCheckSortSource.setText("Repository");
347 mCheckSortSource.setToolTipText("Sort by Repository");
348 mCheckSortSource.addSelectionListener(new SelectionAdapter() {
350 public void widgetSelected(SelectionEvent e) {
351 if (mCheckSortSource.getSelection()) {
352 refreshViewerInput();
353 copySelection(false /*toApi*/);
354 syncViewerSelection();
359 new Label(mGroupOptions, SWT.NONE);
361 Link linkDeselect = new Link(mGroupOptions, SWT.NONE);
362 linkDeselect.setText("<a>Deselect All</a>");
363 linkDeselect.setToolTipText("Deselects all the currently selected items");
364 GridDataBuilder.create(linkDeselect).hFill().hGrab();
365 linkDeselect.addSelectionListener(new SelectionAdapter() {
367 public void widgetSelected(SelectionEvent e) {
368 super.widgetSelected(e);
373 mButtonDelete = new Button(mGroupOptions, SWT.NONE);
374 mButtonDelete.setText(""); //$NON-NLS-1$ placeholder, filled in updateButtonsState()
375 mButtonDelete.setToolTipText("Delete one ore more installed packages");
376 GridDataBuilder.create(mButtonDelete).hFill().vCenter().hGrab();
377 mButtonDelete.addSelectionListener(new SelectionAdapter() {
379 public void widgetSelected(SelectionEvent e) {
380 onButtonDelete(); //$hide$
385 private Image getImage(String filename) {
386 if (mUpdaterData != null) {
387 ImageFactory imgFactory = mUpdaterData.getImageFactory();
388 if (imgFactory != null) {
389 return imgFactory.getImageByName(filename);
396 // -- Start of internal part ----------
397 // Hide everything down-below from SWT designer
401 // --- menu interactions ---
403 public void registerMenuAction(final MenuAction action, MenuItem item) {
404 item.addSelectionListener(new SelectionAdapter() {
406 public void widgetSelected(SelectionEvent e) {
407 Button button = null;
413 case SHOW_ADDON_SITES:
414 AddonSitesDialog d = new AddonSitesDialog(getShell(), mUpdaterData);
419 case TOGGLE_SHOW_ARCHIVES:
420 mDisplayArchives = !mDisplayArchives;
421 // Force the viewer to be refreshed
422 mTreeViewer.setInput(null);
423 refreshViewerInput();
424 syncViewerSelection();
425 updateButtonsState();
427 case TOGGLE_SHOW_INSTALLED_PKG:
428 button = mCheckFilterInstalled;
430 case TOGGLE_SHOW_OBSOLETE_PKG:
431 button = mCheckFilterObsolete;
433 case TOGGLE_SHOW_UPDATE_NEW_PKG:
434 button = mCheckFilterNew;
437 button = mCheckSortApi;
440 button = mCheckSortSource;
444 if (button != null && !button.isDisposed()) {
445 // Toggle this button (radio or checkbox)
447 boolean value = button.getSelection();
449 // SWT doesn't automatically switch radio buttons when using the
450 // Widget#setSelection method, so we'll do it here manually.
451 if (!value && (button.getStyle() & SWT.RADIO) != 0) {
452 // we'll be selecting this radio button, so deselect all ther other ones
453 // in the parent group.
454 for (Control child : button.getParent().getChildren()) {
455 if (child instanceof Button &&
457 (child.getStyle() & SWT.RADIO) != 0) {
458 ((Button) child).setSelection(value);
463 button.setSelection(!value);
465 // SWT doesn't actually invoke the listeners when using Widget#setSelection
466 // so let's run the actual action.
467 button.notifyListeners(SWT.Selection, new Event());
470 updateMenuCheckmarks();
474 mMenuActions.put(action, item);
477 // --- internal methods ---
479 private void updateMenuCheckmarks() {
481 for (Entry<MenuAction, MenuItem> entry : mMenuActions.entrySet()) {
482 MenuAction action = entry.getKey();
483 MenuItem item = entry.getValue();
485 if (action.getMenuStyle() == SWT.NONE) {
489 boolean value = false;
490 Button button = null;
493 case TOGGLE_SHOW_ARCHIVES:
494 value = mDisplayArchives;
496 case TOGGLE_SHOW_INSTALLED_PKG:
497 button = mCheckFilterInstalled;
499 case TOGGLE_SHOW_OBSOLETE_PKG:
500 button = mCheckFilterObsolete;
502 case TOGGLE_SHOW_UPDATE_NEW_PKG:
503 button = mCheckFilterNew;
506 button = mCheckSortApi;
509 button = mCheckSortSource;
513 if (button != null && !button.isDisposed()) {
514 value = button.getSelection();
517 item.setSelection(value);
522 private void postCreate() {
523 if (mUpdaterData != null) {
524 mTextSdkOsPath.setText(mUpdaterData.getOsSdkRoot());
527 mTreeViewer.setContentProvider(new PkgContentProvider());
528 ColumnViewerToolTipSupport.enableFor(mTreeViewer, ToolTip.NO_RECREATE);
530 mColumnApi.setLabelProvider (new PkgTreeColumnViewerLabelProvider(mColumnApi));
531 mColumnName.setLabelProvider (new PkgTreeColumnViewerLabelProvider(mColumnName));
532 mColumnStatus.setLabelProvider (new PkgTreeColumnViewerLabelProvider(mColumnStatus));
533 mColumnRevision.setLabelProvider(new PkgTreeColumnViewerLabelProvider(mColumnRevision));
535 FontData fontData = mTree.getFont().getFontData()[0];
536 fontData.setStyle(SWT.ITALIC);
537 mTreeFontItalic = new Font(mTree.getDisplay(), fontData);
539 mTree.addDisposeListener(new DisposeListener() {
540 public void widgetDisposed(DisposeEvent e) {
541 mTreeFontItalic.dispose();
542 mTreeFontItalic = null;
548 * Performs a full reload by removing all cached packages data, including the platforms
549 * and addons from the sdkmanager instance. This will perform a full local parsing
550 * as well as a full reload of the remote data (by fetching all sources again.)
552 private void fullReload() {
553 // Clear all source information, forcing them to be refreshed.
554 mUpdaterData.getSources().clearAllPackages();
555 // Clear and reload all local data too.
560 * Performs a full reload of all the local package information, including the platforms
561 * and addons from the sdkmanager instance. This will perform a full local parsing.
563 * This method does NOT force a new fetch of the remote sources.
567 private void localReload() {
568 // Clear all source caches, otherwise loading will use the cached data
569 mUpdaterData.getLocalSdkParser().clearPackages();
570 mUpdaterData.getSdkManager().reloadSdk(mUpdaterData.getSdkLog());
574 private void loadPackages() {
575 if (mUpdaterData == null) {
579 // LoadPackage is synchronous but does not block the UI.
580 // Consequently it's entirely possible for the user
581 // to request the app to close whilst the packages are loading. Any
582 // action done after loadPackages must check the UI hasn't been
583 // disposed yet. Otherwise hilarity ensues.
585 final boolean displaySortByApi = isSortByApi();
587 if (!mTreeColumnName.isDisposed()) {
588 mTreeColumnName.setImage(
589 getImage(displaySortByApi ? ICON_SORT_BY_API : ICON_SORT_BY_SOURCE));
592 mDiffLogic.updateStart();
593 mDiffLogic.getPackageLoader().loadPackages(new ISourceLoadedCallback() {
594 public boolean onUpdateSource(SdkSource source, Package[] newPackages) {
595 // This runs in a thread and must not access UI directly.
596 final boolean changed = mDiffLogic.updateSourcePackages(
597 displaySortByApi, source, newPackages);
599 if (!mGroupPackages.isDisposed()) {
600 mGroupPackages.getDisplay().syncExec(new Runnable() {
603 mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
604 refreshViewerInput();
610 // Return true to tell the loader to continue with the next source.
611 // Return false to stop the loader if any UI has been disposed, which can
612 // happen if the user is trying to close the window during the load operation.
613 return !mGroupPackages.isDisposed();
616 public void onLoadCompleted() {
617 // This runs in a thread and must not access UI directly.
618 final boolean changed = mDiffLogic.updateEnd(displaySortByApi);
620 if (!mGroupPackages.isDisposed()) {
621 mGroupPackages.getDisplay().syncExec(new Runnable() {
624 mTreeViewer.getInput() != mDiffLogic.getCategories(isSortByApi())) {
625 refreshViewerInput();
628 if (mDiffLogic.isFirstLoadComplete() && !mGroupPackages.isDisposed()) {
629 // At the end of the first load, if nothing is selected then
630 // automatically select all new and update packages.
631 Object[] checked = mTreeViewer.getCheckedElements();
632 if (checked == null || checked.length == 0) {
635 true, //selectUpdates,
646 private void refreshViewerInput() {
647 // Dynamically update the table while we load after each source.
648 // Since the official Android source gets loaded first, it makes the
649 // window look non-empty a lot sooner.
650 if (!mGroupPackages.isDisposed()) {
652 List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
653 if (mTreeViewer.getInput() != cats) {
655 mTreeViewer.setInput(cats);
657 // refresh existing, which preserves the expanded state, the selection
658 // and the checked state.
659 mTreeViewer.refresh();
662 // set the initial expanded state
663 expandInitial(mTreeViewer.getInput());
665 updateButtonsState();
666 updateMenuCheckmarks();
670 private boolean isSortByApi() {
671 return mCheckSortApi != null && !mCheckSortApi.isDisposed() && mCheckSortApi.getSelection();
675 * Decide whether to keep an item in the current tree based on user-chosen filter options.
677 private boolean filterViewerItem(Object treeElement) {
678 if (treeElement instanceof PkgCategory) {
679 PkgCategory cat = (PkgCategory) treeElement;
681 if (!cat.getItems().isEmpty()) {
682 // A category is hidden if all of its content is hidden.
683 // However empty categories are always visible.
684 for (PkgItem item : cat.getItems()) {
685 if (filterViewerItem(item)) {
686 // We found at least one element that is visible.
694 if (treeElement instanceof PkgItem) {
695 PkgItem item = (PkgItem) treeElement;
697 if (!mCheckFilterObsolete.getSelection()) {
698 if (item.isObsolete()) {
703 if (!mCheckFilterInstalled.getSelection()) {
704 if (item.getState() == PkgState.INSTALLED) {
709 if (!mCheckFilterNew.getSelection()) {
710 if (item.getState() == PkgState.NEW || item.hasUpdatePkg()) {
720 * Performs the initial expansion of the tree. This expands categories that contain
721 * at least one installed item and collapses the ones with nothing installed.
723 * TODO: change this to only change the expanded state on categories that have not
724 * been touched by the user yet. Once we do that, call this every time a new source
725 * is added or the list is reloaded.
727 private void expandInitial(Object elem) {
731 if (mTreeViewer != null && !mTreeViewer.getTree().isDisposed()) {
732 mTreeViewer.setExpandedState(elem, true);
734 ((ITreeContentProvider) mTreeViewer.getContentProvider()).getChildren(elem)) {
735 if (pkg instanceof PkgCategory) {
736 PkgCategory cat = (PkgCategory) pkg;
737 for (PkgItem item : cat.getItems()) {
738 if (item.getState() == PkgState.INSTALLED) {
749 * Handle checking and unchecking of the tree items.
751 * When unchecking, all sub-tree items checkboxes are cleared too.
752 * When checking a source, all of its packages are checked too.
753 * When checking a package, only its compatible archives are checked.
755 private void onTreeCheckStateChanged(CheckStateChangedEvent event) {
756 boolean checked = event.getChecked();
757 Object elem = event.getElement();
759 assert event.getSource() == mTreeViewer;
761 // When selecting, we want to only select compatible archives and expand the super nodes.
762 checkAndExpandItem(elem, checked, true/*fixChildren*/, true/*fixParent*/);
763 updateButtonsState();
766 private void onTreeDoubleClick(DoubleClickEvent event) {
767 assert event.getSource() == mTreeViewer;
768 ISelection sel = event.getSelection();
769 if (sel.isEmpty() || !(sel instanceof ITreeSelection)) {
772 ITreeSelection tsel = (ITreeSelection) sel;
773 Object elem = tsel.getFirstElement();
778 ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
779 Object[] children = provider.getElements(elem);
780 if (children == null) {
784 if (children.length > 0) {
785 // If the element has children, expand/collapse it.
786 if (mTreeViewer.getExpandedState(elem)) {
787 mTreeViewer.collapseToLevel(elem, 1);
789 mTreeViewer.expandToLevel(elem, 1);
792 // If the element is a terminal one, select/deselect it.
795 !mTreeViewer.getChecked(elem),
796 false /*fixChildren*/,
798 updateButtonsState();
802 private void checkAndExpandItem(
807 ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
809 // fix the item itself
810 if (checked != mTreeViewer.getChecked(elem)) {
811 mTreeViewer.setChecked(elem, checked);
813 if (elem instanceof PkgItem) {
814 // update the PkgItem to reflect the selection
815 ((PkgItem) elem).setChecked(checked);
820 // when de-selecting, we deselect all children too
821 mTreeViewer.setSubtreeChecked(elem, checked);
822 for (Object child : provider.getChildren(elem)) {
823 checkAndExpandItem(child, checked, fixChildren, false/*fixParent*/);
827 // fix the parent when deselecting
829 Object parent = provider.getParent(elem);
830 if (parent != null && mTreeViewer.getChecked(parent)) {
831 mTreeViewer.setChecked(parent, false);
837 // When selecting, we also select sub-items (for a category)
839 if (elem instanceof PkgCategory || elem instanceof PkgItem) {
840 Object[] children = provider.getChildren(elem);
841 for (Object child : children) {
842 checkAndExpandItem(child, true, fixChildren, false/*fixParent*/);
844 // only fix the parent once the last sub-item is set
845 if (elem instanceof PkgCategory) {
846 if (children.length > 0) {
848 children[0], true, false/*fixChildren*/, true/*fixParent*/);
850 mTreeViewer.setChecked(elem, false);
853 } else if (elem instanceof Package) {
854 // in details mode, we auto-select compatible packages
855 selectCompatibleArchives(elem, provider);
859 if (fixParent && checked && elem instanceof PkgItem) {
860 Object parent = provider.getParent(elem);
861 if (!mTreeViewer.getChecked(parent)) {
862 Object[] children = provider.getChildren(parent);
863 boolean allChecked = children.length > 0;
864 for (Object e : children) {
865 if (!mTreeViewer.getChecked(e)) {
871 mTreeViewer.setChecked(parent, true);
877 private void selectCompatibleArchives(Object pkg, ITreeContentProvider provider) {
878 for (Object archive : provider.getChildren(pkg)) {
879 if (archive instanceof Archive) {
880 mTreeViewer.setChecked(archive, ((Archive) archive).isCompatible());
886 * Checks all PkgItems that are either new or have updates or select top platform
889 private void onSelectNewUpdates(boolean selectNew, boolean selectUpdates, boolean selectTop) {
890 // This does not update the tree itself, syncViewerSelection does it below.
891 mDiffLogic.checkNewUpdateItems(
895 SdkConstants.CURRENT_PLATFORM);
896 syncViewerSelection();
897 updateButtonsState();
901 * Deselect all checked PkgItems.
903 private void onDeselectAll() {
904 // This does not update the tree itself, syncViewerSelection does it below.
905 mDiffLogic.uncheckAllItems();
906 syncViewerSelection();
907 updateButtonsState();
911 * When switching between the tree-by-api and the tree-by-source, copy the selection
912 * (aka the checked items) from one list to the other.
913 * This does not update the tree itself.
915 private void copySelection(boolean fromSourceToApi) {
916 List<PkgItem> fromItems = mDiffLogic.getAllPkgItems(!fromSourceToApi, fromSourceToApi);
917 List<PkgItem> toItems = mDiffLogic.getAllPkgItems(fromSourceToApi, !fromSourceToApi);
919 // deselect all targets
920 for (PkgItem item : toItems) {
921 item.setChecked(false);
924 // mark new one from the source
925 for (PkgItem source : fromItems) {
926 if (source.isChecked()) {
927 // There should typically be a corresponding item in the target side
928 for (PkgItem target : toItems) {
929 if (target.isSameMainPackageAs(source.getMainPackage())) {
930 target.setChecked(true);
939 * Synchronize the 'checked' state of PkgItems in the tree with their internal isChecked state.
941 private void syncViewerSelection() {
942 ITreeContentProvider provider = (ITreeContentProvider) mTreeViewer.getContentProvider();
944 Object input = mTreeViewer.getInput();
948 for (Object cat : provider.getElements(input)) {
949 Object[] children = provider.getElements(cat);
950 boolean allChecked = children.length > 0;
951 for (Object child : children) {
952 if (child instanceof PkgItem) {
953 PkgItem item = (PkgItem) child;
954 boolean checked = item.isChecked();
955 allChecked &= checked;
957 if (checked != mTreeViewer.getChecked(item)) {
959 if (!mTreeViewer.getExpandedState(cat)) {
960 mTreeViewer.setExpandedState(cat, true);
963 checkAndExpandItem(item, checked, true/*fixChildren*/, false/*fixParent*/);
968 if (allChecked != mTreeViewer.getChecked(cat)) {
969 mTreeViewer.setChecked(cat, allChecked);
975 * Indicate an install/delete operation is pending.
976 * This disables the install/delete buttons.
977 * Use {@link #endOperationPending()} to revert, typically in a {@code try..finally} block.
979 private void beginOperationPending() {
980 mOperationPending = true;
981 updateButtonsState();
984 private void endOperationPending() {
985 mOperationPending = false;
986 updateButtonsState();
990 * Updates the Install and Delete Package buttons.
992 private void updateButtonsState() {
993 int numPackages = getArchivesForInstall(null /*archives*/);
995 mButtonInstall.setEnabled((numPackages > 0) && !mOperationPending);
996 mButtonInstall.setText(
997 numPackages == 0 ? "Install packages..." : // disabled button case
998 numPackages == 1 ? "Install 1 package..." :
999 String.format("Install %d packages...", numPackages));
1001 // We can only delete local archives
1002 numPackages = getArchivesToDelete(null /*outMsg*/, null /*outArchives*/);
1004 mButtonDelete.setEnabled((numPackages > 0) && !mOperationPending);
1005 mButtonDelete.setText(
1006 numPackages == 0 ? "Delete packages..." : // disabled button case
1007 numPackages == 1 ? "Delete 1 package..." :
1008 String.format("Delete %d packages...", numPackages));
1012 * Called when the Install Package button is selected.
1013 * Collects the packages to be installed and shows the installation window.
1015 private void onButtonInstall() {
1016 ArrayList<Archive> archives = new ArrayList<Archive>();
1017 getArchivesForInstall(archives);
1019 if (mUpdaterData != null) {
1020 boolean needsRefresh = false;
1022 beginOperationPending();
1024 List<Archive> installed = mUpdaterData.updateOrInstallAll_WithGUI(
1026 mCheckFilterObsolete.getSelection() /* includeObsoletes */,
1027 mContext == SdkInvocationContext.IDE ?
1028 UpdaterData.TOOLS_MSG_UPDATED_FROM_ADT :
1029 UpdaterData.TOOLS_MSG_UPDATED_FROM_SDKMAN);
1030 needsRefresh = installed != null && !installed.isEmpty();
1032 endOperationPending();
1035 // The local package list has changed, make sure to refresh it
1043 * Selects the archives that can be installed.
1044 * This can be used with a null {@code outArchives} just to count the number of
1045 * installable archives.
1047 * @param outArchives An archive list where to add the archives that can be installed.
1049 * @return The number of archives that can be installed.
1051 private int getArchivesForInstall(List<Archive> outArchives) {
1052 if (mTreeViewer == null ||
1053 mTreeViewer.getTree() == null ||
1054 mTreeViewer.getTree().isDisposed()) {
1057 Object[] checked = mTreeViewer.getCheckedElements();
1058 if (checked == null) {
1064 if (mDisplayArchives) {
1065 // In detail mode, we display archives so we can install only the
1066 // archives that are actually selected.
1067 // Note that in this mode we allow the user to install an archive
1068 // even if it's not "compatible" with the current platform or is
1069 // already installed.
1071 for (Object c : checked) {
1072 if (c instanceof Archive) {
1073 Archive a = (Archive) c;
1076 if (outArchives != null) {
1077 outArchives.add((Archive) c);
1083 // In non-detail mode, we install all the compatible archives
1084 // found in the selected pkg items. We also automatically
1085 // select update packages rather than the root package if any.
1087 for (Object c : checked) {
1089 if (c instanceof Package) {
1090 // This is an update package
1092 } else if (c instanceof PkgItem) {
1093 p = ((PkgItem) c).getMainPackage();
1095 PkgItem pi = (PkgItem) c;
1096 if (pi.getState() == PkgState.INSTALLED) {
1097 // We don't allow installing items that are already installed
1098 // unless they have a pending update.
1099 p = pi.getUpdatePkg();
1101 } else if (pi.getState() == PkgState.NEW) {
1102 p = pi.getMainPackage();
1106 for (Archive a : p.getArchives()) {
1107 if (a != null && a.isCompatible()) {
1109 if (outArchives != null) {
1122 * Called when the Delete Package button is selected.
1123 * Collects the packages to be deleted, prompt the user for confirmation
1124 * and actually performs the deletion.
1126 private void onButtonDelete() {
1127 final String title = "Delete SDK Package";
1128 StringBuilder msg = new StringBuilder("Are you sure you want to delete:");
1130 // A list of archives to delete
1131 final ArrayList<Archive> archives = new ArrayList<Archive>();
1133 getArchivesToDelete(msg, archives);
1135 if (!archives.isEmpty()) {
1136 msg.append("\n").append("This cannot be undone."); //$NON-NLS-1$
1137 if (MessageDialog.openQuestion(getShell(), title, msg.toString())) {
1139 beginOperationPending();
1141 mUpdaterData.getTaskFactory().start("Delete Package", new ITask() {
1142 public void run(ITaskMonitor monitor) {
1143 monitor.setProgressMax(archives.size() + 1);
1144 for (Archive a : archives) {
1145 monitor.setDescription("Deleting '%1$s' (%2$s)",
1146 a.getParentPackage().getShortDescription(),
1147 a.getLocalOsPath());
1149 // Delete the actual package
1152 monitor.incProgress(1);
1153 if (monitor.isCancelRequested()) {
1158 monitor.incProgress(1);
1159 monitor.setDescription("Done");
1163 endOperationPending();
1165 // The local package list has changed, make sure to refresh it
1173 * Selects the archives that can be deleted and collect their names.
1174 * This can be used with a null {@code outArchives} and a null {@code outMsg}
1175 * just to count the number of archives to be deleted.
1177 * @param outMsg A StringBuilder where the names of the packages to be deleted is
1178 * accumulated. This is used to confirm deletion with the user.
1179 * @param outArchives An archive list where to add the archives that can be installed.
1181 * @return The number of archives that can be deleted.
1183 private int getArchivesToDelete(StringBuilder outMsg, List<Archive> outArchives) {
1184 if (mTreeViewer == null ||
1185 mTreeViewer.getTree() == null ||
1186 mTreeViewer.getTree().isDisposed()) {
1189 Object[] checked = mTreeViewer.getCheckedElements();
1190 if (checked == null) {
1191 // This should not happen since the button should be disabled
1197 if (mDisplayArchives) {
1198 // In detail mode, select archives that can be deleted
1200 for (Object c : checked) {
1201 if (c instanceof Archive) {
1202 Archive a = (Archive) c;
1203 if (a != null && a.isLocal()) {
1205 if (outMsg != null) {
1206 String osPath = a.getLocalOsPath();
1207 File dir = new File(osPath);
1208 Package p = a.getParentPackage();
1209 if (p != null && dir.isDirectory()) {
1210 outMsg.append("\n - ") //$NON-NLS-1$
1211 .append(p.getShortDescription());
1214 if (outArchives != null) {
1221 // In non-detail mode, select archives of selected packages that can be deleted.
1223 for (Object c : checked) {
1224 if (c instanceof PkgItem) {
1225 PkgItem pi = (PkgItem) c;
1226 PkgState state = pi.getState();
1227 if (state == PkgState.INSTALLED) {
1228 Package p = pi.getMainPackage();
1230 for (Archive a : p.getArchives()) {
1231 if (a != null && a.isLocal()) {
1233 if (outMsg != null) {
1234 String osPath = a.getLocalOsPath();
1235 File dir = new File(osPath);
1236 if (dir.isDirectory()) {
1237 outMsg.append("\n - ") //$NON-NLS-1$
1238 .append(p.getShortDescription());
1241 if (outArchives != null) {
1254 // ----------------------
1257 * A custom version of {@link TreeColumnViewerLabelProvider} which
1258 * handles {@link TreePath}s and delegates content to a base
1259 * {@link PkgCellLabelProvider} for the given {@link TreeViewerColumn}.
1261 * The implementation handles a variety of providers (table label, table
1262 * color, table font) but does not implement a tooltip provider, so we
1263 * delegate the calls here to the appropriate {@link PkgCellLabelProvider}.
1265 * Only {@link #getToolTipText(Object)} is really useful for us but we
1266 * delegate all the tooltip calls for completeness and avoid surprises later
1267 * if we ever decide to override more things in the label provider.
1269 public class PkgTreeColumnViewerLabelProvider extends TreeColumnViewerLabelProvider {
1271 private CellLabelProvider mTooltipProvider;
1273 public PkgTreeColumnViewerLabelProvider(TreeViewerColumn column) {
1274 super(new PkgCellLabelProvider(column));
1278 public void setProviders(Object provider) {
1279 super.setProviders(provider);
1280 if (provider instanceof CellLabelProvider) {
1281 mTooltipProvider = (CellLabelProvider) provider;
1286 public Image getToolTipImage(Object object) {
1287 if (mTooltipProvider != null) {
1288 return mTooltipProvider.getToolTipImage(object);
1290 return super.getToolTipImage(object);
1294 public String getToolTipText(Object element) {
1295 if (mTooltipProvider != null) {
1296 return mTooltipProvider.getToolTipText(element);
1298 return super.getToolTipText(element);
1302 public Color getToolTipBackgroundColor(Object object) {
1303 if (mTooltipProvider != null) {
1304 return mTooltipProvider.getToolTipBackgroundColor(object);
1306 return super.getToolTipBackgroundColor(object);
1310 public Color getToolTipForegroundColor(Object object) {
1311 if (mTooltipProvider != null) {
1312 return mTooltipProvider.getToolTipForegroundColor(object);
1314 return super.getToolTipForegroundColor(object);
1318 public Font getToolTipFont(Object object) {
1319 if (mTooltipProvider != null) {
1320 return mTooltipProvider.getToolTipFont(object);
1322 return super.getToolTipFont(object);
1326 public Point getToolTipShift(Object object) {
1327 if (mTooltipProvider != null) {
1328 return mTooltipProvider.getToolTipShift(object);
1330 return super.getToolTipShift(object);
1334 public boolean useNativeToolTip(Object object) {
1335 if (mTooltipProvider != null) {
1336 return mTooltipProvider.useNativeToolTip(object);
1338 return super.useNativeToolTip(object);
1342 public int getToolTipTimeDisplayed(Object object) {
1343 if (mTooltipProvider != null) {
1344 return mTooltipProvider.getToolTipTimeDisplayed(object);
1346 return super.getToolTipTimeDisplayed(object);
1350 public int getToolTipDisplayDelayTime(Object object) {
1351 if (mTooltipProvider != null) {
1352 return mTooltipProvider.getToolTipDisplayDelayTime(object);
1354 return super.getToolTipDisplayDelayTime(object);
1358 public int getToolTipStyle(Object object) {
1359 if (mTooltipProvider != null) {
1360 return mTooltipProvider.getToolTipStyle(object);
1362 return super.getToolTipStyle(object);
1366 public class PkgCellLabelProvider extends ColumnLabelProvider implements ITableFontProvider {
1368 private final TreeViewerColumn mColumn;
1370 public PkgCellLabelProvider(TreeViewerColumn column) {
1376 public String getText(Object element) {
1378 if (mColumn == mColumnName) {
1380 if (element instanceof PkgCategory) {
1381 return ((PkgCategory) element).getLabel();
1382 } else if (element instanceof PkgItem) {
1383 return getPkgItemName((PkgItem) element);
1384 } else if (element instanceof IDescription) {
1385 return ((IDescription) element).getShortDescription();
1388 } else if (mColumn == mColumnApi) {
1391 if (element instanceof PkgItem) {
1392 api = ((PkgItem) element).getApi();
1395 return Integer.toString(api);
1398 } else if (mColumn == mColumnRevision) {
1400 if (element instanceof PkgItem) {
1401 PkgItem pkg = (PkgItem) element;
1402 return Integer.toString(pkg.getRevision());
1405 } else if (mColumn == mColumnStatus) {
1407 if (element instanceof PkgItem) {
1408 PkgItem pkg = (PkgItem) element;
1410 switch(pkg.getState()) {
1412 Package update = pkg.getUpdatePkg();
1413 if (update != null) {
1414 return String.format(
1415 "Update available: rev. %1$s",
1416 update.getRevision());
1421 return "Not installed";
1423 return pkg.getState().toString();
1425 } else if (element instanceof Package) {
1426 // This is an update package.
1427 return "New revision " + Integer.toString(((Package) element).getRevision());
1434 private String getPkgItemName(PkgItem item) {
1435 String name = item.getName().trim();
1437 if (isSortByApi()) {
1438 // When sorting by API, the package name might contains the API number
1439 // or the platform name at the end. If we find it, cut it out since it's
1442 PkgCategoryApi cat = (PkgCategoryApi) findCategoryForItem(item);
1443 String apiLabel = cat.getApiLabel();
1444 String platLabel = cat.getPlatformName();
1446 if (platLabel != null && name.endsWith(platLabel)) {
1447 return name.substring(0, name.length() - platLabel.length());
1449 } else if (apiLabel != null && name.endsWith(apiLabel)) {
1450 return name.substring(0, name.length() - apiLabel.length());
1452 } else if (platLabel != null && item.isObsolete() && name.indexOf(platLabel) > 0) {
1453 // For obsolete items, the format is "<base name> <platform name> (Obsolete)"
1454 // so in this case only accept removing a platform name that is not at
1456 name = name.replace(platLabel, ""); //$NON-NLS-1$
1460 // Collapse potential duplicated spacing
1461 name = name.replaceAll(" +", " "); //$NON-NLS-1$ //$NON-NLS-2$
1466 private PkgCategory findCategoryForItem(PkgItem item) {
1467 List<PkgCategory> cats = mDiffLogic.getCategories(isSortByApi());
1468 for (PkgCategory cat : cats) {
1469 for (PkgItem i : cat.getItems()) {
1480 public Image getImage(Object element) {
1481 ImageFactory imgFactory = mUpdaterData.getImageFactory();
1483 if (imgFactory != null) {
1484 if (mColumn == mColumnName) {
1485 if (element instanceof PkgCategory) {
1486 return imgFactory.getImageForObject(((PkgCategory) element).getIconRef());
1487 } else if (element instanceof PkgItem) {
1488 return imgFactory.getImageForObject(((PkgItem) element).getMainPackage());
1490 return imgFactory.getImageForObject(element);
1492 } else if (mColumn == mColumnStatus && element instanceof PkgItem) {
1493 PkgItem pi = (PkgItem) element;
1494 switch(pi.getState()) {
1496 if (pi.hasUpdatePkg()) {
1497 return imgFactory.getImageByName(ICON_PKG_UPDATE);
1499 return imgFactory.getImageByName(ICON_PKG_INSTALLED);
1502 return imgFactory.getImageByName(ICON_PKG_NEW);
1506 return super.getImage(element);
1509 // -- ITableFontProvider
1511 public Font getFont(Object element, int columnIndex) {
1512 if (element instanceof PkgItem) {
1513 if (((PkgItem) element).getState() == PkgState.NEW) {
1514 return mTreeFontItalic;
1516 } else if (element instanceof Package) {
1518 return mTreeFontItalic;
1520 return super.getFont(element);
1523 // -- Tooltip support
1526 public String getToolTipText(Object element) {
1527 if (element instanceof PkgItem) {
1528 element = ((PkgItem) element).getMainPackage();
1530 if (element instanceof IDescription) {
1531 String s = ((IDescription) element).getLongDescription();
1532 if (element instanceof Package) {
1533 SdkSource src = ((Package) element).getParentSource();
1536 URL url = new URL(src.getUrl());
1537 String host = url.getHost();
1538 if (((Package) element).isLocal()) {
1539 s += String.format("\nInstalled from %1$s", host);
1541 s += String.format("\nProvided by %1$s", host);
1543 } catch (MalformedURLException ignore) {
1549 return super.getToolTipText(element);
1553 public Point getToolTipShift(Object object) {
1554 return new Point(15, 5);
1558 public int getToolTipDisplayDelayTime(Object object) {
1563 private class PkgContentProvider implements ITreeContentProvider {
1565 public Object[] getChildren(Object parentElement) {
1566 if (parentElement instanceof ArrayList<?>) {
1567 return ((ArrayList<?>) parentElement).toArray();
1569 } else if (parentElement instanceof PkgCategory) {
1570 return ((PkgCategory) parentElement).getItems().toArray();
1572 } else if (parentElement instanceof PkgItem) {
1573 if (mDisplayArchives) {
1575 Package pkg = ((PkgItem) parentElement).getUpdatePkg();
1577 // Display update packages as sub-items if the details mode is activated.
1579 return new Object[] { pkg };
1582 return ((PkgItem) parentElement).getArchives();
1585 } else if (parentElement instanceof Package) {
1586 if (mDisplayArchives) {
1587 return ((Package) parentElement).getArchives();
1592 return new Object[0];
1595 @SuppressWarnings("unchecked")
1596 public Object getParent(Object element) {
1597 // This operation is expensive, so we do the minimum
1598 // and don't try to cover all cases.
1600 if (element instanceof PkgItem) {
1601 Object input = mTreeViewer.getInput();
1602 if (input != null) {
1603 for (PkgCategory cat : (List<PkgCategory>) input) {
1604 if (cat.getItems().contains(element)) {
1614 public boolean hasChildren(Object parentElement) {
1615 if (parentElement instanceof ArrayList<?>) {
1618 } else if (parentElement instanceof PkgCategory) {
1621 } else if (parentElement instanceof PkgItem) {
1622 if (mDisplayArchives) {
1623 Package pkg = ((PkgItem) parentElement).getUpdatePkg();
1625 // Display update packages as sub-items if the details mode is activated.
1630 Archive[] archives = ((PkgItem) parentElement).getArchives();
1631 return archives.length > 0;
1633 } else if (parentElement instanceof Package) {
1634 if (mDisplayArchives) {
1635 return ((Package) parentElement).getArchives().length > 0;
1642 public Object[] getElements(Object inputElement) {
1643 return getChildren(inputElement);
1646 public void dispose() {
1651 public void inputChanged(Viewer arg0, Object arg1, Object arg2) {
1656 // --- Implementation of ISdkChangeListener ---
1658 public void onSdkLoaded() {
1662 public void onSdkReload() {
1663 // The sdkmanager finished reloading its data. We must not call localReload() from here
1664 // since we don't want to alter the sdkmanager's data that just finished loading.
1668 public void preInstallHook() {
1669 // nothing to be done for now.
1672 public void postInstallHook() {
1673 // nothing to be done for now.
1677 // --- End of hiding from SWT Designer ---