| /** |
| * 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.modules.progress.ui; |
| |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CancellationException; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.FutureTask; |
| 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.*; |
| import org.netbeans.api.progress.ProgressHandle; |
| import org.netbeans.api.progress.ProgressHandleFactory; |
| import org.netbeans.api.progress.ProgressRunnable; |
| 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.*; |
| import org.openide.util.RequestProcessor.Task; |
| import org.openide.util.lookup.ServiceProvider; |
| import org.openide.windows.WindowManager; |
| |
| /** |
| * Default RunOffEDTProvider implementation for ProgressUtils.runOffEventDispatchThread() methods |
| * @author Jan Lahoda, Tomas Holy |
| */ |
| @ServiceProvider(service=RunOffEDTProvider.class, position = 100) |
| public class RunOffEDTImpl implements RunOffEDTProvider, Progress, Progress2 { |
| |
| private static final RequestProcessor TI_WORKER = new RequestProcessor("TI_" + ProgressUtils.class.getName(), 1, true); |
| |
| private static final Map<String, Long> CUMULATIVE_SPENT_TIME = new HashMap<String, Long>(); |
| private static final Map<String, Long> MAXIMAL_SPENT_TIME = new HashMap<String, Long>(); |
| private static final Map<String, Integer> INVOCATION_COUNT = new HashMap<String, Integer>(); |
| 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()); |
| |
| //@GuardedBy("rqByClz") |
| private final Map<Class<?>,Pair<Integer,RequestProcessor>> rqByClz = new HashMap<Class<?>, Pair<Integer, RequestProcessor>>(); |
| private final boolean assertionsOn; |
| |
| @Override |
| public void runOffEventDispatchThread(final Runnable operation, final String operationDescr, |
| final AtomicBoolean cancelOperation, boolean waitForCanceled, int waitCursorTime, int dlgTime) { |
| Parameters.notNull("operation", operation); //NOI18N |
| Parameters.notNull("cancelOperation", cancelOperation); //NOI18N |
| if (!SwingUtilities.isEventDispatchThread()) { |
| operation.run(); |
| return; |
| } |
| long startTime = System.currentTimeMillis(); |
| runOffEventDispatchThreadImpl(operation, operationDescr, cancelOperation, waitForCanceled, waitCursorTime, dlgTime); |
| long elapsed = System.currentTimeMillis() - startTime; |
| |
| if (assertionsOn) { |
| String clazz = operation.getClass().getName(); |
| Long cumulative = CUMULATIVE_SPENT_TIME.get(clazz); |
| if (cumulative == null) { |
| cumulative = 0L; |
| } |
| cumulative += elapsed; |
| CUMULATIVE_SPENT_TIME.put(clazz, cumulative); |
| Long maximal = MAXIMAL_SPENT_TIME.get(clazz); |
| if (maximal == null) { |
| maximal = 0L; |
| } |
| if (elapsed > maximal) { |
| maximal = elapsed; |
| MAXIMAL_SPENT_TIME.put(clazz, maximal); |
| } |
| Integer count = INVOCATION_COUNT.get(clazz); |
| if (count == null) { |
| count = 0; |
| } |
| count++; |
| INVOCATION_COUNT.put(clazz, count); |
| |
| if (elapsed > WARNING_TIME) { |
| LOG.log(Level.WARNING, "Lengthy operation: {0}:{1}:{2}:{3}:{4}", new Object[] { |
| clazz, cumulative, count, maximal, String.format("%3.2f", ((double) cumulative) / count)}); |
| } |
| } |
| } |
| |
| private void runOffEventDispatchThreadImpl(final Runnable operation, final String operationDescr, |
| final AtomicBoolean cancelOperation, boolean waitForCanceled, int waitCursorTime, int dlgTime) { |
| final CountDownLatch latch = new CountDownLatch(1); |
| final AtomicReference<Dialog> d = new AtomicReference<Dialog>(); |
| RequestProcessor rp; |
| synchronized (rqByClz) { |
| final Class<?> clz = operation.getClass(); |
| int index; |
| Pair<Integer,RequestProcessor> p = rqByClz.get(clz); |
| if (p == null) { |
| index = 0; |
| rp = new RequestProcessor(String.format( |
| "%s for: %s", //NOI18N |
| ProgressUtils.class.getName(), |
| clz.getName()), |
| 1, |
| false); |
| } else { |
| index = p.first(); |
| rp = p.second(); |
| } |
| p = Pair.<Integer,RequestProcessor>of(index+1, rp); |
| rqByClz.put(clz, p); |
| } |
| rp.post(new Runnable() { |
| public @Override void run() { |
| if (cancelOperation.get()) { |
| return; |
| } |
| try { |
| operation.run(); |
| } finally { |
| synchronized (rqByClz) { |
| final Class<?> clz = operation.getClass(); |
| Pair<Integer,RequestProcessor> p = rqByClz.remove(clz); |
| if (p.first() > 1) { |
| rqByClz.put(clz, Pair.<Integer,RequestProcessor>of( |
| p.first()-1, |
| p.second())); |
| } |
| } |
| latch.countDown(); |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| |
| public @Override void run() { |
| Dialog dd = d.get(); |
| if (dd != null) { |
| dd.setVisible(false); |
| dd.dispose(); |
| } |
| } |
| }); |
| } |
| } |
| }); |
| 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, waitCursorTime, latch, window)) { |
| return; |
| } |
| |
| Cursor wait = Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR); |
| |
| if (waitMomentarily(glassPane, wait, dlgTime, latch, window)) { |
| return; |
| } |
| |
| String title = NbBundle.getMessage(RunOffEDTImpl.class, "RunOffAWT.TITLE_Operation"); //NOI18N |
| String cancelButton = NbBundle.getMessage(RunOffEDTImpl.class, "RunOffAWT.BTN_Cancel"); //NOI18N |
| |
| DialogDescriptor nd = new DialogDescriptor(operationDescr, title, true, new Object[]{cancelButton}, |
| cancelButton, DialogDescriptor.DEFAULT_ALIGN, null, new ActionListener() { |
| public @Override void actionPerformed(ActionEvent e) { |
| cancelOperation.set(true); |
| d.get().setVisible(false); |
| d.get().dispose(); |
| } |
| }); |
| |
| nd.setMessageType(NotifyDescriptor.INFORMATION_MESSAGE); |
| |
| d.set(DialogDisplayer.getDefault().createDialog(nd)); |
| d.get().setVisible(true); |
| |
| if (waitForCanceled) { |
| try { |
| if (!latch.await(CANCEL_TIME, TimeUnit.MILLISECONDS)) { |
| throw new IllegalStateException("Canceled operation did not finish in time."); //NOI18N |
| } |
| } catch (InterruptedException ex) { |
| LOG.log(Level.FINE, null, ex); |
| } |
| } |
| } |
| |
| @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<Dialog> d = new AtomicReference<Dialog>(); |
| final AtomicReference<RequestProcessor.Task> t = new AtomicReference<RequestProcessor.Task>(); |
| |
| 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(); |
| |
| try { |
| if (wait != null) { |
| window.setCursor(wait); |
| 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); |
| window.setCursor(originalWindow); |
| glassPane.setCursor(originalGlass); |
| } |
| } |
| |
| public RunOffEDTImpl() { |
| boolean ea = false; |
| assert ea = true; |
| assertionsOn = ea; |
| } |
| |
| @Override |
| public <T> Future<T> showProgressDialogAndRunLater (ProgressRunnable<T> operation, ProgressHandle handle, boolean includeDetailLabel) { |
| AbstractWindowRunner<T> wr = new ProgressBackgroundRunner<T>(operation, |
| handle, includeDetailLabel, operation instanceof Cancellable); |
| Future<T> result = wr.start(); |
| assert EventQueue.isDispatchThread() == (result != null); |
| if (result == null) { |
| try { |
| result = wr.waitForStart(); |
| } catch (InterruptedException ex) { |
| LOG.log(Level.FINE, "Interrupted/cancelled during start {0}", operation); //NOI18N |
| LOG.log(Level.FINER, "Interrupted/cancelled during start", ex); //NOI18N |
| return null; |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public <T> T showProgressDialogAndRun(ProgressRunnable<T> toRun, String displayName, boolean includeDetailLabel) { |
| try { |
| return showProgressDialogAndRunLater(toRun, toRun instanceof Cancellable ? |
| ProgressHandleFactory.createHandle(displayName, (Cancellable) toRun) : |
| ProgressHandleFactory.createHandle(displayName), includeDetailLabel).get(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } catch (CancellationException ex) { |
| LOG.log(Level.FINER, "Cancelled " + toRun, ex); //NOI18N |
| } catch (ExecutionException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| return null; |
| } |
| |
| @Override |
| public void showProgressDialogAndRun(Runnable toRun, ProgressHandle handle, boolean includeDetailLabel) { |
| boolean showCancelButton = toRun instanceof Cancellable; |
| AbstractWindowRunner<Void> wr = new ProgressBackgroundRunner<Void>(toRun, |
| handle, includeDetailLabel, showCancelButton); |
| wr.start(); |
| try { |
| try { |
| wr.waitForStart().get(); |
| } catch (CancellationException ex) { |
| LOG.log(Level.FINER, "Cancelled " + toRun, ex); //NOI18N |
| } catch (ExecutionException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| static class CancellableFutureTask<T> extends FutureTask<T> implements Cancellable { |
| volatile Task task; |
| private final Callable<T> c; |
| CancellableFutureTask(Callable<T> c) { |
| super(c); |
| this.c = c; |
| } |
| |
| @Override |
| public boolean cancel() { |
| return cancel(true); |
| } |
| |
| @Override |
| public boolean cancel(boolean mayInterruptIfRunning) { |
| boolean result = c instanceof Cancellable ? ((Cancellable) c).cancel() : false; |
| result &= super.cancel(mayInterruptIfRunning) & task.cancel(); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString() + "[" + c + "]"; //NOI18N |
| } |
| } |
| |
| static final class TranslucentMask extends JComponent { //pkg private for tests |
| private static final String PROGRESS_WINDOW_MASK_COLOR = "progress.windowMaskColor"; //NOI18N |
| TranslucentMask() { |
| setVisible(false); //so we will trigger a property change |
| } |
| |
| @Override |
| public boolean isOpaque() { |
| return false; |
| } |
| |
| @Override |
| public void paint (Graphics g) { |
| Graphics2D g2d = (Graphics2D) g; |
| Color translu = UIManager.getColor(PROGRESS_WINDOW_MASK_COLOR); |
| if (translu == null) { |
| translu = new Color (180, 180, 180, 148); |
| } |
| g2d.setColor(translu); |
| g2d.fillRect (0, 0, getWidth(), getHeight()); |
| } |
| } |
| |
| private static final class ProgressBackgroundRunner<T> extends AbstractWindowRunner<T> implements Cancellable { |
| private final ProgressRunnable<T> toRun; |
| ProgressBackgroundRunner(ProgressRunnable<T> toRun, String displayName, boolean includeDetail, boolean showCancel) { |
| super (showCancel ? |
| ProgressHandleFactory.createHandle(displayName, (Cancellable) toRun, null) : |
| ProgressHandleFactory.createHandle(displayName, (Action)null), includeDetail, showCancel); |
| this.toRun = toRun; |
| } |
| |
| ProgressBackgroundRunner(ProgressRunnable<T> toRun, ProgressHandle handle, boolean includeDetail, boolean showCancel) { |
| super (handle, includeDetail, showCancel); |
| this.toRun = toRun; |
| } |
| |
| ProgressBackgroundRunner(Runnable toRun, ProgressHandle handle, boolean includeDetail, boolean showCancel) { |
| this (showCancel ? new CancellableRunnablePR<T>(toRun) : |
| new RunnablePR<T>(toRun), handle, includeDetail, showCancel); |
| } |
| |
| @Override |
| protected T runBackground() { |
| handle.start(); |
| handle.switchToIndeterminate(); |
| T result; |
| try { |
| result = toRun.run(handle); |
| } finally { |
| handle.finish(); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean cancel() { |
| if (toRun instanceof Cancellable) { |
| return ((Cancellable) toRun).cancel(); |
| } |
| return false; |
| } |
| |
| private static class RunnablePR<T> implements ProgressRunnable<T> { |
| protected final Runnable toRun; |
| RunnablePR(Runnable toRun) { |
| this.toRun = toRun; |
| } |
| |
| @Override |
| public T run(ProgressHandle handle) { |
| toRun.run(); |
| return null; |
| } |
| } |
| |
| private static final class CancellableRunnablePR<T> extends RunnablePR<T> implements Cancellable { |
| CancellableRunnablePR(Runnable toRun) { |
| super (toRun); |
| } |
| |
| @Override |
| public boolean cancel() { |
| return ((Cancellable) toRun).cancel(); |
| } |
| } |
| |
| } |
| |
| 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<Dialog> d, |
| final AtomicReference<RequestProcessor.Task> 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; |
| } |
| } |