blob: ff79c68a3bb64d7ad15f8ef3169db0f512587665 [file] [log] [blame]
/*
* Copyright (C) 2009 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.tasks;
import com.android.sdklib.SdkConstants;
import com.android.sdklib.internal.repository.ITaskMonitor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
/**
* Implements a {@link ProgressDialog}, used by the {@link ProgressTask} class.
* This separates the dialog UI from the task logic.
*
* Note: this does not implement the {@link ITaskMonitor} interface to avoid confusing
* SWT Designer.
*/
final class ProgressDialog extends Dialog {
/**
* Min Y location for dialog. Need to deal with the menu bar on mac os.
*/
private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ?
20 : 0;
private static enum CancelMode {
/** Cancel button says "Cancel" and is enabled. Waiting for user to cancel. */
ACTIVE,
/** Cancel button has been clicked. Waiting for thread to finish. */
CANCEL_PENDING,
/** Close pending. Close button clicked or thread finished but there were some
* messages so the user needs to manually close. */
CLOSE_MANUAL,
/** Close button clicked or thread finished. The window will automatically close. */
CLOSE_AUTO
}
/** The current mode of operation of the dialog. */
private CancelMode mCancelMode = CancelMode.ACTIVE;
/** Last dialog size for this session. */
private static Point sLastSize;
// UI fields
private Shell mDialogShell;
private Composite mRootComposite;
private Label mLabel;
private ProgressBar mProgressBar;
private Button mCancelButton;
private Text mResultText;
private final Thread mTaskThread;
/**
* Create the dialog.
* @param parent Parent container
* @param taskThread The thread to run the task.
*/
public ProgressDialog(Shell parent, Thread taskThread) {
super(parent, SWT.APPLICATION_MODAL);
mTaskThread = taskThread;
}
/**
* Open the dialog and blocks till it gets closed
*/
public void open() {
createContents();
positionShell(); //$hide$ (hide from SWT designer)
mDialogShell.open();
mDialogShell.layout();
startThread(); //$hide$ (hide from SWT designer)
Display display = getParent().getDisplay();
while (!mDialogShell.isDisposed() && mCancelMode != CancelMode.CLOSE_AUTO) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
setCancelRequested(); //$hide$ (hide from SWT designer)
if (!mDialogShell.isDisposed()) {
sLastSize = mDialogShell.getSize();
mDialogShell.close();
}
}
/**
* Create contents of the dialog.
*/
private void createContents() {
mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE);
mDialogShell.addShellListener(new ShellAdapter() {
@Override
public void shellClosed(ShellEvent e) {
onShellClosed(e);
}
});
mDialogShell.setLayout(new GridLayout(1, false));
mDialogShell.setSize(450, 300);
mDialogShell.setText(getText());
mRootComposite = new Composite(mDialogShell, SWT.NONE);
mRootComposite.setLayout(new GridLayout(2, false));
mRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
mLabel = new Label(mRootComposite, SWT.NONE);
mLabel.setText("Task");
mLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
mProgressBar = new ProgressBar(mRootComposite, SWT.NONE);
mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
mCancelButton = new Button(mRootComposite, SWT.NONE);
mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
mCancelButton.setText("Cancel");
mCancelButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onCancelSelected(); //$hide$
}
});
mResultText = new Text(mRootComposite,
SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI);
mResultText.setEditable(true);
mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
}
// -- End of UI, Start of internal logic ----------
// Hide everything down-below from SWT designer
//$hide>>$
public boolean isCancelRequested() {
return mCancelMode != CancelMode.ACTIVE;
}
/**
* Sets the mode to cancel pending.
* The first time this grays the cancel button, to let the user know that the
* cancel operation is pending.
*/
public void setCancelRequested() {
if (!mDialogShell.isDisposed()) {
// The dialog is not disposed, make sure to run all this in the UI thread
// and lock on the cancel button mode.
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
synchronized (mCancelMode) {
if (mCancelMode == CancelMode.ACTIVE) {
mCancelMode = CancelMode.CANCEL_PENDING;
if (!mCancelButton.isDisposed()) {
mCancelButton.setEnabled(false);
}
}
}
}
});
} else {
// The dialog is disposed. Just set the boolean. We shouldn't be here.
if (mCancelMode == CancelMode.ACTIVE) {
mCancelMode = CancelMode.CANCEL_PENDING;
}
}
}
/**
* Sets the mode to close manual.
* The first time, this also ungrays the pause button and converts it to a close button.
*/
public void setManualCloseRequested() {
if (!mDialogShell.isDisposed()) {
// The dialog is not disposed, make sure to run all this in the UI thread
// and lock on the cancel button mode.
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
synchronized (mCancelMode) {
if (mCancelMode != CancelMode.CLOSE_MANUAL &&
mCancelMode != CancelMode.CLOSE_AUTO) {
mCancelMode = CancelMode.CLOSE_MANUAL;
if (!mCancelButton.isDisposed()) {
mCancelButton.setEnabled(true);
mCancelButton.setText("Close");
}
}
}
}
});
} else {
// The dialog is disposed. Just set the booleans. We shouldn't be here.
if (mCancelMode != CancelMode.CLOSE_MANUAL &&
mCancelMode != CancelMode.CLOSE_AUTO) {
mCancelMode = CancelMode.CLOSE_MANUAL;
}
}
}
/**
* Sets the mode to close auto.
* The main loop will just exit and close the shell at the first opportunity.
*/
public void setAutoCloseRequested() {
synchronized (mCancelMode) {
if (mCancelMode != CancelMode.CLOSE_AUTO) {
mCancelMode = CancelMode.CLOSE_AUTO;
}
}
}
/**
* Callback invoked when the cancel button is selected.
* When in closing mode, this simply closes the shell. Otherwise triggers a cancel.
*/
private void onCancelSelected() {
if (mCancelMode == CancelMode.CLOSE_MANUAL) {
setAutoCloseRequested();
} else {
setCancelRequested();
}
}
/**
* Callback invoked when the shell is closed either by clicking the close button
* on by calling shell.close().
* This does the same thing as clicking the cancel/close button unless the mode is
* to auto close in which case we should do nothing to let the shell close normally.
*/
private void onShellClosed(ShellEvent e) {
if (mCancelMode != CancelMode.CLOSE_AUTO) {
e.doit = false; // don't close directly
onCancelSelected();
}
}
/**
* Sets the description in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
public void setDescription(final String descriptionFormat, final Object...args) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mLabel.isDisposed()) {
mLabel.setText(String.format(descriptionFormat, args));
}
}
});
}
/**
* Sets the description in the current task dialog.
* This method can be invoked from a non-UI thread.
*/
public void setResult(final String resultFormat, final Object...args) {
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mResultText.isDisposed()) {
mResultText.setVisible(true);
String newText = String.format(resultFormat, args);
String lastText = mResultText.getText();
if (lastText != null &&
lastText.length() > 0 &&
!lastText.endsWith("\n") &&
!newText.startsWith("\n")) {
mResultText.append("\n");
}
mResultText.append(newText);
}
}
});
}
}
/**
* Sets the max value of the progress bar.
* This method can be invoked from a non-UI thread.
*
* @see ProgressBar#setMaximum(int)
*/
public void setProgressMax(final int max) {
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mProgressBar.isDisposed()) {
mProgressBar.setMaximum(max);
}
}
});
}
}
/**
* Sets the current value of the progress bar.
* This method can be invoked from a non-UI thread.
*/
public void setProgress(final int value) {
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mProgressBar.isDisposed()) {
mProgressBar.setSelection(value);
}
}
});
}
}
/**
* Returns the current value of the progress bar,
* between 0 and up to {@link #setProgressMax(int)} - 1.
* This method can be invoked from a non-UI thread.
*/
public int getProgress() {
final int[] result = new int[] { 0 };
if (!mDialogShell.isDisposed()) {
mDialogShell.getDisplay().syncExec(new Runnable() {
public void run() {
if (!mProgressBar.isDisposed()) {
result[0] = mProgressBar.getSelection();
}
}
});
}
return result[0];
}
/**
* Starts the thread that runs the task.
* This is deferred till the UI is created.
*/
private void startThread() {
if (mTaskThread != null) {
mTaskThread.start();
}
}
/**
* Centers the dialog in its parent shell.
*/
private void positionShell() {
// Centers the dialog in its parent shell
Shell child = mDialogShell;
Shell parent = getParent();
if (child != null && parent != null) {
// get the parent client area with a location relative to the display
Rectangle parentArea = parent.getClientArea();
Point parentLoc = parent.getLocation();
int px = parentLoc.x;
int py = parentLoc.y;
int pw = parentArea.width;
int ph = parentArea.height;
// Reuse the last size if there's one, otherwise use the default
Point childSize = sLastSize != null ? sLastSize : child.getSize();
int cw = childSize.x;
int ch = childSize.y;
int x = px + (pw - cw) / 2;
if (x < 0) x = 0;
int y = py + (ph - ch) / 2;
if (y < MIN_Y) y = MIN_Y;
child.setLocation(x, y);
child.setSize(cw, ch);
}
}
// End of hiding from SWT Designer
//$hide<<$
}