1 /* 2 * Copyright (C) 2009 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.tasks; 18 19 import com.android.sdklib.SdkConstants; 20 import com.android.sdklib.internal.repository.ITaskMonitor; 21 22 import org.eclipse.swt.SWT; 23 import org.eclipse.swt.events.SelectionAdapter; 24 import org.eclipse.swt.events.SelectionEvent; 25 import org.eclipse.swt.graphics.Point; 26 import org.eclipse.swt.graphics.Rectangle; 27 import org.eclipse.swt.layout.GridData; 28 import org.eclipse.swt.layout.GridLayout; 29 import org.eclipse.swt.widgets.Button; 30 import org.eclipse.swt.widgets.Composite; 31 import org.eclipse.swt.widgets.Dialog; 32 import org.eclipse.swt.widgets.Display; 33 import org.eclipse.swt.widgets.Label; 34 import org.eclipse.swt.widgets.ProgressBar; 35 import org.eclipse.swt.widgets.Shell; 36 import org.eclipse.swt.widgets.Text; 37 import org.eclipse.swt.events.ShellAdapter; 38 import org.eclipse.swt.events.ShellEvent; 39 40 41 /** 42 * Implements a {@link ProgressDialog}, used by the {@link ProgressTask} class. 43 * This separates the dialog UI from the task logic. 44 * 45 * Note: this does not implement the {@link ITaskMonitor} interface to avoid confusing 46 * SWT Designer. 47 */ 48 final class ProgressDialog extends Dialog { 49 50 /** 51 * Min Y location for dialog. Need to deal with the menu bar on mac os. 52 */ 53 private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 54 20 : 0; 55 56 private static enum CancelMode { 57 /** Cancel button says "Cancel" and is enabled. Waiting for user to cancel. */ 58 ACTIVE, 59 /** Cancel button has been clicked. Waiting for thread to finish. */ 60 CANCEL_PENDING, 61 /** Close pending. Close button clicked or thread finished but there were some 62 * messages so the user needs to manually close. */ 63 CLOSE_MANUAL, 64 /** Close button clicked or thread finished. The window will automatically close. */ 65 CLOSE_AUTO 66 } 67 68 /** The current mode of operation of the dialog. */ 69 private CancelMode mCancelMode = CancelMode.ACTIVE; 70 71 /** Last dialog size for this session. */ 72 private static Point sLastSize; 73 74 75 // UI fields 76 private Shell mDialogShell; 77 private Composite mRootComposite; 78 private Label mLabel; 79 private ProgressBar mProgressBar; 80 private Button mCancelButton; 81 private Text mResultText; 82 private final Thread mTaskThread; 83 84 85 /** 86 * Create the dialog. 87 * @param parent Parent container 88 * @param taskThread The thread to run the task. 89 */ ProgressDialog(Shell parent, Thread taskThread)90 public ProgressDialog(Shell parent, Thread taskThread) { 91 super(parent, SWT.APPLICATION_MODAL); 92 mTaskThread = taskThread; 93 } 94 95 /** 96 * Open the dialog and blocks till it gets closed 97 */ open()98 public void open() { 99 createContents(); 100 positionShell(); //$hide$ (hide from SWT designer) 101 mDialogShell.open(); 102 mDialogShell.layout(); 103 104 startThread(); //$hide$ (hide from SWT designer) 105 106 Display display = getParent().getDisplay(); 107 while (!mDialogShell.isDisposed() && mCancelMode != CancelMode.CLOSE_AUTO) { 108 if (!display.readAndDispatch()) { 109 display.sleep(); 110 } 111 } 112 113 setCancelRequested(); //$hide$ (hide from SWT designer) 114 115 if (!mDialogShell.isDisposed()) { 116 sLastSize = mDialogShell.getSize(); 117 mDialogShell.close(); 118 } 119 } 120 121 /** 122 * Create contents of the dialog. 123 */ createContents()124 private void createContents() { 125 mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE); 126 mDialogShell.addShellListener(new ShellAdapter() { 127 @Override 128 public void shellClosed(ShellEvent e) { 129 onShellClosed(e); 130 } 131 }); 132 mDialogShell.setLayout(new GridLayout(1, false)); 133 mDialogShell.setSize(450, 300); 134 mDialogShell.setText(getText()); 135 136 mRootComposite = new Composite(mDialogShell, SWT.NONE); 137 mRootComposite.setLayout(new GridLayout(2, false)); 138 mRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); 139 140 mLabel = new Label(mRootComposite, SWT.NONE); 141 mLabel.setText("Task"); 142 mLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); 143 144 mProgressBar = new ProgressBar(mRootComposite, SWT.NONE); 145 mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 146 mCancelButton = new Button(mRootComposite, SWT.NONE); 147 mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 148 mCancelButton.setText("Cancel"); 149 150 mCancelButton.addSelectionListener(new SelectionAdapter() { 151 @Override 152 public void widgetSelected(SelectionEvent e) { 153 onCancelSelected(); //$hide$ 154 } 155 }); 156 157 mResultText = new Text(mRootComposite, 158 SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI); 159 mResultText.setEditable(true); 160 mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); 161 } 162 163 // -- End of UI, Start of internal logic ---------- 164 // Hide everything down-below from SWT designer 165 //$hide>>$ 166 isCancelRequested()167 public boolean isCancelRequested() { 168 return mCancelMode != CancelMode.ACTIVE; 169 } 170 171 /** 172 * Sets the mode to cancel pending. 173 * The first time this grays the cancel button, to let the user know that the 174 * cancel operation is pending. 175 */ setCancelRequested()176 public void setCancelRequested() { 177 if (!mDialogShell.isDisposed()) { 178 // The dialog is not disposed, make sure to run all this in the UI thread 179 // and lock on the cancel button mode. 180 mDialogShell.getDisplay().syncExec(new Runnable() { 181 182 public void run() { 183 synchronized (mCancelMode) { 184 if (mCancelMode == CancelMode.ACTIVE) { 185 mCancelMode = CancelMode.CANCEL_PENDING; 186 187 if (!mCancelButton.isDisposed()) { 188 mCancelButton.setEnabled(false); 189 } 190 } 191 } 192 } 193 }); 194 } else { 195 // The dialog is disposed. Just set the boolean. We shouldn't be here. 196 if (mCancelMode == CancelMode.ACTIVE) { 197 mCancelMode = CancelMode.CANCEL_PENDING; 198 } 199 } 200 } 201 202 /** 203 * Sets the mode to close manual. 204 * The first time, this also ungrays the pause button and converts it to a close button. 205 */ setManualCloseRequested()206 public void setManualCloseRequested() { 207 if (!mDialogShell.isDisposed()) { 208 // The dialog is not disposed, make sure to run all this in the UI thread 209 // and lock on the cancel button mode. 210 mDialogShell.getDisplay().syncExec(new Runnable() { 211 212 public void run() { 213 synchronized (mCancelMode) { 214 if (mCancelMode != CancelMode.CLOSE_MANUAL && 215 mCancelMode != CancelMode.CLOSE_AUTO) { 216 mCancelMode = CancelMode.CLOSE_MANUAL; 217 218 if (!mCancelButton.isDisposed()) { 219 mCancelButton.setEnabled(true); 220 mCancelButton.setText("Close"); 221 } 222 } 223 } 224 } 225 }); 226 } else { 227 // The dialog is disposed. Just set the booleans. We shouldn't be here. 228 if (mCancelMode != CancelMode.CLOSE_MANUAL && 229 mCancelMode != CancelMode.CLOSE_AUTO) { 230 mCancelMode = CancelMode.CLOSE_MANUAL; 231 } 232 } 233 } 234 235 /** 236 * Sets the mode to close auto. 237 * The main loop will just exit and close the shell at the first opportunity. 238 */ setAutoCloseRequested()239 public void setAutoCloseRequested() { 240 synchronized (mCancelMode) { 241 if (mCancelMode != CancelMode.CLOSE_AUTO) { 242 mCancelMode = CancelMode.CLOSE_AUTO; 243 } 244 } 245 } 246 247 /** 248 * Callback invoked when the cancel button is selected. 249 * When in closing mode, this simply closes the shell. Otherwise triggers a cancel. 250 */ onCancelSelected()251 private void onCancelSelected() { 252 if (mCancelMode == CancelMode.CLOSE_MANUAL) { 253 setAutoCloseRequested(); 254 } else { 255 setCancelRequested(); 256 } 257 } 258 259 /** 260 * Callback invoked when the shell is closed either by clicking the close button 261 * on by calling shell.close(). 262 * This does the same thing as clicking the cancel/close button unless the mode is 263 * to auto close in which case we should do nothing to let the shell close normally. 264 */ onShellClosed(ShellEvent e)265 private void onShellClosed(ShellEvent e) { 266 if (mCancelMode != CancelMode.CLOSE_AUTO) { 267 e.doit = false; // don't close directly 268 onCancelSelected(); 269 } 270 } 271 272 /** 273 * Sets the description in the current task dialog. 274 * This method can be invoked from a non-UI thread. 275 */ setDescription(final String descriptionFormat, final Object...args)276 public void setDescription(final String descriptionFormat, final Object...args) { 277 mDialogShell.getDisplay().syncExec(new Runnable() { 278 public void run() { 279 if (!mLabel.isDisposed()) { 280 mLabel.setText(String.format(descriptionFormat, args)); 281 } 282 } 283 }); 284 } 285 286 /** 287 * Sets the description in the current task dialog. 288 * This method can be invoked from a non-UI thread. 289 */ setResult(final String resultFormat, final Object...args)290 public void setResult(final String resultFormat, final Object...args) { 291 if (!mDialogShell.isDisposed()) { 292 mDialogShell.getDisplay().syncExec(new Runnable() { 293 public void run() { 294 if (!mResultText.isDisposed()) { 295 mResultText.setVisible(true); 296 String newText = String.format(resultFormat, args); 297 String lastText = mResultText.getText(); 298 if (lastText != null && 299 lastText.length() > 0 && 300 !lastText.endsWith("\n") && 301 !newText.startsWith("\n")) { 302 mResultText.append("\n"); 303 } 304 mResultText.append(newText); 305 } 306 } 307 }); 308 } 309 } 310 311 /** 312 * Sets the max value of the progress bar. 313 * This method can be invoked from a non-UI thread. 314 * 315 * @see ProgressBar#setMaximum(int) 316 */ setProgressMax(final int max)317 public void setProgressMax(final int max) { 318 if (!mDialogShell.isDisposed()) { 319 mDialogShell.getDisplay().syncExec(new Runnable() { 320 public void run() { 321 if (!mProgressBar.isDisposed()) { 322 mProgressBar.setMaximum(max); 323 } 324 } 325 }); 326 } 327 } 328 329 /** 330 * Sets the current value of the progress bar. 331 * This method can be invoked from a non-UI thread. 332 */ setProgress(final int value)333 public void setProgress(final int value) { 334 if (!mDialogShell.isDisposed()) { 335 mDialogShell.getDisplay().syncExec(new Runnable() { 336 public void run() { 337 if (!mProgressBar.isDisposed()) { 338 mProgressBar.setSelection(value); 339 } 340 } 341 }); 342 } 343 } 344 345 /** 346 * Returns the current value of the progress bar, 347 * between 0 and up to {@link #setProgressMax(int)} - 1. 348 * This method can be invoked from a non-UI thread. 349 */ getProgress()350 public int getProgress() { 351 final int[] result = new int[] { 0 }; 352 353 if (!mDialogShell.isDisposed()) { 354 mDialogShell.getDisplay().syncExec(new Runnable() { 355 public void run() { 356 if (!mProgressBar.isDisposed()) { 357 result[0] = mProgressBar.getSelection(); 358 } 359 } 360 }); 361 } 362 363 return result[0]; 364 } 365 366 /** 367 * Starts the thread that runs the task. 368 * This is deferred till the UI is created. 369 */ startThread()370 private void startThread() { 371 if (mTaskThread != null) { 372 mTaskThread.start(); 373 } 374 } 375 376 /** 377 * Centers the dialog in its parent shell. 378 */ positionShell()379 private void positionShell() { 380 // Centers the dialog in its parent shell 381 Shell child = mDialogShell; 382 Shell parent = getParent(); 383 if (child != null && parent != null) { 384 385 // get the parent client area with a location relative to the display 386 Rectangle parentArea = parent.getClientArea(); 387 Point parentLoc = parent.getLocation(); 388 int px = parentLoc.x; 389 int py = parentLoc.y; 390 int pw = parentArea.width; 391 int ph = parentArea.height; 392 393 // Reuse the last size if there's one, otherwise use the default 394 Point childSize = sLastSize != null ? sLastSize : child.getSize(); 395 int cw = childSize.x; 396 int ch = childSize.y; 397 398 int x = px + (pw - cw) / 2; 399 if (x < 0) x = 0; 400 401 int y = py + (ph - ch) / 2; 402 if (y < MIN_Y) y = MIN_Y; 403 404 child.setLocation(x, y); 405 child.setSize(cw, ch); 406 } 407 } 408 409 // End of hiding from SWT Designer 410 //$hide<<$ 411 } 412