/* * 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<<$ }