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.ui; 18 19 20 import com.android.SdkConstants; 21 import com.android.sdklib.internal.repository.ITaskFactory; 22 import com.android.sdklib.internal.repository.sources.SdkSourceProperties; 23 import com.android.sdkuilib.internal.repository.AboutDialog; 24 import com.android.sdkuilib.internal.repository.ISdkUpdaterWindow; 25 import com.android.sdkuilib.internal.repository.MenuBarWrapper; 26 import com.android.sdkuilib.internal.repository.SettingsController; 27 import com.android.sdkuilib.internal.repository.SettingsController.Settings; 28 import com.android.sdkuilib.internal.repository.SettingsDialog; 29 import com.android.sdkuilib.internal.repository.UpdaterData; 30 import com.android.sdkuilib.internal.repository.icons.ImageFactory; 31 import com.android.sdkuilib.internal.repository.ui.PackagesPage.MenuAction; 32 import com.android.sdkuilib.internal.tasks.ILogUiProvider; 33 import com.android.sdkuilib.internal.tasks.ProgressView; 34 import com.android.sdkuilib.internal.tasks.ProgressViewFactory; 35 import com.android.sdkuilib.internal.widgets.ImgDisabledButton; 36 import com.android.sdkuilib.internal.widgets.ToggleButton; 37 import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext; 38 import com.android.sdkuilib.repository.ISdkChangeListener; 39 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext; 40 import com.android.utils.ILogger; 41 42 import org.eclipse.swt.SWT; 43 import org.eclipse.swt.events.DisposeEvent; 44 import org.eclipse.swt.events.DisposeListener; 45 import org.eclipse.swt.events.SelectionAdapter; 46 import org.eclipse.swt.events.SelectionEvent; 47 import org.eclipse.swt.graphics.Image; 48 import org.eclipse.swt.graphics.Point; 49 import org.eclipse.swt.layout.GridData; 50 import org.eclipse.swt.layout.GridLayout; 51 import org.eclipse.swt.widgets.Composite; 52 import org.eclipse.swt.widgets.Display; 53 import org.eclipse.swt.widgets.Event; 54 import org.eclipse.swt.widgets.Label; 55 import org.eclipse.swt.widgets.Listener; 56 import org.eclipse.swt.widgets.Menu; 57 import org.eclipse.swt.widgets.MenuItem; 58 import org.eclipse.swt.widgets.ProgressBar; 59 import org.eclipse.swt.widgets.Shell; 60 61 /** 62 * This is the private implementation of the UpdateWindow 63 * for the second version of the SDK Manager. 64 * <p/> 65 * This window features only one embedded page, the combined installed+available package list. 66 */ 67 public class SdkUpdaterWindowImpl2 implements ISdkUpdaterWindow { 68 69 public static final String APP_NAME = "Android SDK Manager"; 70 private static final String SIZE_POS_PREFIX = "sdkman2"; //$NON-NLS-1$ 71 72 private final Shell mParentShell; 73 private final SdkInvocationContext mContext; 74 /** Internal data shared between the window and its pages. */ 75 private final UpdaterData mUpdaterData; 76 77 // --- UI members --- 78 79 protected Shell mShell; 80 private PackagesPage mPkgPage; 81 private ProgressBar mProgressBar; 82 private Label mStatusText; 83 private ImgDisabledButton mButtonStop; 84 private ToggleButton mButtonShowLog; 85 private SettingsController mSettingsController; 86 private LogWindow mLogWindow; 87 88 /** 89 * Creates a new window. Caller must call open(), which will block. 90 * 91 * @param parentShell Parent shell. 92 * @param sdkLog Logger. Cannot be null. 93 * @param osSdkRoot The OS path to the SDK root. 94 * @param context The {@link SdkInvocationContext} to change the behavior depending on who's 95 * opening the SDK Manager. 96 */ 97 public SdkUpdaterWindowImpl2( 98 Shell parentShell, 99 ILogger sdkLog, 100 String osSdkRoot, 101 SdkInvocationContext context) { 102 mParentShell = parentShell; 103 mContext = context; 104 mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); 105 } 106 107 /** 108 * Creates a new window. Caller must call open(), which will block. 109 * <p/> 110 * This is to be used when the window is opened from {@link AvdManagerWindowImpl1} 111 * to share the same {@link UpdaterData} structure. 112 * 113 * @param parentShell Parent shell. 114 * @param updaterData The parent's updater data. 115 * @param context The {@link SdkInvocationContext} to change the behavior depending on who's 116 * opening the SDK Manager. 117 */ 118 public SdkUpdaterWindowImpl2( 119 Shell parentShell, 120 UpdaterData updaterData, 121 SdkInvocationContext context) { 122 mParentShell = parentShell; 123 mContext = context; 124 mUpdaterData = updaterData; 125 } 126 127 /** 128 * Opens the window. 129 * @wbp.parser.entryPoint 130 */ 131 @Override 132 public void open() { 133 if (mParentShell == null) { 134 Display.setAppName(APP_NAME); //$hide$ (hide from SWT designer) 135 } 136 137 createShell(); 138 preCreateContent(); 139 createContents(); 140 createMenuBar(); 141 createLogWindow(); 142 mShell.open(); 143 mShell.layout(); 144 145 if (postCreateContent()) { //$hide$ (hide from SWT designer) 146 Display display = Display.getDefault(); 147 while (!mShell.isDisposed()) { 148 if (!display.readAndDispatch()) { 149 display.sleep(); 150 } 151 } 152 } 153 154 SdkSourceProperties p = new SdkSourceProperties(); 155 p.save(); 156 157 dispose(); //$hide$ 158 } 159 160 private void createShell() { 161 // The SDK Manager must use a shell trim when standalone 162 // or a dialog trim when invoked from somewhere else. 163 int style = SWT.SHELL_TRIM; 164 if (mContext != SdkInvocationContext.STANDALONE) { 165 style |= SWT.APPLICATION_MODAL; 166 } 167 168 mShell = new Shell(mParentShell, style); 169 mShell.addDisposeListener(new DisposeListener() { 170 @Override 171 public void widgetDisposed(DisposeEvent e) { 172 ShellSizeAndPos.saveSizeAndPos(mShell, SIZE_POS_PREFIX); 173 onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer) 174 } 175 }); 176 177 GridLayout glShell = new GridLayout(2, false); 178 glShell.verticalSpacing = 0; 179 glShell.horizontalSpacing = 0; 180 glShell.marginWidth = 0; 181 glShell.marginHeight = 0; 182 mShell.setLayout(glShell); 183 184 mShell.setMinimumSize(new Point(500, 300)); 185 mShell.setSize(700, 500); 186 mShell.setText(APP_NAME); 187 188 ShellSizeAndPos.loadSizeAndPos(mShell, SIZE_POS_PREFIX); 189 } 190 191 private void createContents() { 192 mPkgPage = new PackagesPage(mShell, SWT.NONE, mUpdaterData, mContext); 193 mPkgPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); 194 195 Composite composite1 = new Composite(mShell, SWT.NONE); 196 composite1.setLayout(new GridLayout(1, false)); 197 composite1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 198 199 mProgressBar = new ProgressBar(composite1, SWT.NONE); 200 mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 201 202 mStatusText = new Label(composite1, SWT.NONE); 203 mStatusText.setText("Status Placeholder"); //$NON-NLS-1$ placeholder 204 mStatusText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 205 206 Composite composite2 = new Composite(mShell, SWT.NONE); 207 composite2.setLayout(new GridLayout(2, false)); 208 209 mButtonStop = new ImgDisabledButton(composite2, SWT.NONE, 210 getImage("stop_enabled_16.png"), //$NON-NLS-1$ 211 getImage("stop_disabled_16.png"), //$NON-NLS-1$ 212 "Click to abort the current task", 213 ""); //$NON-NLS-1$ nothing to abort 214 mButtonStop.addListener(SWT.Selection, new Listener() { 215 @Override 216 public void handleEvent(Event event) { 217 onStopSelected(); 218 } 219 }); 220 221 mButtonShowLog = new ToggleButton(composite2, SWT.NONE, 222 getImage("log_off_16.png"), //$NON-NLS-1$ 223 getImage("log_on_16.png"), //$NON-NLS-1$ 224 "Click to show the log window", // tooltip for state hidden=>shown 225 "Click to hide the log window"); // tooltip for state shown=>hidden 226 mButtonShowLog.addListener(SWT.Selection, new Listener() { 227 @Override 228 public void handleEvent(Event event) { 229 onToggleLogWindow(); 230 } 231 }); 232 } 233 234 @SuppressWarnings("unused") // MenuItem works using side effects 235 private void createMenuBar() { 236 237 Menu menuBar = new Menu(mShell, SWT.BAR); 238 mShell.setMenuBar(menuBar); 239 240 MenuItem menuBarPackages = new MenuItem(menuBar, SWT.CASCADE); 241 menuBarPackages.setText("Packages"); 242 243 Menu menuPkgs = new Menu(menuBarPackages); 244 menuBarPackages.setMenu(menuPkgs); 245 246 MenuItem showUpdatesNew = new MenuItem(menuPkgs, 247 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuStyle()); 248 showUpdatesNew.setText( 249 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuTitle()); 250 mPkgPage.registerMenuAction( 251 MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG, showUpdatesNew); 252 253 MenuItem showInstalled = new MenuItem(menuPkgs, 254 MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuStyle()); 255 showInstalled.setText( 256 MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuTitle()); 257 mPkgPage.registerMenuAction( 258 MenuAction.TOGGLE_SHOW_INSTALLED_PKG, showInstalled); 259 260 MenuItem showObsoletePackages = new MenuItem(menuPkgs, 261 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuStyle()); 262 showObsoletePackages.setText( 263 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuTitle()); 264 mPkgPage.registerMenuAction( 265 MenuAction.TOGGLE_SHOW_OBSOLETE_PKG, showObsoletePackages); 266 267 MenuItem showArchives = new MenuItem(menuPkgs, 268 MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuStyle()); 269 showArchives.setText( 270 MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuTitle()); 271 mPkgPage.registerMenuAction( 272 MenuAction.TOGGLE_SHOW_ARCHIVES, showArchives); 273 274 new MenuItem(menuPkgs, SWT.SEPARATOR); 275 276 MenuItem sortByApi = new MenuItem(menuPkgs, 277 MenuAction.SORT_API_LEVEL.getMenuStyle()); 278 sortByApi.setText( 279 MenuAction.SORT_API_LEVEL.getMenuTitle()); 280 mPkgPage.registerMenuAction( 281 MenuAction.SORT_API_LEVEL, sortByApi); 282 283 MenuItem sortBySource = new MenuItem(menuPkgs, 284 MenuAction.SORT_SOURCE.getMenuStyle()); 285 sortBySource.setText( 286 MenuAction.SORT_SOURCE.getMenuTitle()); 287 mPkgPage.registerMenuAction( 288 MenuAction.SORT_SOURCE, sortBySource); 289 290 new MenuItem(menuPkgs, SWT.SEPARATOR); 291 292 MenuItem reload = new MenuItem(menuPkgs, 293 MenuAction.RELOAD.getMenuStyle()); 294 reload.setText( 295 MenuAction.RELOAD.getMenuTitle()); 296 mPkgPage.registerMenuAction( 297 MenuAction.RELOAD, reload); 298 299 MenuItem menuBarTools = new MenuItem(menuBar, SWT.CASCADE); 300 menuBarTools.setText("Tools"); 301 302 Menu menuTools = new Menu(menuBarTools); 303 menuBarTools.setMenu(menuTools); 304 305 if (mContext == SdkInvocationContext.STANDALONE) { 306 MenuItem manageAvds = new MenuItem(menuTools, SWT.NONE); 307 manageAvds.setText("Manage AVDs..."); 308 manageAvds.addSelectionListener(new SelectionAdapter() { 309 @Override 310 public void widgetSelected(SelectionEvent event) { 311 onAvdManager(); 312 } 313 }); 314 } 315 316 MenuItem manageSources = new MenuItem(menuTools, 317 MenuAction.SHOW_ADDON_SITES.getMenuStyle()); 318 manageSources.setText( 319 MenuAction.SHOW_ADDON_SITES.getMenuTitle()); 320 mPkgPage.registerMenuAction( 321 MenuAction.SHOW_ADDON_SITES, manageSources); 322 323 if (mContext == SdkInvocationContext.STANDALONE || mContext == SdkInvocationContext.IDE) { 324 try { 325 new MenuBarWrapper(APP_NAME, menuTools) { 326 @Override 327 public void onPreferencesMenuSelected() { 328 329 // capture a copy of the initial settings 330 Settings settings1 = new Settings(mSettingsController.getSettings()); 331 332 // open the dialog and wait for it to close 333 SettingsDialog sd = new SettingsDialog(mShell, mUpdaterData); 334 sd.open(); 335 336 // get the new settings 337 Settings settings2 = mSettingsController.getSettings(); 338 339 // We need to reload the package list if the http mode or the preview 340 // modes have changed. 341 if (settings1.getForceHttp() != settings2.getForceHttp() || 342 settings1.getEnablePreviews() != settings2.getEnablePreviews()) { 343 mPkgPage.onSdkReload(); 344 } 345 } 346 347 @Override 348 public void onAboutMenuSelected() { 349 AboutDialog ad = new AboutDialog(mShell, mUpdaterData); 350 ad.open(); 351 } 352 353 @Override 354 public void printError(String format, Object... args) { 355 if (mUpdaterData != null) { 356 mUpdaterData.getSdkLog().error(null, format, args); 357 } 358 } 359 }; 360 } catch (Throwable e) { 361 mUpdaterData.getSdkLog().error(e, "Failed to setup menu bar"); 362 e.printStackTrace(); 363 } 364 } 365 } 366 367 private Image getImage(String filename) { 368 if (mUpdaterData != null) { 369 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 370 if (imgFactory != null) { 371 return imgFactory.getImageByName(filename); 372 } 373 } 374 return null; 375 } 376 377 /** 378 * Creates the log window. 379 * <p/> 380 * If this is invoked from an IDE, we also define a secondary logger so that all 381 * messages flow to the IDE log. This may or may not be what we want in the end 382 * (e.g. a middle ground would be to repeat error, and ignore normal/verbose) 383 */ 384 private void createLogWindow() { 385 mLogWindow = new LogWindow(mShell, 386 mContext == SdkInvocationContext.IDE ? mUpdaterData.getSdkLog() : null); 387 mLogWindow.open(); 388 } 389 390 391 // -- Start of internal part ---------- 392 // Hide everything down-below from SWT designer 393 //$hide>>$ 394 395 // --- Public API ----------- 396 397 /** 398 * Adds a new listener to be notified when a change is made to the content of the SDK. 399 */ 400 @Override 401 public void addListener(ISdkChangeListener listener) { 402 mUpdaterData.addListeners(listener); 403 } 404 405 /** 406 * Removes a new listener to be notified anymore when a change is made to the content of 407 * the SDK. 408 */ 409 @Override 410 public void removeListener(ISdkChangeListener listener) { 411 mUpdaterData.removeListener(listener); 412 } 413 414 // --- Internals & UI Callbacks ----------- 415 416 /** 417 * Called before the UI is created. 418 */ 419 private void preCreateContent() { 420 mUpdaterData.setWindowShell(mShell); 421 // We need the UI factory to create the UI 422 mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay())); 423 // Note: we can't create the TaskFactory yet because we need the UI 424 // to be created first, so this is done in postCreateContent(). 425 } 426 427 /** 428 * Once the UI has been created, initializes the content. 429 * This creates the pages, selects the first one, setups sources and scans for local folders. 430 * 431 * Returns true if we should show the window. 432 */ 433 private boolean postCreateContent() { 434 ProgressViewFactory factory = new ProgressViewFactory(); 435 436 // This class delegates all logging to the mLogWindow window 437 // and filters errors to make sure the window is visible when 438 // an error is logged. 439 ILogUiProvider logAdapter = new ILogUiProvider() { 440 @Override 441 public void setDescription(String description) { 442 mLogWindow.setDescription(description); 443 } 444 445 @Override 446 public void log(String log) { 447 mLogWindow.log(log); 448 } 449 450 @Override 451 public void logVerbose(String log) { 452 mLogWindow.logVerbose(log); 453 } 454 455 @Override 456 public void logError(String log) { 457 mLogWindow.logError(log); 458 459 // Run the window visibility check/toggle on the UI thread. 460 // Note: at least on Windows, it seems ok to check for the window visibility 461 // on a sub-thread but that doesn't seem cross-platform safe. We shouldn't 462 // have a lot of error logging, so this should be acceptable. If not, we could 463 // cache the visibility state. 464 if (mShell != null && !mShell.isDisposed()) { 465 mShell.getDisplay().syncExec(new Runnable() { 466 @Override 467 public void run() { 468 if (!mLogWindow.isVisible()) { 469 // Don't toggle the window visibility directly. 470 // Instead use the same action as the log-toggle button 471 // so that the button's state be kept in sync. 472 onToggleLogWindow(); 473 } 474 } 475 }); 476 } 477 } 478 }; 479 480 factory.setProgressView( 481 new ProgressView(mStatusText, mProgressBar, mButtonStop, logAdapter)); 482 mUpdaterData.setTaskFactory(factory); 483 484 setWindowImage(mShell); 485 486 setupSources(); 487 initializeSettings(); 488 489 if (mUpdaterData.checkIfInitFailed()) { 490 return false; 491 } 492 493 mUpdaterData.broadcastOnSdkLoaded(); 494 495 // Tell the one page its the selected one 496 mPkgPage.performFirstLoad(); 497 498 return true; 499 } 500 501 /** 502 * Creates the icon of the window shell. 503 * 504 * @param shell The shell on which to put the icon 505 */ 506 private void setWindowImage(Shell shell) { 507 String imageName = "android_icon_16.png"; //$NON-NLS-1$ 508 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) { 509 imageName = "android_icon_128.png"; //$NON-NLS-1$ 510 } 511 512 if (mUpdaterData != null) { 513 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 514 if (imgFactory != null) { 515 shell.setImage(imgFactory.getImageByName(imageName)); 516 } 517 } 518 } 519 520 /** 521 * Called by the main loop when the window has been disposed. 522 */ 523 private void dispose() { 524 mLogWindow.close(); 525 mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog()); 526 } 527 528 /** 529 * Callback called when the window shell is disposed. 530 */ 531 private void onAndroidSdkUpdaterDispose() { 532 if (mUpdaterData != null) { 533 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 534 if (imgFactory != null) { 535 imgFactory.dispose(); 536 } 537 } 538 } 539 540 /** 541 * Used to initialize the sources. 542 */ 543 private void setupSources() { 544 mUpdaterData.setupDefaultSources(); 545 } 546 547 /** 548 * Initializes settings. 549 * This must be called after addExtraPages(), which created a settings page. 550 * Iterate through all the pages to find the first (and supposedly unique) setting page, 551 * and use it to load and apply these settings. 552 */ 553 private void initializeSettings() { 554 mSettingsController = mUpdaterData.getSettingsController(); 555 mSettingsController.loadSettings(); 556 mSettingsController.applySettings(); 557 } 558 559 private void onToggleLogWindow() { 560 // toggle visibility 561 if (!mButtonShowLog.isDisposed()) { 562 mLogWindow.setVisible(!mLogWindow.isVisible()); 563 mButtonShowLog.setState(mLogWindow.isVisible() ? 1 : 0); 564 } 565 } 566 567 private void onStopSelected() { 568 // TODO 569 } 570 571 private void onAvdManager() { 572 ITaskFactory oldFactory = mUpdaterData.getTaskFactory(); 573 574 try { 575 AvdManagerWindowImpl1 win = new AvdManagerWindowImpl1( 576 mShell, 577 mUpdaterData, 578 AvdInvocationContext.DIALOG); 579 580 win.open(); 581 } catch (Exception e) { 582 mUpdaterData.getSdkLog().error(e, "AVD Manager window error"); 583 } finally { 584 mUpdaterData.setTaskFactory(oldFactory); 585 } 586 } 587 588 // End of hiding from SWT Designer 589 //$hide<<$ 590 } 591