| /******************************************************************************* |
| * Copyright (c) 2000, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Eugene Ostroukhov <eugeneo@symbian.org> - Bug 287887 [Wizards] [api] Cancel button has two distinct roles |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 440270 |
| * Jan-Ove Weichel <janove.weichel@vogella.com> - Bug 475879 |
| * Karsten Thoms <karsten.thoms@itemis.de> - Bug 520720 Asynchronous, throttled label update |
| *******************************************************************************/ |
| package org.eclipse.jface.wizard; |
| |
| import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter; |
| |
| import java.time.Duration; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IProgressMonitorWithBlocking; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.jface.dialogs.ProgressIndicator; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.util.Throttler; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.FontMetrics; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| |
| /** |
| * A standard implementation of an IProgressMonitor. It consists |
| * of a label displaying the task and subtask name, and a |
| * progress indicator to show progress. In contrast to |
| * <code>ProgressMonitorDialog</code> this class only implements |
| * <code>IProgressMonitor</code>. |
| */ |
| public class ProgressMonitorPart extends Composite implements |
| IProgressMonitorWithBlocking { |
| |
| /** the label */ |
| protected Label fLabel; |
| |
| /** the current task name */ |
| protected String fTaskName; |
| |
| /** the current sub task name */ |
| protected String fSubTaskName; |
| |
| /** the progress indicator */ |
| protected ProgressIndicator fProgressIndicator; |
| |
| /** the cancel component */ |
| protected Control fCancelComponent; |
| |
| /** true if canceled */ |
| protected volatile boolean fIsCanceled; |
| |
| /** current blocked status */ |
| protected IStatus blockedStatus; |
| |
| /** the cancel lister attached to the cancel component */ |
| protected Listener fCancelListener = e -> { |
| setCanceled(true); |
| if (fCancelComponent != null) { |
| fCancelComponent.setEnabled(false); |
| } |
| }; |
| |
| /** toolbar for managing stop button **/ |
| private ToolBar fToolBar; |
| |
| /** default tool item for canceling tasks **/ |
| private ToolItem fStopButton; |
| |
| /** <code>true</code> if this monitor part should show stop button **/ |
| private boolean fHasStopButton = false; |
| |
| private Throttler throttledUpdate; |
| |
| /** |
| * Creates a <code>ProgressMonitorPart</code> that does not provide a stop button. |
| * |
| * @param parent The SWT parent of the part. |
| * @param layout The SWT grid layout used by the part. A client can supply the layout to control |
| * how the progress monitor part is laid out. If <code>null</code> is passed the part |
| * uses its default layout. |
| */ |
| public ProgressMonitorPart(Composite parent, Layout layout) { |
| this(parent, layout, false); |
| } |
| |
| /** |
| * Creates a <code>ProgressMonitorPart</code> that does not provide a stop button. |
| * |
| * @param parent The SWT parent of the part. |
| * @param layout The SWT grid layout used by the part. A client can supply the layout to control |
| * how the progress monitor part is laid out. If <code>null</code> is passed the part |
| * uses its default layout. |
| * @param progressIndicatorHeight The height of the progress indicator in pixels. This value may |
| * be SWT.DEFAULT in order to get the default height as calculated by the widget and |
| * its layout. |
| */ |
| public ProgressMonitorPart(Composite parent, Layout layout, int progressIndicatorHeight) { |
| super(parent, SWT.NONE); |
| initialize(layout, progressIndicatorHeight); |
| } |
| |
| /** |
| * Creates a <code>ProgressMonitorPart</code>. |
| * |
| * @param parent the SWT parent of the part |
| * @param layout the SWT grid layout used by the part. A client can supply the layout to control |
| * how the progress monitor part is laid out. If <code>null</code> is passed the part |
| * uses its default layout. |
| * @param createStopButton <code>true</code> if the progress indicator should include a stop |
| * button that can be used to cancel any currently running task, and |
| * <code>false</code> if no such stop button should be created. |
| * |
| * @since 3.6 |
| */ |
| public ProgressMonitorPart(Composite parent, Layout layout, boolean createStopButton) { |
| super(parent, SWT.NONE); |
| fHasStopButton= createStopButton; |
| initialize(layout, SWT.DEFAULT); |
| } |
| |
| /** |
| * Attaches the progress monitor part to the given cancel component. |
| * |
| * @param cancelComponent the control whose selection will trigger a cancel. This parameter will |
| * be ignored and hence can be <code>null</code> if a stop button was requested upon |
| * construction and instead the stop button will enabled and serve as the cancel |
| * component. |
| * @see #ProgressMonitorPart(Composite, Layout, boolean) |
| */ |
| public void attachToCancelComponent(Control cancelComponent) { |
| if (fHasStopButton) |
| setCancelEnabled(true); |
| else { |
| fCancelComponent = cancelComponent; |
| fCancelComponent.addListener(SWT.Selection, fCancelListener); |
| } |
| } |
| |
| @Override |
| public void beginTask(String name, int totalWork) { |
| fTaskName = name; |
| fSubTaskName = ""; //$NON-NLS-1$ |
| queueUpdateLabel(); |
| if (!fProgressIndicator.isDisposed()) { |
| if (totalWork == IProgressMonitor.UNKNOWN || totalWork == 0) { |
| fProgressIndicator.beginAnimatedTask(); |
| } else { |
| fProgressIndicator.beginTask(totalWork); |
| } |
| } |
| if (fToolBar != null && !fToolBar.isDisposed()) { |
| fToolBar.setVisible(true); |
| fToolBar.setFocus(); |
| } |
| } |
| |
| @Override |
| public void done() { |
| if (!fLabel.isDisposed()) { |
| fLabel.setText("");//$NON-NLS-1$ |
| } |
| fSubTaskName = ""; //$NON-NLS-1$ |
| if (!fProgressIndicator.isDisposed()) { |
| fProgressIndicator.sendRemainingWork(); |
| fProgressIndicator.done(); |
| } |
| if (fToolBar != null && !fToolBar.isDisposed()) |
| fToolBar.setVisible(false); |
| } |
| |
| /** |
| * Escapes any occurrence of '&' in the given String so that |
| * it is not considered as a mnemonic |
| * character in SWT ToolItems, MenuItems, Button and Labels. |
| * @param in the original String |
| * @return The converted String |
| */ |
| protected static String escapeMetaCharacters(String in) { |
| if (in == null || in.indexOf('&') < 0) { |
| return in; |
| } |
| int length = in.length(); |
| StringBuilder out = new StringBuilder(length + 1); |
| for (int i = 0; i < length; i++) { |
| char c = in.charAt(i); |
| if (c == '&') { |
| out.append("&&");//$NON-NLS-1$ |
| } else { |
| out.append(c); |
| } |
| } |
| return out.toString(); |
| } |
| |
| /** |
| * Creates the progress monitor's UI parts and layouts them |
| * according to the given layout. If the layout is <code>null</code> |
| * the part's default layout is used. |
| * @param layout The layout for the receiver. |
| * @param progressIndicatorHeight The suggested height of the indicator |
| */ |
| protected void initialize(Layout layout, int progressIndicatorHeight) { |
| if (layout == null) { |
| GridLayout l = new GridLayout(); |
| l.marginWidth = 0; |
| l.marginHeight = 0; |
| layout = l; |
| } |
| int numColumns = 1; |
| if (fHasStopButton) |
| numColumns++; |
| setLayout(layout); |
| |
| if (layout instanceof GridLayout) |
| ((GridLayout)layout).numColumns = numColumns; |
| |
| fLabel = new Label(this, SWT.LEFT); |
| fLabel.setLayoutData(new GridData(GridData.FILL, GridData.CENTER, true, false, numColumns, 1)); |
| |
| if (progressIndicatorHeight == SWT.DEFAULT) { |
| GC gc = new GC(fLabel); |
| FontMetrics fm = gc.getFontMetrics(); |
| gc.dispose(); |
| progressIndicatorHeight = fm.getHeight(); |
| } |
| |
| fProgressIndicator = new ProgressIndicator(this); |
| GridData gd = new GridData(); |
| gd.horizontalAlignment = GridData.FILL; |
| gd.grabExcessHorizontalSpace = true; |
| gd.grabExcessVerticalSpace = false; |
| gd.verticalAlignment = GridData.CENTER; |
| gd.heightHint = progressIndicatorHeight; |
| fProgressIndicator.setLayoutData(gd); |
| |
| if (fHasStopButton) { |
| fToolBar = new ToolBar(this, SWT.FLAT); |
| |
| gd = new GridData(); |
| gd.grabExcessHorizontalSpace = false; |
| gd.grabExcessVerticalSpace = false; |
| gd.verticalAlignment = GridData.CENTER; |
| fToolBar.setLayoutData(gd); |
| fStopButton = new ToolItem(fToolBar, SWT.PUSH); |
| // It would have been nice to use the fCancelListener, but that |
| // listener operates on the fCancelComponent which must be a control. |
| fStopButton.addSelectionListener(widgetSelectedAdapter(e -> { |
| setCanceled(true); |
| if (fStopButton != null) { |
| fStopButton.setEnabled(false); |
| } |
| })); |
| final Image stopImage = ImageDescriptor.createFromFile( |
| ProgressMonitorPart.class, "images/stop.png").createImage(getDisplay()); //$NON-NLS-1$ |
| fToolBar.setCursor(this.getDisplay().getSystemCursor(SWT.CURSOR_ARROW)); |
| fStopButton.setImage(stopImage); |
| fStopButton.addDisposeListener(e -> { |
| stopImage.dispose(); |
| }); |
| fStopButton.setEnabled(false); |
| fStopButton.setToolTipText(JFaceResources.getString("ProgressMonitorPart.cancelToolTip")); //$NON-NLS-1$ |
| } |
| |
| throttledUpdate = new Throttler(fLabel.getDisplay(), Duration.ofMillis(100), this::updateLabel); |
| } |
| |
| @Override |
| public void internalWorked(double work) { |
| if (!fProgressIndicator.isDisposed()) { |
| fProgressIndicator.worked(work); |
| } |
| } |
| |
| @Override |
| public boolean isCanceled() { |
| return fIsCanceled; |
| } |
| |
| /** |
| * Detach the progress monitor part from the given cancel component. |
| * |
| * @param cancelComponent the control that was previously used as a cancel component. This |
| * parameter will be ignored and hence can be <code>null</code> if a stop button was |
| * requested upon construction and instead the stop button will be disabled. |
| * @see #ProgressMonitorPart(Composite, Layout, boolean) |
| */ |
| public void removeFromCancelComponent(Control cancelComponent) { |
| if (fHasStopButton) { |
| setCancelEnabled(false); |
| } else { |
| Assert.isTrue(fCancelComponent == cancelComponent && fCancelComponent != null); |
| fCancelComponent.removeListener(SWT.Selection, fCancelListener); |
| fCancelComponent = null; |
| } |
| } |
| |
| @Override |
| public void setCanceled(boolean b) { |
| fIsCanceled = b; |
| } |
| |
| @Override |
| public void setFont(Font font) { |
| super.setFont(font); |
| fLabel.setFont(font); |
| fProgressIndicator.setFont(font); |
| } |
| |
| @Override |
| public void setTaskName(String name) { |
| fTaskName = name; |
| queueUpdateLabel(); |
| } |
| |
| @Override |
| public void subTask(String name) { |
| fSubTaskName = name; |
| queueUpdateLabel(); |
| } |
| |
| /** |
| * Enqueues a label update for asynchronous execution. The update is performed |
| * throttled to 100ms, i.e. updates within the throttle range are not displayed. |
| * |
| * @since 3.14 |
| */ |
| protected void queueUpdateLabel() { |
| throttledUpdate.throttledExec(); |
| } |
| |
| /** |
| * Updates the label with the current task and subtask names. |
| */ |
| protected void updateLabel() { |
| if (fLabel.isDisposed() || fLabel.isAutoDirection()) { |
| return; |
| } |
| if (blockedStatus == null) { |
| String text = taskLabel(); |
| fLabel.setText(text); |
| } else { |
| fLabel.setText(blockedStatus.getMessage()); |
| } |
| |
| // Force an update as we are in the UI Thread |
| fLabel.update(); |
| } |
| |
| /** |
| * Return the label for showing tasks |
| * @return String |
| */ |
| private String taskLabel() { |
| boolean hasTask= fTaskName != null && fTaskName.length() > 0; |
| boolean hasSubtask= fSubTaskName != null && fSubTaskName.length() > 0; |
| |
| if (hasTask) { |
| if (hasSubtask) |
| return escapeMetaCharacters(JFaceResources.format("Set_SubTask", fTaskName, fSubTaskName));//$NON-NLS-1$ |
| return escapeMetaCharacters(fTaskName); |
| |
| } else if (hasSubtask) { |
| return escapeMetaCharacters(fSubTaskName); |
| |
| } else { |
| return ""; //$NON-NLS-1$ |
| } |
| } |
| |
| @Override |
| public void worked(int work) { |
| internalWorked(work); |
| } |
| |
| @Override |
| public void clearBlocked() { |
| blockedStatus = null; |
| queueUpdateLabel(); |
| } |
| |
| @Override |
| public void setBlocked(IStatus reason) { |
| blockedStatus = reason; |
| queueUpdateLabel(); |
| } |
| |
| private void setCancelEnabled(boolean enabled) { |
| if (fStopButton != null && !fStopButton.isDisposed()) { |
| fStopButton.setEnabled(enabled); |
| if (enabled) |
| fToolBar.setFocus(); |
| } |
| } |
| } |