diff --git a/api.progress/apichanges.xml b/api.progress/apichanges.xml --- a/api.progress/apichanges.xml +++ b/api.progress/apichanges.xml @@ -105,6 +105,17 @@ + + + RunOffAWT added. + + + + + RunOffAWT was added to allow movement of operations out of AWT thread while blocking UI. + + + Add ProgressHandle.suspend(String) method for visual suspend of a running task. diff --git a/api.progress/manifest.mf b/api.progress/manifest.mf --- a/api.progress/manifest.mf +++ b/api.progress/manifest.mf @@ -3,6 +3,6 @@ OpenIDE-Module-Localizing-Bundle: org/netbeans/progress/module/resources/Bundle.properties OpenIDE-Module-Layer: org/netbeans/progress/module/resources/layer.xml OpenIDE-Module-Implementation-Version: 1 -OpenIDE-Module-Recommends: org.netbeans.progress.spi.ProgressUIWorkerProvider +OpenIDE-Module-Recommends: org.netbeans.progress.spi.ProgressUIWorkerProvider, org.netbeans.progress.spi.RunOffAWTProvider AutoUpdate-Essential-Module: true diff --git a/api.progress/nbproject/project.xml b/api.progress/nbproject/project.xml --- a/api.progress/nbproject/project.xml +++ b/api.progress/nbproject/project.xml @@ -63,9 +63,9 @@ - - - unit + + + unit org.netbeans.libs.junit4 @@ -75,13 +75,13 @@ - - org.netbeans.modules.progress.ui - - - - - + + org.netbeans.modules.progress.ui + + + + + org.netbeans.api.progress org.netbeans.api.progress.aggregate diff --git a/api.progress/src/org/netbeans/api/progress/ProgressUtils.java b/api.progress/src/org/netbeans/api/progress/ProgressUtils.java new file mode 100644 --- /dev/null +++ b/api.progress/src/org/netbeans/api/progress/ProgressUtils.java @@ -0,0 +1,115 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.api.progress; + +import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.SwingUtilities; +import org.netbeans.progress.spi.RunOffEDTProvider; +import org.openide.util.Lookup; +import org.openide.util.RequestProcessor; +import org.openide.util.RequestProcessor.Task; + +/** + * Useful static methods + * @author Tomas Holy + * @since 1.16 + */ +public final class ProgressUtils { + private static final RunOffEDTProvider PROVIDER = getProvider(); + private static final int DISPLAY_DIALOG_MS = 9450; + private static final int DISPLAY_WAIT_CURSOR_MS = 50; + + private ProgressUtils() { + } + + private static RunOffEDTProvider getProvider() { + RunOffEDTProvider p = Lookup.getDefault().lookup(RunOffEDTProvider.class); + return p != null ? p : new Trivial(); + } + + /** + * Runs operation out of event dispatch thread, blocks UI while operation is in progress. First it shows + * wait cursor after ~50ms elapses, if operation takes longer than ~10s a dialog with Cancel button is shown. + * If cancelled operation is not finished in 1s ISE is thrown. + *

+ * This method is supposed to be used by user invoked foreground actions, that are expected to run very fast in vast majority of cases. + * However, in some rather rare cases (e.g. extensive IO operations in progress), supplied operation may need longer time. In such case + * this method first displays wait cursor and if operation takes even more time it displays dialog allowing to cancel operation. + * DO NOT use this method for operations that may take long time under normal circumstances! + * @param operation operation to perform + * @param operationDescr text shown in dialog + * @param cancelOperation set to true if user cancelled the operation + */ + public static void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation) { + PROVIDER.runOffEventDispatchThread(operation, operationDescr, cancelOperation, DISPLAY_WAIT_CURSOR_MS, DISPLAY_DIALOG_MS); + } + + /** + * Runs operation out of event dispatch thread, blocks UI while operation is in progress. First it shows + * wait cursor after waitCursorAfter elapses, if operation takes longer than dialogAfter a dialog with Cancel button is shown. + * If cancelled operation is not finished in 1s ISE is thrown. + *

+ * This method is supposed to be used by user invoked foreground actions, that are expected to run very fast in vast majority of cases. + * However, in some rather rare cases (e.g. extensive IO operations in progress), supplied operation may need longer time. In such case + * this method first displays wait cursor and if operation takes even more time it displays dialog allowing to cancel operation. + * DO NOT use this method for operations that may take long time under normal circumstances! + * @param operation operation to perform + * @param operationDescr text shown in dialog + * @param cancelOperation set to true if user cancelled the operation + * @param waitCursorAfter time in ms after which wait cursor is shown + * @param dialogAfter time in ms after which dialog with "Cancel" button is shown + */ + public static void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, int waitCursorAfter, int dialogAfter) { + PROVIDER.runOffEventDispatchThread(operation, operationDescr, cancelOperation, waitCursorAfter, dialogAfter); + } + + private static class Trivial implements RunOffEDTProvider { + private static final RequestProcessor WORKER = new RequestProcessor(ProgressUtils.class.getName()); + + public void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, int waitCursorAfter, int dialogAfter) { + if (SwingUtilities.isEventDispatchThread()) { + Task t = WORKER.post(operation); + t.waitFinished(); + } else { + operation.run(); + } + } + } +} diff --git a/api.progress/src/org/netbeans/progress/spi/RunOffEDTProvider.java b/api.progress/src/org/netbeans/progress/spi/RunOffEDTProvider.java new file mode 100644 --- /dev/null +++ b/api.progress/src/org/netbeans/progress/spi/RunOffEDTProvider.java @@ -0,0 +1,50 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.progress.spi; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Interface for ProgressUtils.runOffEventDispatchThread() methods + * @author Tomas Holy + */ +public interface RunOffEDTProvider { + void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, int waitCursorAfter, int dialogAfter); +} diff --git a/api.progress/test/unit/src/org/netbeans/api/progress/RunOffEDTTest.java b/api.progress/test/unit/src/org/netbeans/api/progress/RunOffEDTTest.java new file mode 100644 --- /dev/null +++ b/api.progress/test/unit/src/org/netbeans/api/progress/RunOffEDTTest.java @@ -0,0 +1,272 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ +package org.netbeans.api.progress; + +import java.awt.KeyboardFocusManager; +import java.awt.Window; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import javax.swing.SwingUtilities; +import org.netbeans.junit.Log; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Exceptions; + +/** + * + * @author Tomas Holy + */ +public class RunOffEDTTest extends NbTestCase { + + { + System.setProperty("org.netbeans.modules.progress.ui.WARNING_TIME", "1000"); + } + + public RunOffEDTTest(String name) { + super(name); + } + + @Override + protected boolean runInEQ() { + return false; + } + + @Override + protected int timeOut() { + return 30000; + } + + private static class R implements Runnable { + + int runCount; + Thread runT; + CountDownLatch l; + + public void run() { + runCount++; + runT = Thread.currentThread(); + if (l != null) { + try { + l.await(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + } + } + } + + public void testOutOfEDTRunsImmediately() { + R r = new R(); + ProgressUtils.runOffEventDispatchThread(r, "Simple", new AtomicBoolean()); + assertSame("Should be invoked by calling thread", Thread.currentThread(), r.runT); + assertEquals("Should run once", 1, r.runCount); + } + + public void testCallerBlockedUntilFinished() throws Exception { + final AtomicBoolean passed = new AtomicBoolean(false); + SwingUtilities.invokeAndWait(new Runnable() { + + public void run() { + final AtomicBoolean finished = new AtomicBoolean(false); + final int[] cnt = new int[]{0}; + final Thread[] t = new Thread[]{null}; + Runnable r = new Runnable() { + + public void run() { + cnt[0]++; + t[0] = Thread.currentThread(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + finished.set(true); + } + }; + ProgressUtils.runOffEventDispatchThread(r, "Test", new AtomicBoolean(false)); + passed.set(finished.get() && cnt[0] == 1 && t[0] != Thread.currentThread()); + } + }); + assertTrue(passed.get()); + } + + public void testCallerBlockedUntilCancelledOperationFinished() throws Exception { + final R r = new R(); + r.l = new CountDownLatch(1); + + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + ProgressUtils.runOffEventDispatchThread(r, "Test", new AtomicBoolean(false), 10, 100); + } + }); + for (int i = 0; i < 100; i++) { + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); + if (w != null) { + w.setVisible(false); + break; + } + } + + final AtomicBoolean finished = new AtomicBoolean(false); + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + finished.set(true); + } + }); + Thread.sleep(100); + assertFalse("should not run yet", finished.get()); + r.l.countDown(); + SwingUtilities.invokeAndWait(new Runnable() { + + public void run() { + } + }); + assertTrue("should be finished now", finished.get()); + assertEquals("Should run once", 1, r.runCount); + } + + public void testISEThrownIfCancelledOperationNotFinishedInTime() throws Exception { + final R r = new R(); + r.l = new CountDownLatch(1); + final AtomicBoolean ex = new AtomicBoolean(false); + + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + try { + ProgressUtils.runOffEventDispatchThread(r, "testExceptionIfCancelledOperationNotFinishedInTime", new AtomicBoolean(false), 10, 500); + } catch (IllegalStateException e) { + ex.set(true); + } + } + }); + Window w = null; + for (int i = 0; i < 100; i++) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Exceptions.printStackTrace(e); + } + w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); + if (w != null) { + w.setVisible(false); + break; + } + } + assertNotNull(w); + Thread.sleep(1100); + assertTrue("ISE should be thrown", ex.get()); + r.l.countDown(); + SwingUtilities.invokeAndWait(new Runnable() { + + public void run() { + } + }); + } + + public void testDlgIsShown() throws Exception { + final R r = new R(); + r.l = new CountDownLatch(1); + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + ProgressUtils.runOffEventDispatchThread(r, "Test", new AtomicBoolean(false), 10, 100); + } + }); + for (int i = 0; i < 100; i++) { + Thread.sleep(100); + Window w = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); + if (w != null) { + r.l.countDown(); + return; + } + } + fail("Dialog was not shown"); + } + + public void testNoWarningMsg() throws Exception { + CharSequence s = Log.enable("org.netbeans.modules.progress.ui", Level.WARNING); + SwingUtilities.invokeAndWait(new Runnable() { + + public void run() { + ProgressUtils.runOffEventDispatchThread(new SR(1000), "Test", new AtomicBoolean(false)); + ProgressUtils.runOffEventDispatchThread(new SR(10), "Test", new AtomicBoolean(false)); + ProgressUtils.runOffEventDispatchThread(new SR(100), "Test", new AtomicBoolean(false)); + } + }); + assertFalse("Warning should be logged", s.toString().indexOf("Operation is too slow") >= 0); + } + + public void testWarningMsgIfOperationLengthyTooOften() throws Exception { + CharSequence s = Log.enable("org.netbeans.modules.progress.ui", Level.WARNING); + SwingUtilities.invokeAndWait(new Runnable() { + + public void run() { + Runnable r = new SR(1000); + ProgressUtils.runOffEventDispatchThread(r, "Test", new AtomicBoolean(false)); + ProgressUtils.runOffEventDispatchThread(r, "Test", new AtomicBoolean(false)); + } + }); + assertTrue("Warning should be logged", s.toString().indexOf("Operation is too slow") >= 0); + } + + private static class SR implements Runnable { + + long sleepTime; + + public SR(long sleepTime) { + this.sleepTime = sleepTime; + } + + public void run() { + try { + Thread.sleep(sleepTime); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } + } + }; +} diff --git a/progress.ui/manifest.mf b/progress.ui/manifest.mf --- a/progress.ui/manifest.mf +++ b/progress.ui/manifest.mf @@ -2,6 +2,6 @@ OpenIDE-Module: org.netbeans.modules.progress.ui OpenIDE-Module-Layer: org/netbeans/modules/progress/ui/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/progress/ui/Bundle.properties -OpenIDE-Module-Provides: org.netbeans.progress.spi.ProgressUIWorkerProvider +OpenIDE-Module-Provides: org.netbeans.progress.spi.ProgressUIWorkerProvider, org.netbeans.progress.spi.RunOffAWTProvider AutoUpdate-Essential-Module: true diff --git a/progress.ui/src/org/netbeans/modules/progress/ui/Bundle.properties b/progress.ui/src/org/netbeans/modules/progress/ui/Bundle.properties --- a/progress.ui/src/org/netbeans/modules/progress/ui/Bundle.properties +++ b/progress.ui/src/org/netbeans/modules/progress/ui/Bundle.properties @@ -53,3 +53,7 @@ StatusLineComponent.Cancel=Cancel Process StatusLineComponent.View=Show Output ListComponent.Watch=Watch Process + +RunOffAWT.TITLE_Operation=Lengthy operation in progress +RunOffAWT.BTN_Cancel=Cancel + diff --git a/progress.ui/src/org/netbeans/modules/progress/ui/RunOffEDTImpl.java b/progress.ui/src/org/netbeans/modules/progress/ui/RunOffEDTImpl.java new file mode 100644 --- /dev/null +++ b/progress.ui/src/org/netbeans/modules/progress/ui/RunOffEDTImpl.java @@ -0,0 +1,187 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ +package org.netbeans.modules.progress.ui; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import org.netbeans.api.progress.ProgressUtils; +import org.netbeans.progress.spi.RunOffEDTProvider; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.openide.util.Parameters; +import org.openide.util.RequestProcessor; +import org.openide.windows.WindowManager; + +/** + * Default RunOffEDTProvider implementation for ProgressUtils.runOffEventDispatchThread() methods + * @author Jan Lahoda, Tomas Holy + */ +@org.openide.util.lookup.ServiceProvider(service = org.netbeans.progress.spi.RunOffEDTProvider.class, position = 100) +public class RunOffEDTImpl implements RunOffEDTProvider { + + private static final RequestProcessor WORKER = new RequestProcessor(ProgressUtils.class.getName()); + private static final Map, Integer> OPERATIONS = new WeakHashMap, Integer>(); + private static final int CLEAR_TIME = 100; + private static final int CANCEL_TIME = 1000; + private static final int WARNING_TIME = Integer.getInteger("org.netbeans.modules.progress.ui.WARNING_TIME", 10000); + private static final Logger LOG = Logger.getLogger(RunOffEDTImpl.class.getName()); + + public void runOffEventDispatchThread(final Runnable operation, final String operationDescr, final AtomicBoolean cancelOperation, int waitCursorTime, int dlgTime) { + Parameters.notNull("operation", operation); + Parameters.notNull("cancelOperation", cancelOperation); + if (!SwingUtilities.isEventDispatchThread()) { + operation.run(); + return; + } + long startTime = System.currentTimeMillis(); + runOffEventDispatchThreadImpl(operation, operationDescr, cancelOperation, waitCursorTime, dlgTime); + int elapsed = (int) (System.currentTimeMillis() - startTime); + Class clazz = operation.getClass(); + synchronized (OPERATIONS) { + if (elapsed < CLEAR_TIME) { + OPERATIONS.remove(clazz); + } else { + Integer prevElapsed = OPERATIONS.get(operation.getClass()); + if (prevElapsed != null && elapsed + prevElapsed > WARNING_TIME) { + LOG.log(Level.WARNING, "Operation is too slow", new Exception(clazz + " is too slow")); + } + OPERATIONS.put(clazz, elapsed); + } + } + } + + private void runOffEventDispatchThreadImpl(final Runnable operation, final String operationDescr, final AtomicBoolean cancelOperation, int waitCursorTime, int dlgTime) { + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference

d = new AtomicReference(); + + WORKER.post(new Runnable() { + + public void run() { + if (cancelOperation.get()) { + return; + } + operation.run(); + latch.countDown(); + + SwingUtilities.invokeLater(new Runnable() { + + public void run() { + Dialog dd = d.get(); + if (dd != null) { + dd.setVisible(false); + } + } + }); + } + }); + + Component glassPane = ((JFrame) WindowManager.getDefault().getMainWindow()).getGlassPane(); + + if (waitMomentarily(glassPane, null, waitCursorTime, latch)) { + return; + } + + Cursor wait = org.openide.util.Utilities.createProgressCursor(glassPane); + + if (waitMomentarily(glassPane, wait, dlgTime, latch)) { + return; + } + + String title = NbBundle.getMessage(RunOffEDTImpl.class, "RunOffAWT.TITLE_Operation"); + String cancelButton = NbBundle.getMessage(RunOffEDTImpl.class, "RunOffAWT.BTN_Cancel"); + + DialogDescriptor nd = new DialogDescriptor(operationDescr, title, true, new Object[]{cancelButton}, cancelButton, DialogDescriptor.DEFAULT_ALIGN, null, new ActionListener() { + + public void actionPerformed(ActionEvent e) { + cancelOperation.set(true); + d.get().setVisible(false); + } + }); + + nd.setMessageType(NotifyDescriptor.INFORMATION_MESSAGE); + + d.set(DialogDisplayer.getDefault().createDialog(nd)); + d.get().setVisible(true); + + try { + if (!latch.await(CANCEL_TIME, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException("Cancelled operation did not finish in time."); + } + } catch (InterruptedException ex) { + LOG.log(Level.FINE, null, ex); + } + } + + private static boolean waitMomentarily(Component glassPane, Cursor wait, int timeout, final CountDownLatch l) { + Cursor original = glassPane.getCursor(); + + try { + if (wait != null) { + glassPane.setCursor(wait); + } + + glassPane.setVisible(true); + try { + return l.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException ex) { + LOG.log(Level.FINE, null, ex); + return true; + } + } finally { + glassPane.setVisible(false); + glassPane.setCursor(original); + } + } +}