--- a/api.progress/apichanges.xml Fri Aug 24 12:23:36 2012 +0200 +++ a/api.progress/apichanges.xml Tue Aug 28 12:04:44 2012 +0200 @@ -106,8 +106,20 @@ + + + + ProgressUtils class with runOffEventThreadWithCustomDialogContent and runOffEventThreadWithProgressDialog methods were added. + + + + + ProgressUtils class with runOffEventThreadWithCustomDialogContent and runOffEventThreadWithProgressDialog methods were added. These methods allow movement of operations out of AWT thread, showing the waint cursor after one second and a dialog when task is not finished in a three seconds. + + + + - Modal progress dialogs --- a/api.progress/manifest.mf Fri Aug 24 12:23:36 2012 +0200 +++ a/api.progress/manifest.mf Tue Aug 28 12:04:44 2012 +0200 @@ -3,5 +3,5 @@ OpenIDE-Module-Localizing-Bundle: org/netbeans/progress/module/resources/Bundle.properties OpenIDE-Module-Recommends: org.netbeans.modules.progress.spi.ProgressUIWorkerProvider, org.netbeans.modules.progress.spi.RunOffEDTProvider AutoUpdate-Essential-Module: true -OpenIDE-Module-Specification-Version: 1.29 +OpenIDE-Module-Specification-Version: 1.30 --- a/api.progress/src/org/netbeans/api/progress/ProgressUtils.java Fri Aug 24 12:23:36 2012 +0200 +++ a/api.progress/src/org/netbeans/api/progress/ProgressUtils.java Tue Aug 28 12:04:44 2012 +0200 @@ -47,9 +47,11 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.netbeans.modules.progress.spi.RunOffEDTProvider; import org.netbeans.modules.progress.spi.RunOffEDTProvider.Progress; +import org.netbeans.modules.progress.spi.RunOffEDTProvider.Progress2; import org.openide.util.Lookup; import org.openide.util.RequestProcessor; import org.openide.util.RequestProcessor.Task; @@ -140,6 +142,87 @@ } /** + * Runs operation out of the event thread, blocking the whole UI. When + * operation takes more than 1s, the method first displays wait cursor. + * If operation will not end in 3s interval, modal dialog with + * progress is shown up. + * If operation is marked with {@link org.openide.util.Cancellable} + * interface, cancel button is part of dialog and can be used + * to interrupt the operation. + * + * @param operation task to perform in the background + * @param dialogTitle dialog title + * @param progress progress handle. Do not invoke any methods before + * passing to this method. Start/progress/finish it + * only in {@code operation} + * @param includeDetailLabel show progress detail label in the dialog + * @param waitCursorAfter amount of time, in milliseconds, after which wait + * cursor is shown + * @param dialogAfter amount of time, in milliseconds, after which dialog + * is shown + * + * @since 1.30 + */ + public static void runOffEventThreadWithProgressDialog( + final Runnable operation, + final String dialogTitle, + final ProgressHandle progress, + final boolean includeDetailLabel, + int waitCursorAfter, + int dialogAfter) + { + if (PROVIDER instanceof Progress2) { + Progress2 p = (Progress2) PROVIDER; + p.runOffEventThreadWithProgressDialog(operation, dialogTitle, progress, includeDetailLabel, waitCursorAfter, dialogAfter); + } else { + PROVIDER.runOffEventDispatchThread(operation, progress.getDisplayName(), + new AtomicBoolean(false), + true, + DISPLAY_WAIT_CURSOR_MS, + DISPLAY_DIALOG_MS); + } + } + + /** + * Runs operation out of the event thread, blocking the whole UI. When + * operation takes more than 1s, the method first displays wait cursor. + * If operation will not end up in 3s interval, modal dialog with + * {@code content} panel is shown. + * If operation is marked with {@link org.openide.util.Cancellable} + * interface, cancel button is part of dialog and can be used to + * interrupt the operation. + * + * @param operation task to perform in the background + * @param dialogTitle dialog title + * @param content panel to be shown in the dialog + * @param waitCursorAfter amount of time, in milliseconds, after which wait + * cursor is shown + * @param dialogAfter amount of time, in milliseconds, after which dialog + * is shown + * + * @since 1.30 + */ + public static void runOffEventThreadWithCustomDialogContent( + final Runnable operation, + final String dialogTitle, + final JPanel content, + int waitCursorAfter, + int dialogAfter) + { + if (PROVIDER instanceof Progress2) { + Progress2 p = (Progress2) PROVIDER; + p.runOffEventThreadWithCustomDialogContent(operation, dialogTitle, content, waitCursorAfter, dialogAfter); + } else { + PROVIDER.runOffEventDispatchThread(operation, + dialogTitle, + new AtomicBoolean(false), + true, + DISPLAY_WAIT_CURSOR_MS, + DISPLAY_DIALOG_MS); + } + } + + /** * Show a modal progress dialog that blocks the main window, while running * the passed runnable on a background thread. *

@@ -249,6 +332,7 @@ private static class Trivial implements RunOffEDTProvider { private static final RequestProcessor WORKER = new RequestProcessor(ProgressUtils.class.getName()); + @Override public void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, boolean waitForCanceled, int waitCursorAfter, int dialogAfter) { if (SwingUtilities.isEventDispatchThread()) { Task t = WORKER.post(operation); --- a/api.progress/src/org/netbeans/modules/progress/spi/RunOffEDTProvider.java Fri Aug 24 12:23:36 2012 +0200 +++ a/api.progress/src/org/netbeans/modules/progress/spi/RunOffEDTProvider.java Tue Aug 28 12:04:44 2012 +0200 @@ -43,6 +43,7 @@ import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; +import javax.swing.JPanel; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressRunnable; @@ -116,4 +117,64 @@ */ public Future showProgressDialogAndRunLater (ProgressRunnable toRun, ProgressHandle handle, boolean includeDetailLabel); } + + public interface Progress2 extends Progress { + + /** + * Runs operation out of the event thread, blocking the whole UI. When + * operation takes more than 1s, the method first displays wait cursor. + * If operation will not end in 3s interval, modal dialog with + * progress is shown up. + * If operation is marked with {@link org.openide.util.Cancellable} + * interface, cancel button is part of dialog and can be used + * to interrupt the operation. + * + * @param operation task to perform in the background + * @param dialogTitle dialog title + * @param progress progress handle. Do not invoke any methods before + * passing to this method. Start/progress/finish it + * only in {@code operation} + * @param includeDetailLabel show progress detail label in the dialog + * @param waitCursorAfter amount of time, in milliseconds, after which + * wait cursor is shown + * @param dialogAfter amount of time, in milliseconds, after which + * dialog is shown + * + * @since 1.30 + */ + public void runOffEventThreadWithProgressDialog( + final Runnable operation, + final String operationDescr, + final ProgressHandle progress, + final boolean includeDetailLabel, + int waitCursorAfter, + int dialogAfter); + + + /** + * Runs operation out of the event thread, blocking the whole UI. When + * operation takes more than 1s, the method first displays wait cursor. + * If operation will not end up in 3s interval, modal dialog with + * {@code content} panel is shown. + * If operation is marked with {@link org.openide.util.Cancellable} + * interface, cancel button is part of dialog and can be used to + * interrupt the operation. + * + * @param operation task to perform in the background + * @param dialogTitle dialog title + * @param content panel to be shown in the dialog + * @param waitCursorAfter amount of time, in milliseconds, after which + * wait cursor is shown + * @param dialogAfter amount of time, in milliseconds, after which + * dialog is shown + * + * @since 1.30 + */ + public void runOffEventThreadWithCustomDialogContent( + final Runnable operation, + final String dialogTitle, + final JPanel content, + int waitCursorAfter, + int dialogAfter); + } } --- a/progress.ui/nbproject/project.xml Fri Aug 24 12:23:36 2012 +0200 +++ a/progress.ui/nbproject/project.xml Tue Aug 28 12:04:44 2012 +0200 @@ -11,7 +11,7 @@ 1 - 1.19 + 1.30 --- a/progress.ui/src/org/netbeans/modules/progress/ui/RunOffEDTImpl.java Fri Aug 24 12:23:36 2012 +0200 +++ a/progress.ui/src/org/netbeans/modules/progress/ui/RunOffEDTImpl.java Tue Aug 28 12:04:44 2012 +0200 @@ -64,14 +64,11 @@ import org.netbeans.api.progress.ProgressUtils; import org.netbeans.modules.progress.spi.RunOffEDTProvider; import org.netbeans.modules.progress.spi.RunOffEDTProvider.Progress; +import org.netbeans.modules.progress.spi.RunOffEDTProvider.Progress2; import org.openide.DialogDescriptor; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; -import org.openide.util.Cancellable; -import org.openide.util.Exceptions; -import org.openide.util.NbBundle; -import org.openide.util.Parameters; -import org.openide.util.RequestProcessor; +import org.openide.util.*; import org.openide.util.RequestProcessor.Task; import org.openide.util.lookup.ServiceProvider; import org.openide.windows.WindowManager; @@ -81,9 +78,11 @@ * @author Jan Lahoda, Tomas Holy */ @ServiceProvider(service=RunOffEDTProvider.class, position = 100) -public class RunOffEDTImpl implements RunOffEDTProvider, Progress { +public class RunOffEDTImpl implements RunOffEDTProvider, Progress, Progress2 { private static final RequestProcessor WORKER = new RequestProcessor(ProgressUtils.class.getName()); + private static final RequestProcessor TI_WORKER = new RequestProcessor("TI_" + ProgressUtils.class.getName(), 1, true); + private static final Map CUMULATIVE_SPENT_TIME = new HashMap(); private static final Map MAXIMAL_SPENT_TIME = new HashMap(); private static final Map INVOCATION_COUNT = new HashMap(); @@ -216,10 +215,94 @@ } } } + + @Override + public void runOffEventThreadWithCustomDialogContent(Runnable operation, String dialogTitle, JPanel content, int waitCursorAfter, int dialogAfter) + { + runOffEventThreadCustomDialogImpl(operation, dialogTitle, content, waitCursorAfter, dialogAfter); + } + @Override + public void runOffEventThreadWithProgressDialog(final Runnable operation, final String operationDescr, + ProgressHandle handle, boolean includeDetailLabel, int waitCursorAfter, int dialogAfter) { + JPanel content = contentPanel(handle, includeDetailLabel); + runOffEventThreadCustomDialogImpl(operation, operationDescr, content, waitCursorAfter, dialogAfter); + } + + private void runOffEventThreadCustomDialogImpl(final Runnable operation, final String operationDescr, + final JPanel contentPanel, int waitCursorAfter, int dialogAfter) { + if (waitCursorAfter < 0) waitCursorAfter = 1000; + if (dialogAfter < 0) dialogAfter = 2000; + + final CountDownLatch latch = new CountDownLatch(1); + final AtomicReference

d = new AtomicReference(); + final AtomicReference t = new AtomicReference(); + + JDialog dialog = createModalDialog(operation, operationDescr, contentPanel, d, t, operation instanceof Cancellable); + + final Task rt = TI_WORKER.post(new Runnable() { + + public @Override void run() { + try { + operation.run(); + } finally { + latch.countDown(); + + SwingUtilities.invokeLater(new Runnable() { + + public @Override void run() { + Dialog dd = d.get(); + if (dd != null) { + dd.setVisible(false); + dd.dispose(); + } + } + }); + } + } + }); + t.set(rt); + + Window window = null; + Component glassPane = null; + Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + if (focusOwner != null) { + window = SwingUtilities.getWindowAncestor(focusOwner); + if (window != null) { + RootPaneContainer root = (RootPaneContainer) SwingUtilities.getAncestorOfClass(RootPaneContainer.class, focusOwner); + glassPane = root.getGlassPane(); + } + } + if (window == null || glassPane == null) { + window = WindowManager.getDefault().getMainWindow(); + glassPane = ((JFrame) window).getGlassPane(); + } + if (waitMomentarily(glassPane, null, waitCursorAfter, latch, window)) { + return; + } + + Cursor wait = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); + + if (waitMomentarily(glassPane, wait, dialogAfter, latch, window)) { + return; + } + + d.set(dialog); + if (EventQueue.isDispatchThread()) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + d.get().setVisible(true); + } + }); + } else { + d.get().setVisible(true); + } + } + private static boolean waitMomentarily(Component glassPane, Cursor wait, int timeout, final CountDownLatch l, Window window) { Cursor originalWindow = window.getCursor(); - Cursor originalGlass = glassPane.getCursor(); + Cursor originalGlass = glassPane.getCursor(); try { if (wait != null) { @@ -412,4 +495,110 @@ } } + + private static JPanel contentPanel(final ProgressHandle handle, boolean includeDetail) { + // top panel + JPanel contentPanel = new JPanel(new GridBagLayout()); + + // main label + GridBagConstraints gridBagConstraints = new GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START; + + JLabel mainLabel = ProgressHandleFactory.createMainLabelComponent(handle); + Font f = mainLabel.getFont(); + if (f != null) { + mainLabel.setFont(f.deriveFont(Font.BOLD)); + } + contentPanel.add(mainLabel, gridBagConstraints); + + // progress bar + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + JComponent progressBar = ProgressHandleFactory.createProgressComponent(handle); + contentPanel.add (progressBar, gridBagConstraints); + + if (includeDetail) { + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.insets = new java.awt.Insets(5, 5, 0, 0); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 2; + gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_START; + JLabel details = ProgressHandleFactory.createDetailLabelComponent(handle); + contentPanel.add(details, gridBagConstraints); + } + + // empty panel - for correct resizing + JPanel emptyPanel = new JPanel(); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = includeDetail ? 3 : 2; + gridBagConstraints.weighty = 2.0; + gridBagConstraints.weightx = 2.0; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + contentPanel.add(emptyPanel, gridBagConstraints); + + return contentPanel; + } + + private static JDialog createModalDialog( + final Runnable operation, + final String title, + final JPanel content, + final AtomicReference d, + final AtomicReference task, + final boolean cancelAvail) + { + assert EventQueue.isDispatchThread(); + + JPanel panel = new JPanel(new GridBagLayout()); + + GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 0; + gridBagConstraints.weightx = 1.0; + gridBagConstraints.weighty = 1.0; + gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH; + + panel.add(content, gridBagConstraints); + + if (cancelAvail) { + JPanel buttonsPanel = new JPanel(); + buttonsPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); + String cancelButton = NbBundle.getMessage(RunOffEDTImpl.class, "RunOffAWT.BTN_Cancel"); //NOI18N + JButton cancel = new JButton(cancelButton); + cancel.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + if (operation instanceof Cancellable) { + ((Cancellable) operation).cancel(); + task.get().cancel(); + d.get().setVisible(false); + d.get().dispose(); + } + } + }); + buttonsPanel.add(cancel); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 0; + gridBagConstraints.gridy = 1; + gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL; + gridBagConstraints.weightx = 1.0; + panel.add(buttonsPanel, gridBagConstraints); + } + + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + final JDialog result = new JDialog(mainWindow, title, true); + result.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + result.setSize(400, 150); + result.setContentPane(panel); + result.setLocationRelativeTo(WindowManager.getDefault().getMainWindow()); + return result; + } }