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 import com.android.sdklib.internal.repository.UserCredentials; 22 import com.android.sdkuilib.ui.AuthenticationDialog; 23 import com.android.sdkuilib.ui.GridDialog; 24 import com.android.util.Pair; 25 26 import org.eclipse.jface.dialogs.MessageDialog; 27 import org.eclipse.swt.SWT; 28 import org.eclipse.swt.events.SelectionAdapter; 29 import org.eclipse.swt.events.SelectionEvent; 30 import org.eclipse.swt.events.ShellAdapter; 31 import org.eclipse.swt.events.ShellEvent; 32 import org.eclipse.swt.graphics.Point; 33 import org.eclipse.swt.graphics.Rectangle; 34 import org.eclipse.swt.layout.GridData; 35 import org.eclipse.swt.layout.GridLayout; 36 import org.eclipse.swt.widgets.Button; 37 import org.eclipse.swt.widgets.Composite; 38 import org.eclipse.swt.widgets.Dialog; 39 import org.eclipse.swt.widgets.Display; 40 import org.eclipse.swt.widgets.Label; 41 import org.eclipse.swt.widgets.ProgressBar; 42 import org.eclipse.swt.widgets.Shell; 43 import org.eclipse.swt.widgets.Text; 44 45 46 /** 47 * Implements a {@link ProgressTaskDialog}, used by the {@link ProgressTask} class. 48 * This separates the dialog UI from the task logic. 49 * 50 * Note: this does not implement the {@link ITaskMonitor} interface to avoid confusing 51 * SWT Designer. 52 */ 53 final class ProgressTaskDialog extends Dialog implements IProgressUiProvider { 54 55 /** 56 * Min Y location for dialog. Need to deal with the menu bar on mac os. 57 */ 58 private final static int MIN_Y = SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN ? 59 20 : 0; 60 61 private static enum CancelMode { 62 /** Cancel button says "Cancel" and is enabled. Waiting for user to cancel. */ 63 ACTIVE, 64 /** Cancel button has been clicked. Waiting for thread to finish. */ 65 CANCEL_PENDING, 66 /** Close pending. Close button clicked or thread finished but there were some 67 * messages so the user needs to manually close. */ 68 CLOSE_MANUAL, 69 /** Close button clicked or thread finished. The window will automatically close. */ 70 CLOSE_AUTO 71 } 72 73 /** The current mode of operation of the dialog. */ 74 private CancelMode mCancelMode = CancelMode.ACTIVE; 75 76 /** Last dialog size for this session. */ 77 private static Point sLastSize; 78 79 80 // UI fields 81 private Shell mDialogShell; 82 private Composite mRootComposite; 83 private Label mLabel; 84 private ProgressBar mProgressBar; 85 private Button mCancelButton; 86 private Text mResultText; 87 88 89 /** 90 * Create the dialog. 91 * @param parent Parent container 92 */ ProgressTaskDialog(Shell parent)93 public ProgressTaskDialog(Shell parent) { 94 super(parent, SWT.APPLICATION_MODAL); 95 } 96 97 /** 98 * Open the dialog and blocks till it gets closed 99 * @param taskThread The thread to run the task. Cannot be null. 100 */ open(Thread taskThread)101 public void open(Thread taskThread) { 102 createContents(); 103 positionShell(); //$hide$ (hide from SWT designer) 104 mDialogShell.open(); 105 mDialogShell.layout(); 106 107 startThread(taskThread); //$hide$ (hide from SWT designer) 108 109 Display display = getParent().getDisplay(); 110 while (!mDialogShell.isDisposed() && mCancelMode != CancelMode.CLOSE_AUTO) { 111 if (!display.readAndDispatch()) { 112 display.sleep(); 113 } 114 } 115 116 setCancelRequested(); //$hide$ (hide from SWT designer) 117 118 if (!mDialogShell.isDisposed()) { 119 sLastSize = mDialogShell.getSize(); 120 mDialogShell.close(); 121 } 122 } 123 124 /** 125 * Create contents of the dialog. 126 */ createContents()127 private void createContents() { 128 mDialogShell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE); 129 mDialogShell.addShellListener(new ShellAdapter() { 130 @Override 131 public void shellClosed(ShellEvent e) { 132 onShellClosed(e); 133 } 134 }); 135 mDialogShell.setLayout(new GridLayout(1, false)); 136 mDialogShell.setSize(450, 300); 137 mDialogShell.setText(getText()); 138 139 mRootComposite = new Composite(mDialogShell, SWT.NONE); 140 mRootComposite.setLayout(new GridLayout(2, false)); 141 mRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1)); 142 143 mLabel = new Label(mRootComposite, SWT.NONE); 144 mLabel.setText("Task"); 145 mLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); 146 147 mProgressBar = new ProgressBar(mRootComposite, SWT.NONE); 148 mProgressBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1)); 149 mCancelButton = new Button(mRootComposite, SWT.NONE); 150 mCancelButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); 151 mCancelButton.setText("Cancel"); 152 153 mCancelButton.addSelectionListener(new SelectionAdapter() { 154 @Override 155 public void widgetSelected(SelectionEvent e) { 156 onCancelSelected(); //$hide$ 157 } 158 }); 159 160 mResultText = new Text(mRootComposite, 161 SWT.BORDER | SWT.READ_ONLY | SWT.WRAP | 162 SWT.H_SCROLL | SWT.V_SCROLL | SWT.CANCEL | SWT.MULTI); 163 mResultText.setEditable(true); 164 mResultText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); 165 } 166 167 // -- End of UI, Start of internal logic ---------- 168 // Hide everything down-below from SWT designer 169 //$hide>>$ 170 171 @Override isCancelRequested()172 public boolean isCancelRequested() { 173 return mCancelMode != CancelMode.ACTIVE; 174 } 175 176 /** 177 * Sets the mode to cancel pending. 178 * The first time this grays the cancel button, to let the user know that the 179 * cancel operation is pending. 180 */ setCancelRequested()181 public void setCancelRequested() { 182 if (!mDialogShell.isDisposed()) { 183 // The dialog is not disposed, make sure to run all this in the UI thread 184 // and lock on the cancel button mode. 185 mDialogShell.getDisplay().syncExec(new Runnable() { 186 187 @Override 188 public void run() { 189 synchronized (mCancelMode) { 190 if (mCancelMode == CancelMode.ACTIVE) { 191 mCancelMode = CancelMode.CANCEL_PENDING; 192 193 if (!mCancelButton.isDisposed()) { 194 mCancelButton.setEnabled(false); 195 } 196 } 197 } 198 } 199 }); 200 } else { 201 // The dialog is disposed. Just set the boolean. We shouldn't be here. 202 if (mCancelMode == CancelMode.ACTIVE) { 203 mCancelMode = CancelMode.CANCEL_PENDING; 204 } 205 } 206 } 207 208 /** 209 * Sets the mode to close manual. 210 * The first time, this also ungrays the pause button and converts it to a close button. 211 */ setManualCloseRequested()212 public void setManualCloseRequested() { 213 if (!mDialogShell.isDisposed()) { 214 // The dialog is not disposed, make sure to run all this in the UI thread 215 // and lock on the cancel button mode. 216 mDialogShell.getDisplay().syncExec(new Runnable() { 217 218 @Override 219 public void run() { 220 synchronized (mCancelMode) { 221 if (mCancelMode != CancelMode.CLOSE_MANUAL && 222 mCancelMode != CancelMode.CLOSE_AUTO) { 223 mCancelMode = CancelMode.CLOSE_MANUAL; 224 225 if (!mCancelButton.isDisposed()) { 226 mCancelButton.setEnabled(true); 227 mCancelButton.setText("Close"); 228 } 229 } 230 } 231 } 232 }); 233 } else { 234 // The dialog is disposed. Just set the booleans. We shouldn't be here. 235 if (mCancelMode != CancelMode.CLOSE_MANUAL && 236 mCancelMode != CancelMode.CLOSE_AUTO) { 237 mCancelMode = CancelMode.CLOSE_MANUAL; 238 } 239 } 240 } 241 242 /** 243 * Sets the mode to close auto. 244 * The main loop will just exit and close the shell at the first opportunity. 245 */ setAutoCloseRequested()246 public void setAutoCloseRequested() { 247 synchronized (mCancelMode) { 248 if (mCancelMode != CancelMode.CLOSE_AUTO) { 249 mCancelMode = CancelMode.CLOSE_AUTO; 250 } 251 } 252 } 253 254 /** 255 * Callback invoked when the cancel button is selected. 256 * When in closing mode, this simply closes the shell. Otherwise triggers a cancel. 257 */ onCancelSelected()258 private void onCancelSelected() { 259 if (mCancelMode == CancelMode.CLOSE_MANUAL) { 260 setAutoCloseRequested(); 261 } else { 262 setCancelRequested(); 263 } 264 } 265 266 /** 267 * Callback invoked when the shell is closed either by clicking the close button 268 * on by calling shell.close(). 269 * This does the same thing as clicking the cancel/close button unless the mode is 270 * to auto close in which case we should do nothing to let the shell close normally. 271 */ onShellClosed(ShellEvent e)272 private void onShellClosed(ShellEvent e) { 273 if (mCancelMode != CancelMode.CLOSE_AUTO) { 274 e.doit = false; // don't close directly 275 onCancelSelected(); 276 } 277 } 278 279 /** 280 * Sets the description in the current task dialog. 281 * This method can be invoked from a non-UI thread. 282 */ 283 @Override setDescription(final String description)284 public void setDescription(final String description) { 285 mDialogShell.getDisplay().syncExec(new Runnable() { 286 @Override 287 public void run() { 288 if (!mLabel.isDisposed()) { 289 mLabel.setText(description); 290 } 291 } 292 }); 293 } 294 295 /** 296 * Adds to the log in the current task dialog. 297 * This method can be invoked from a non-UI thread. 298 */ 299 @Override log(final String info)300 public void log(final String info) { 301 if (!mDialogShell.isDisposed()) { 302 mDialogShell.getDisplay().syncExec(new Runnable() { 303 @Override 304 public void run() { 305 if (!mResultText.isDisposed()) { 306 mResultText.setVisible(true); 307 String lastText = mResultText.getText(); 308 if (lastText != null && 309 lastText.length() > 0 && 310 !lastText.endsWith("\n") && //$NON-NLS-1$ 311 !info.startsWith("\n")) { //$NON-NLS-1$ 312 mResultText.append("\n"); //$NON-NLS-1$ 313 } 314 mResultText.append(info); 315 } 316 } 317 }); 318 } 319 } 320 321 @Override logError(String info)322 public void logError(String info) { 323 log(info); 324 } 325 326 @Override logVerbose(String info)327 public void logVerbose(String info) { 328 log(info); 329 } 330 331 /** 332 * Sets the max value of the progress bar. 333 * This method can be invoked from a non-UI thread. 334 * 335 * @see ProgressBar#setMaximum(int) 336 */ 337 @Override setProgressMax(final int max)338 public void setProgressMax(final int max) { 339 if (!mDialogShell.isDisposed()) { 340 mDialogShell.getDisplay().syncExec(new Runnable() { 341 @Override 342 public void run() { 343 if (!mProgressBar.isDisposed()) { 344 mProgressBar.setMaximum(max); 345 } 346 } 347 }); 348 } 349 } 350 351 /** 352 * Sets the current value of the progress bar. 353 * This method can be invoked from a non-UI thread. 354 */ 355 @Override setProgress(final int value)356 public void setProgress(final int value) { 357 if (!mDialogShell.isDisposed()) { 358 mDialogShell.getDisplay().syncExec(new Runnable() { 359 @Override 360 public void run() { 361 if (!mProgressBar.isDisposed()) { 362 mProgressBar.setSelection(value); 363 } 364 } 365 }); 366 } 367 } 368 369 /** 370 * Returns the current value of the progress bar, 371 * between 0 and up to {@link #setProgressMax(int)} - 1. 372 * This method can be invoked from a non-UI thread. 373 */ 374 @Override getProgress()375 public int getProgress() { 376 final int[] result = new int[] { 0 }; 377 378 if (!mDialogShell.isDisposed()) { 379 mDialogShell.getDisplay().syncExec(new Runnable() { 380 @Override 381 public void run() { 382 if (!mProgressBar.isDisposed()) { 383 result[0] = mProgressBar.getSelection(); 384 } 385 } 386 }); 387 } 388 389 return result[0]; 390 } 391 392 /** 393 * Display a yes/no question dialog box. 394 * 395 * This implementation allow this to be called from any thread, it 396 * makes sure the dialog is opened synchronously in the ui thread. 397 * 398 * @param title The title of the dialog box 399 * @param message The error message 400 * @return true if YES was clicked. 401 */ 402 @Override displayPrompt(final String title, final String message)403 public boolean displayPrompt(final String title, final String message) { 404 Display display = mDialogShell.getDisplay(); 405 406 // we need to ask the user what he wants to do. 407 final boolean[] result = new boolean[] { false }; 408 display.syncExec(new Runnable() { 409 @Override 410 public void run() { 411 result[0] = MessageDialog.openQuestion(mDialogShell, title, message); 412 } 413 }); 414 return result[0]; 415 } 416 417 /** 418 * This method opens a pop-up window which requests for User Login and 419 * password. 420 * 421 * @param title The title of the window. 422 * @param message The message to displayed in the login/password window. 423 * @return Returns a {@link Pair} holding the entered login and password. 424 * The information must always be in the following order: 425 * Login,Password. So in order to retrieve the <b>login</b> callers 426 * should retrieve the first element, and the second value for the 427 * <b>password</b>. 428 * If operation is <b>canceled</b> by user the return value must be <b>null</b>. 429 * @see ITaskMonitor#displayLoginCredentialsPrompt(String, String) 430 */ 431 @Override displayLoginCredentialsPrompt( final String title, final String message)432 public UserCredentials displayLoginCredentialsPrompt( 433 final String title, final String message) { 434 Display display = mDialogShell.getDisplay(); 435 436 // open dialog and request login and password 437 GetUserCredentialsTask task = new GetUserCredentialsTask(mDialogShell, title, message); 438 display.syncExec(task); 439 440 return new UserCredentials(task.userName, task.password, task.workstation, task.domain); 441 } 442 443 private static class GetUserCredentialsTask implements Runnable { 444 public String userName = null; 445 public String password = null; 446 public String workstation = null; 447 public String domain = null; 448 449 private Shell mShell; 450 private String mTitle; 451 private String mMessage; 452 GetUserCredentialsTask(Shell shell, String title, String message)453 public GetUserCredentialsTask(Shell shell, String title, String message) { 454 mShell = shell; 455 mTitle = title; 456 mMessage = message; 457 } 458 459 @Override run()460 public void run() { 461 AuthenticationDialog authenticationDialog = new AuthenticationDialog(mShell, 462 mTitle, mMessage); 463 int dlgResult= authenticationDialog.open(); 464 if(dlgResult == GridDialog.OK) { 465 userName = authenticationDialog.getLogin(); 466 password = authenticationDialog.getPassword(); 467 workstation = authenticationDialog.getWorkstation(); 468 domain = authenticationDialog.getDomain(); 469 } 470 } 471 } 472 473 /** 474 * Starts the thread that runs the task. 475 * This is deferred till the UI is created. 476 */ startThread(Thread taskThread)477 private void startThread(Thread taskThread) { 478 if (taskThread != null) { 479 taskThread.start(); 480 } 481 } 482 483 /** 484 * Centers the dialog in its parent shell. 485 */ positionShell()486 private void positionShell() { 487 // Centers the dialog in its parent shell 488 Shell child = mDialogShell; 489 Shell parent = getParent(); 490 if (child != null && parent != null) { 491 492 // get the parent client area with a location relative to the display 493 Rectangle parentArea = parent.getClientArea(); 494 Point parentLoc = parent.getLocation(); 495 int px = parentLoc.x; 496 int py = parentLoc.y; 497 int pw = parentArea.width; 498 int ph = parentArea.height; 499 500 // Reuse the last size if there's one, otherwise use the default 501 Point childSize = sLastSize != null ? sLastSize : child.getSize(); 502 int cw = childSize.x; 503 int ch = childSize.y; 504 505 int x = px + (pw - cw) / 2; 506 if (x < 0) x = 0; 507 508 int y = py + (ph - ch) / 2; 509 if (y < MIN_Y) y = MIN_Y; 510 511 child.setLocation(x, y); 512 child.setSize(cw, ch); 513 } 514 } 515 516 // End of hiding from SWT Designer 517 //$hide<<$ 518 } 519