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