org.netbeans.api.progress.BaseProgressUtils Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.api.progress;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
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.Cancellable;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.RequestProcessor;
/**
* Contains useful utilities that can function in a non-Swing environment. For
* Swing-related utilities (which use e.g. JComponent and Swing Actions), use
* org.netbeans.api.progress.ProgressUtils from the api.progress.nb module.
*
* @author Tomas Holy
* @since 1.40
*/
public final class BaseProgressUtils {
private static final RunOffEDTProvider PROVIDER = getProvider();
private static final int DISPLAY_DIALOG_MS = 9450;
private static final int DISPLAY_WAIT_CURSOR_MS = 50;
private BaseProgressUtils() {
}
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.
*
* 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 canceled the operation
* @param waitForCanceled true if method should wait until canceled task is finished (if it is not finished in 1s ISE is thrown)
*/
public static void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, boolean waitForCanceled) {
PROVIDER.runOffEventDispatchThread(operation, operationDescr, cancelOperation, waitForCanceled, 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.
*
* 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 canceled the operation
* @param waitForCanceled true if method should wait until canceled task is finished (if it is not finished in 1s ISE is thrown)
* @param waitCursorAfter time in ms after which wait cursor is shown
* @param dialogAfter time in ms after which dialog with "Cancel" button is shown
* @since 1.19
*/
public static void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, boolean waitForCanceled, int waitCursorAfter, int dialogAfter) {
PROVIDER.runOffEventDispatchThread(operation, operationDescr, cancelOperation, waitForCanceled, waitCursorAfter, dialogAfter);
}
/**
* Show a modal progress dialog that blocks the main window and all other
* currently displayed frames or dialogs, while running the passed runnable
* on a background thread.
*
* This method is thread-safe, and will block until the operation has
* completed, regardless of what thread calls this method.
*
* The resulting progress UI should show a cancel button if the passed
* runnable implements org.openide.util.Cancellable.
*
* Unless you are being passed the runnable or progress handle from foreign
* code (such as in WizardDescriptor.progressInstantiatingIterator), it
* is usually simpler to use {@link #showProgressDialogAndRun(ProgressRunnable, String, boolean)}.
*
* @param operation A runnable to run in the background
* @param progress A progress handle to create a progress bar for
* @param includeDetailLabel True if the caller will use
* ProgressHandle.progress (String, int), false if not. If true, the
* created dialog will include a label that shows progress details.
* @since 1.19
*/
public static void showProgressDialogAndRun(Runnable operation, ProgressHandle progress, boolean includeDetailLabel) {
if (PROVIDER instanceof Progress) {
Progress p = (Progress) PROVIDER;
p.showProgressDialogAndRun(operation, progress, includeDetailLabel);
} else {
PROVIDER.runOffEventDispatchThread(operation, progress.getDisplayName(), new AtomicBoolean(false), false, 0, 0);
}
}
/**
* 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);
}
}
/**
* Show a modal progress dialog that blocks the main window and all other
* currently displayed frames or dialogs, while running the passed runnable
* on a background thread.
*
* This method is thread-safe, and will block until the operation has
* completed, regardless of what thread calls this method.
*
* The resulting progress UI should show a cancel button if the passed
* runnable implements org.openide.util.Cancellable.
*
* @param The result type - use Void if no return type needed
* @param operation A runnable-like object which performs work in the
* background, and is passed a ProgressHandle to update progress
* @param displayName The display name for this operation
* @param includeDetailLabel If true, include a lable to show progress
* details (needed only if you plan to call ProgressHandle.setProgress(String, int)
* @return The result of the operation.
* @since 1.19
*/
public static T showProgressDialogAndRun(final ProgressRunnable operation, final String displayName, boolean includeDetailLabel) {
if (PROVIDER instanceof Progress) {
Progress p = (Progress) PROVIDER;
return p.showProgressDialogAndRun(operation, displayName, includeDetailLabel);
} else {
final AtomicReference ref = new AtomicReference();
PROVIDER.runOffEventDispatchThread(new Runnable() {
@Override
public void run() {
try (ProgressHandle handle = ProgressHandle.createHandle(displayName)) {
handle.start();
handle.switchToIndeterminate();
ref.set(operation.run(handle));
}
}
}, displayName, new AtomicBoolean(false), true, 0, 0);
return ref.get();
}
}
/**
* Show a modal progress dialog that blocks the main window and all other
* currently displayed frames or dialogs, while running the passed runnable
* on a background thread with an indeterminate-state progress bar.
*
* This method is thread-safe, and will block until the operation has
* completed, regardless of what thread calls this method.
*
* The resulting progress UI should show a cancel button if the passed
* runnable implements org.openide.util.Cancellable.
* .
* @param operation A runnable to run
* @param displayName The display name of the operation, to show in the dialog
* @since 1.19
*/
public static void showProgressDialogAndRun(Runnable operation, String displayName) {
RunnableWrapper wrapper;
if (operation instanceof Cancellable) {
wrapper = new CancellableRunnableWrapper(operation);
} else {
wrapper = new RunnableWrapper(operation);
}
showProgressDialogAndRun(wrapper, displayName, false);
}
/**
* Show a modal progress dialog that blocks the main window and all other
* currently displayed frames or dialogs while running a background process.
* This call should block until the work is started, and then return a task
* which can be monitored for completion or cancellation. This method will
* not block while the work is run, only until the progress UI is
* initialized.
*
* The resulting progress UI should show a cancel button if the passed
* runnable implements org.openide.util.Cancellable.
*
* @param operation
* @param handle
* @param includeDetailLabel
* @return
*/
public static Future showProgressDialogAndRunLater (final ProgressRunnable operation, final ProgressHandle handle, boolean includeDetailLabel) {
if (PROVIDER instanceof Progress) {
Progress p = (Progress) PROVIDER;
return p.showProgressDialogAndRunLater(operation, handle, includeDetailLabel);
} else {
FutureTask result = new FutureTask(new Callable() {
@Override
public T call() throws Exception {
return operation.run(handle);
}
});
PROVIDER.runOffEventDispatchThread(result, handle.getDisplayName(), new AtomicBoolean(false), true, 0, 0);
return result;
}
}
private static class RunnableWrapper implements ProgressRunnable {
private final Runnable toRun;
RunnableWrapper(Runnable toRun) {
this.toRun = toRun;
}
@Override
public Void run(ProgressHandle handle) {
toRun.run();
return null;
}
}
private static final RequestProcessor TRIVIAL = new RequestProcessor(Trivial.class);
private static final class CancellableRunnableWrapper extends RunnableWrapper implements Cancellable {
private final Cancellable cancelable;
CancellableRunnableWrapper(Runnable toRun) {
super(toRun);
this.cancelable = (Cancellable)toRun;
}
@Override
public boolean cancel() {
return cancelable.cancel();
}
}
private static class Trivial implements RunOffEDTProvider {
@Override
public void runOffEventDispatchThread(Runnable operation, String operationDescr, AtomicBoolean cancelOperation, boolean waitForCanceled, int waitCursorAfter, int dialogAfter) {
if (Mutex.EVENT.isReadAccess()) {
// PENDING: the EDT should continue to service events.
TRIVIAL.post(operation).waitFinished();
} else {
operation.run();
}
}
}
}