Home | History | Annotate | Download | only in ui
      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