• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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