/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.sdkuilib.internal.repository.ui;
import com.android.SdkConstants;
import com.android.sdklib.internal.repository.ITaskFactory;
import com.android.sdklib.internal.repository.sources.SdkSourceProperties;
import com.android.sdkuilib.internal.repository.AboutDialog;
import com.android.sdkuilib.internal.repository.ISdkUpdaterWindow;
import com.android.sdkuilib.internal.repository.MenuBarWrapper;
import com.android.sdkuilib.internal.repository.SettingsController;
import com.android.sdkuilib.internal.repository.SettingsController.Settings;
import com.android.sdkuilib.internal.repository.SettingsDialog;
import com.android.sdkuilib.internal.repository.UpdaterData;
import com.android.sdkuilib.internal.repository.icons.ImageFactory;
import com.android.sdkuilib.internal.repository.ui.PackagesPage.MenuAction;
import com.android.sdkuilib.internal.tasks.ILogUiProvider;
import com.android.sdkuilib.internal.tasks.ProgressView;
import com.android.sdkuilib.internal.tasks.ProgressViewFactory;
import com.android.sdkuilib.internal.widgets.ImgDisabledButton;
import com.android.sdkuilib.internal.widgets.ToggleButton;
import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext;
import com.android.sdkuilib.repository.ISdkChangeListener;
import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext;
import com.android.utils.ILogger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
/**
* This is the private implementation of the UpdateWindow
* for the second version of the SDK Manager.
* <p/>
* This window features only one embedded page, the combined installed+available package list.
*/
public class SdkUpdaterWindowImpl2 implements ISdkUpdaterWindow {
public static final String APP_NAME = "Android SDK Manager";
private static final String SIZE_POS_PREFIX = "sdkman2"; //$NON-NLS-1$
private final Shell mParentShell;
private final SdkInvocationContext mContext;
/** Internal data shared between the window and its pages. */
private final UpdaterData mUpdaterData;
// --- UI members ---
protected Shell mShell;
private PackagesPage mPkgPage;
private ProgressBar mProgressBar;
private Label mStatusText;
private ImgDisabledButton mButtonStop;
private ToggleButton mButtonShowLog;
private SettingsController mSettingsController;
private LogWindow mLogWindow;
/**
* Creates a new window. Caller must call open(), which will block.
*
* @param parentShell Parent shell.
* @param sdkLog Logger. Cannot be null.
* @param osSdkRoot The OS path to the SDK root.
* @param context The {@link SdkInvocationContext} to change the behavior depending on who's
* opening the SDK Manager.
*/
public SdkUpdaterWindowImpl2(
Shell parentShell,
ILogger sdkLog,
String osSdkRoot,
SdkInvocationContext context) {
mParentShell = parentShell;
mContext = context;
mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
}
/**
* Creates a new window. Caller must call open(), which will block.
* <p/>
* This is to be used when the window is opened from {@link AvdManagerWindowImpl1}
* to share the same {@link UpdaterData} structure.
*
* @param parentShell Parent shell.
* @param updaterData The parent's updater data.
* @param context The {@link SdkInvocationContext} to change the behavior depending on who's
* opening the SDK Manager.
*/
public SdkUpdaterWindowImpl2(
Shell parentShell,
UpdaterData updaterData,
SdkInvocationContext context) {
mParentShell = parentShell;
mContext = context;
mUpdaterData = updaterData;
}
/**
* Opens the window.
* @wbp.parser.entryPoint
*/
@Override
public void open() {
if (mParentShell == null) {
Display.setAppName(APP_NAME); //$hide$ (hide from SWT designer)
}
createShell();
preCreateContent();
createContents();
createMenuBar();
createLogWindow();
mShell.open();
mShell.layout();
if (postCreateContent()) { //$hide$ (hide from SWT designer)
Display display = Display.getDefault();
while (!mShell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
SdkSourceProperties p = new SdkSourceProperties();
p.save();
dispose(); //$hide$
}
private void createShell() {
// The SDK Manager must use a shell trim when standalone
// or a dialog trim when invoked from somewhere else.
int style = SWT.SHELL_TRIM;
if (mContext != SdkInvocationContext.STANDALONE) {
style |= SWT.APPLICATION_MODAL;
}
mShell = new Shell(mParentShell, style);
mShell.addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(DisposeEvent e) {
ShellSizeAndPos.saveSizeAndPos(mShell, SIZE_POS_PREFIX);
onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer)
}
});
GridLayout glShell = new GridLayout(2, false);
glShell.verticalSpacing = 0;
glShell.horizontalSpacing = 0;
glShell.marginWidth = 0;
glShell.marginHeight = 0;
mShell.setLayout(glShell);
mShell.setMinimumSize(new Point(600, 300));
mShell.setSize(700, 500);
mShell.setText(APP_NAME);
ShellSizeAndPos.loadSizeAndPos(mShell, SIZE_POS_PREFIX);
}
private void createContents() {
mPkgPage = new PackagesPage(mShell, SWT.NONE, mUpdaterData, mContext);
mPkgPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
Composite composite1 = new Composite(mShell, SWT.NONE);
composite1.setLayout(new GridLayout(1, false));
composite1.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
mProgressBar = new ProgressBar(composite1, SWT.NONE);
mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
mStatusText = new Label(composite1, SWT.NONE);
mStatusText.setText("Status Placeholder"); //$NON-NLS-1$ placeholder
mStatusText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
Composite composite2 = new Composite(mShell, SWT.NONE);
composite2.setLayout(new GridLayout(2, false));
mButtonStop = new ImgDisabledButton(composite2, SWT.NONE,
getImage("stop_enabled_16.png"), //$NON-NLS-1$
getImage("stop_disabled_16.png"), //$NON-NLS-1$
"Click to abort the current task",
""); //$NON-NLS-1$ nothing to abort
mButtonStop.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
onStopSelected();
}
});
mButtonShowLog = new ToggleButton(composite2, SWT.NONE,
getImage("log_off_16.png"), //$NON-NLS-1$
getImage("log_on_16.png"), //$NON-NLS-1$
"Click to show the log window", // tooltip for state hidden=>shown
"Click to hide the log window"); // tooltip for state shown=>hidden
mButtonShowLog.addListener(SWT.Selection, new Listener() {
@Override
public void handleEvent(Event event) {
onToggleLogWindow();
}
});
}
@SuppressWarnings("unused") // MenuItem works using side effects
private void createMenuBar() {
Menu menuBar = new Menu(mShell, SWT.BAR);
mShell.setMenuBar(menuBar);
MenuItem menuBarPackages = new MenuItem(menuBar, SWT.CASCADE);
menuBarPackages.setText("Packages");
Menu menuPkgs = new Menu(menuBarPackages);
menuBarPackages.setMenu(menuPkgs);
MenuItem showUpdatesNew = new MenuItem(menuPkgs,
MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuStyle());
showUpdatesNew.setText(
MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.TOGGLE_SHOW_UPDATE_NEW_PKG, showUpdatesNew);
MenuItem showInstalled = new MenuItem(menuPkgs,
MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuStyle());
showInstalled.setText(
MenuAction.TOGGLE_SHOW_INSTALLED_PKG.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.TOGGLE_SHOW_INSTALLED_PKG, showInstalled);
MenuItem showObsoletePackages = new MenuItem(menuPkgs,
MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuStyle());
showObsoletePackages.setText(
MenuAction.TOGGLE_SHOW_OBSOLETE_PKG.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.TOGGLE_SHOW_OBSOLETE_PKG, showObsoletePackages);
MenuItem showArchives = new MenuItem(menuPkgs,
MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuStyle());
showArchives.setText(
MenuAction.TOGGLE_SHOW_ARCHIVES.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.TOGGLE_SHOW_ARCHIVES, showArchives);
new MenuItem(menuPkgs, SWT.SEPARATOR);
MenuItem sortByApi = new MenuItem(menuPkgs,
MenuAction.SORT_API_LEVEL.getMenuStyle());
sortByApi.setText(
MenuAction.SORT_API_LEVEL.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.SORT_API_LEVEL, sortByApi);
MenuItem sortBySource = new MenuItem(menuPkgs,
MenuAction.SORT_SOURCE.getMenuStyle());
sortBySource.setText(
MenuAction.SORT_SOURCE.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.SORT_SOURCE, sortBySource);
new MenuItem(menuPkgs, SWT.SEPARATOR);
MenuItem reload = new MenuItem(menuPkgs,
MenuAction.RELOAD.getMenuStyle());
reload.setText(
MenuAction.RELOAD.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.RELOAD, reload);
MenuItem menuBarTools = new MenuItem(menuBar, SWT.CASCADE);
menuBarTools.setText("Tools");
Menu menuTools = new Menu(menuBarTools);
menuBarTools.setMenu(menuTools);
if (mContext == SdkInvocationContext.STANDALONE) {
MenuItem manageAvds = new MenuItem(menuTools, SWT.NONE);
manageAvds.setText("Manage AVDs...");
manageAvds.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
onAvdManager();
}
});
}
MenuItem manageSources = new MenuItem(menuTools,
MenuAction.SHOW_ADDON_SITES.getMenuStyle());
manageSources.setText(
MenuAction.SHOW_ADDON_SITES.getMenuTitle());
mPkgPage.registerMenuAction(
MenuAction.SHOW_ADDON_SITES, manageSources);
if (mContext == SdkInvocationContext.STANDALONE || mContext == SdkInvocationContext.IDE) {
try {
new MenuBarWrapper(APP_NAME, menuTools) {
@Override
public void onPreferencesMenuSelected() {
// capture a copy of the initial settings
Settings settings1 = new Settings(mSettingsController.getSettings());
// open the dialog and wait for it to close
SettingsDialog sd = new SettingsDialog(mShell, mUpdaterData);
sd.open();
// get the new settings
Settings settings2 = mSettingsController.getSettings();
// We need to reload the package list if the http mode or the preview
// modes have changed.
if (settings1.getForceHttp() != settings2.getForceHttp() ||
settings1.getEnablePreviews() != settings2.getEnablePreviews()) {
mPkgPage.onSdkReload();
}
}
@Override
public void onAboutMenuSelected() {
AboutDialog ad = new AboutDialog(mShell, mUpdaterData);
ad.open();
}
@Override
public void printError(String format, Object... args) {
if (mUpdaterData != null) {
mUpdaterData.getSdkLog().error(null, format, args);
}
}
};
} catch (Throwable e) {
mUpdaterData.getSdkLog().error(e, "Failed to setup menu bar");
e.printStackTrace();
}
}
}
private Image getImage(String filename) {
if (mUpdaterData != null) {
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
return imgFactory.getImageByName(filename);
}
}
return null;
}
/**
* Creates the log window.
* <p/>
* If this is invoked from an IDE, we also define a secondary logger so that all
* messages flow to the IDE log. This may or may not be what we want in the end
* (e.g. a middle ground would be to repeat error, and ignore normal/verbose)
*/
private void createLogWindow() {
mLogWindow = new LogWindow(mShell,
mContext == SdkInvocationContext.IDE ? mUpdaterData.getSdkLog() : null);
mLogWindow.open();
}
// -- Start of internal part ----------
// Hide everything down-below from SWT designer
//$hide>>$
// --- Public API -----------
/**
* Adds a new listener to be notified when a change is made to the content of the SDK.
*/
@Override
public void addListener(ISdkChangeListener listener) {
mUpdaterData.addListeners(listener);
}
/**
* Removes a new listener to be notified anymore when a change is made to the content of
* the SDK.
*/
@Override
public void removeListener(ISdkChangeListener listener) {
mUpdaterData.removeListener(listener);
}
// --- Internals & UI Callbacks -----------
/**
* Called before the UI is created.
*/
private void preCreateContent() {
mUpdaterData.setWindowShell(mShell);
// We need the UI factory to create the UI
mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay()));
// Note: we can't create the TaskFactory yet because we need the UI
// to be created first, so this is done in postCreateContent().
}
/**
* Once the UI has been created, initializes the content.
* This creates the pages, selects the first one, setups sources and scans for local folders.
*
* Returns true if we should show the window.
*/
private boolean postCreateContent() {
ProgressViewFactory factory = new ProgressViewFactory();
// This class delegates all logging to the mLogWindow window
// and filters errors to make sure the window is visible when
// an error is logged.
ILogUiProvider logAdapter = new ILogUiProvider() {
@Override
public void setDescription(String description) {
mLogWindow.setDescription(description);
}
@Override
public void log(String log) {
mLogWindow.log(log);
}
@Override
public void logVerbose(String log) {
mLogWindow.logVerbose(log);
}
@Override
public void logError(String log) {
mLogWindow.logError(log);
// Run the window visibility check/toggle on the UI thread.
// Note: at least on Windows, it seems ok to check for the window visibility
// on a sub-thread but that doesn't seem cross-platform safe. We shouldn't
// have a lot of error logging, so this should be acceptable. If not, we could
// cache the visibility state.
if (mShell != null && !mShell.isDisposed()) {
mShell.getDisplay().syncExec(new Runnable() {
@Override
public void run() {
if (!mLogWindow.isVisible()) {
// Don't toggle the window visibility directly.
// Instead use the same action as the log-toggle button
// so that the button's state be kept in sync.
onToggleLogWindow();
}
}
});
}
}
};
factory.setProgressView(
new ProgressView(mStatusText, mProgressBar, mButtonStop, logAdapter));
mUpdaterData.setTaskFactory(factory);
setWindowImage(mShell);
setupSources();
initializeSettings();
if (mUpdaterData.checkIfInitFailed()) {
return false;
}
mUpdaterData.broadcastOnSdkLoaded();
// Tell the one page its the selected one
mPkgPage.performFirstLoad();
return true;
}
/**
* Creates the icon of the window shell.
*
* @param shell The shell on which to put the icon
*/
private void setWindowImage(Shell shell) {
String imageName = "android_icon_16.png"; //$NON-NLS-1$
if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
imageName = "android_icon_128.png"; //$NON-NLS-1$
}
if (mUpdaterData != null) {
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
shell.setImage(imgFactory.getImageByName(imageName));
}
}
}
/**
* Called by the main loop when the window has been disposed.
*/
private void dispose() {
mLogWindow.close();
mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog());
}
/**
* Callback called when the window shell is disposed.
*/
private void onAndroidSdkUpdaterDispose() {
if (mUpdaterData != null) {
ImageFactory imgFactory = mUpdaterData.getImageFactory();
if (imgFactory != null) {
imgFactory.dispose();
}
}
}
/**
* Used to initialize the sources.
*/
private void setupSources() {
mUpdaterData.setupDefaultSources();
}
/**
* Initializes settings.
* This must be called after addExtraPages(), which created a settings page.
* Iterate through all the pages to find the first (and supposedly unique) setting page,
* and use it to load and apply these settings.
*/
private void initializeSettings() {
mSettingsController = mUpdaterData.getSettingsController();
mSettingsController.loadSettings();
mSettingsController.applySettings();
}
private void onToggleLogWindow() {
// toggle visibility
if (!mButtonShowLog.isDisposed()) {
mLogWindow.setVisible(!mLogWindow.isVisible());
mButtonShowLog.setState(mLogWindow.isVisible() ? 1 : 0);
}
}
private void onStopSelected() {
// TODO
}
private void onAvdManager() {
ITaskFactory oldFactory = mUpdaterData.getTaskFactory();
try {
AvdManagerWindowImpl1 win = new AvdManagerWindowImpl1(
mShell,
mUpdaterData,
AvdInvocationContext.DIALOG);
win.open();
} catch (Exception e) {
mUpdaterData.getSdkLog().error(e, "AVD Manager window error");
} finally {
mUpdaterData.setTaskFactory(oldFactory);
}
}
// End of hiding from SWT Designer
//$hide<<$
}