| /******************************************************************************* |
| * Copyright (c) 2000, 2004 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 |
| *******************************************************************************/ |
| |
| package org.eclipse.jface.action; |
| |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.jface.dialogs.ProgressIndicator; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.JFaceColors; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CLabel; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| 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.Display; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| |
| /** |
| * A StatusLine control is a SWT Composite with a horizontal layout which hosts |
| * a number of status indication controls. |
| * Typically it is situated below the content area of the window. |
| * <p> |
| * By default a StatusLine has two predefined status controls: a MessageLine and a |
| * ProgressIndicator and it provides API for easy access. |
| * </p> |
| * <p> |
| * This is an internal class, not intended to be used outside the JFace framework. |
| * </p> |
| */ |
| /* package */class StatusLine extends Composite implements IProgressMonitor { |
| |
| /** Horizontal gaps between items. */ |
| public static final int GAP = 3; |
| |
| /** Progress bar creation is delayed by this ms */ |
| public static final int DELAY_PROGRESS = 500; |
| |
| // state |
| protected boolean fProgressIsVisible = false; |
| |
| protected boolean fCancelButtonIsVisible = false; |
| |
| protected boolean fCancelEnabled = false; |
| |
| protected String fTaskName; |
| |
| protected boolean fIsCanceled; |
| |
| protected long fStartTime; |
| |
| private Cursor fStopButtonCursor; |
| |
| protected String fMessageText; |
| |
| protected Image fMessageImage; |
| |
| protected String fErrorText; |
| |
| protected Image fErrorImage; |
| |
| // SWT widgets |
| protected CLabel fMessageLabel; |
| |
| protected Composite fProgressBarComposite; |
| |
| protected ProgressIndicator fProgressBar; |
| |
| protected ToolBar fToolBar; |
| |
| protected ToolItem fCancelButton; |
| |
| protected static ImageDescriptor fgStopImage = ImageDescriptor |
| .createFromFile(StatusLine.class, "images/stop.gif");//$NON-NLS-1$ |
| static { |
| JFaceResources.getImageRegistry().put( |
| "org.eclipse.jface.parts.StatusLine.stopImage", fgStopImage);//$NON-NLS-1$ |
| } |
| |
| /** |
| * Layout the contribution item controls on the status line. |
| */ |
| public class StatusLineLayout extends Layout { |
| private final StatusLineLayoutData DEFAULT_DATA = new StatusLineLayoutData(); |
| |
| public Point computeSize(Composite composite, int wHint, int hHint, |
| boolean changed) { |
| |
| if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT) |
| return new Point(wHint, hHint); |
| |
| Control[] children = composite.getChildren(); |
| int totalWidth = 0; |
| int maxHeight = 0; |
| int totalCnt = 0; |
| for (int i = 0; i < children.length; i++) { |
| boolean useWidth = true; |
| Control w = children[i]; |
| if (w == fProgressBarComposite && !fProgressIsVisible) |
| useWidth = false; |
| else if (w == fToolBar && !fCancelButtonIsVisible) |
| useWidth = false; |
| StatusLineLayoutData data = (StatusLineLayoutData) w |
| .getLayoutData(); |
| if (data == null) |
| data = DEFAULT_DATA; |
| Point e = w.computeSize(data.widthHint, data.heightHint, |
| changed); |
| if (useWidth) { |
| totalWidth += e.x; |
| totalCnt++; |
| } |
| maxHeight = Math.max(maxHeight, e.y); |
| } |
| if (totalCnt > 0) |
| totalWidth += (totalCnt - 1) * GAP; |
| if (totalWidth <= 0) |
| totalWidth = maxHeight * 4; |
| return new Point(totalWidth, maxHeight); |
| } |
| |
| public void layout(Composite composite, boolean flushCache) { |
| |
| if (composite == null) |
| return; |
| |
| // StatusLineManager skips over the standard status line widgets |
| // in its update method. There is thus a dependency |
| // between the layout of the standard widgets and the update method. |
| |
| // Make sure cancel button and progress bar are before contributions. |
| fMessageLabel.moveAbove(null); |
| fToolBar.moveBelow(fMessageLabel); |
| fProgressBarComposite.moveBelow(fToolBar); |
| |
| Rectangle rect = composite.getClientArea(); |
| Control[] children = composite.getChildren(); |
| int count = children.length; |
| |
| int ws[] = new int[count]; |
| |
| int h = rect.height; |
| int totalWidth = -GAP; |
| for (int i = 0; i < count; i++) { |
| Control w = children[i]; |
| if (w == fProgressBarComposite && !fProgressIsVisible) |
| continue; |
| if (w == fToolBar && !fCancelButtonIsVisible) |
| continue; |
| StatusLineLayoutData data = (StatusLineLayoutData) w |
| .getLayoutData(); |
| if (data == null) |
| data = DEFAULT_DATA; |
| int width = w.computeSize(data.widthHint, h, flushCache).x; |
| ws[i] = width; |
| totalWidth += width + GAP; |
| } |
| |
| int diff = rect.width - totalWidth; |
| ws[0] += diff; // make the first StatusLabel wider |
| |
| // Check against minimum recommended width |
| final int msgMinWidth = rect.width / 3; |
| if (ws[0] < msgMinWidth) { |
| diff = ws[0] - msgMinWidth; |
| ws[0] = msgMinWidth; |
| } else { |
| diff = 0; |
| } |
| |
| // Take space away from the contributions first. |
| for (int i = count - 1; i >= 0 && diff < 0; --i) { |
| int min = Math.min(ws[i], -diff); |
| ws[i] -= min; |
| diff += min + GAP; |
| } |
| |
| int x = rect.x; |
| int y = rect.y; |
| for (int i = 0; i < count; i++) { |
| Control w = children[i]; |
| /* |
| * Workaround for Linux Motif: |
| * Even if the progress bar and cancel button are |
| * not set to be visible ad of width 0, they still |
| * draw over the first pixel of the editor |
| * contributions. |
| * |
| * The fix here is to draw the progress bar and |
| * cancel button off screen if they are not visible. |
| */ |
| if (w == fProgressBarComposite && !fProgressIsVisible |
| || w == fToolBar && !fCancelButtonIsVisible) { |
| w.setBounds(x + rect.width, y, ws[i], h); |
| continue; |
| } |
| w.setBounds(x, y, ws[i], h); |
| if (ws[i] > 0) |
| x += ws[i] + GAP; |
| } |
| } |
| } |
| |
| /** |
| * Create a new StatusLine as a child of the given parent. |
| */ |
| public StatusLine(Composite parent, int style) { |
| super(parent, style); |
| |
| addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| handleDispose(); |
| } |
| }); |
| |
| // StatusLineManager skips over the standard status line widgets |
| // in its update method. There is thus a dependency |
| // between this code defining the creation and layout of the standard |
| // widgets and the update method. |
| |
| setLayout(new StatusLineLayout()); |
| |
| fMessageLabel = new CLabel(this, SWT.NONE);//SWT.SHADOW_IN); |
| // Color[] colors = new Color[2]; |
| // colors[0] = parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); |
| // colors[1] = fMessageLabel.getBackground(); |
| // int[] gradient = new int[] {JFaceColors.STATUS_PERCENT}; |
| // fMessageLabel.setBackground(colors, gradient); |
| |
| fProgressIsVisible = false; |
| fCancelEnabled = false; |
| |
| fToolBar = new ToolBar(this, SWT.FLAT); |
| fCancelButton = new ToolItem(fToolBar, SWT.PUSH); |
| fCancelButton.setImage(fgStopImage.createImage()); |
| fCancelButton.setToolTipText(JFaceResources |
| .getString("Cancel_Current_Operation")); //$NON-NLS-1$ |
| fCancelButton.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent e) { |
| setCanceled(true); |
| } |
| }); |
| fCancelButton.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| Image i = fCancelButton.getImage(); |
| if ((i != null) && (!i.isDisposed())) |
| i.dispose(); |
| } |
| }); |
| |
| // We create a composite to create the progress bar in |
| // so that it can be centered. See bug #32331 |
| fProgressBarComposite = new Composite(this, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.horizontalSpacing = 0; |
| layout.verticalSpacing = 0; |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| fProgressBarComposite.setLayout(layout); |
| fProgressBar = new ProgressIndicator(fProgressBarComposite); |
| fProgressBar.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
| | GridData.GRAB_VERTICAL)); |
| |
| fStopButtonCursor = new Cursor(getDisplay(), SWT.CURSOR_ARROW); |
| } |
| |
| /** |
| * Notifies that the main task is beginning. |
| * |
| * @param name the name (or description) of the main task |
| * @param totalWork the total number of work units into which |
| * the main task is been subdivided. If the value is 0 or UNKNOWN the |
| * implemenation is free to indicate progress in a way which doesn't |
| * require the total number of work units in advance. In general users |
| * should use the UNKNOWN value if they don't know the total amount of |
| * work units. |
| */ |
| public void beginTask(String name, int totalWork) { |
| final long timestamp = System.currentTimeMillis(); |
| fStartTime = timestamp; |
| final boolean animated = (totalWork == UNKNOWN || totalWork == 0); |
| // make sure the progress bar is made visible while |
| // the task is running. Fixes bug 32198 for the non-animated case. |
| Runnable timer = new Runnable() { |
| public void run() { |
| StatusLine.this.startTask(timestamp, animated); |
| } |
| }; |
| if (fProgressBar == null) |
| return; |
| |
| fProgressBar.getDisplay().timerExec(DELAY_PROGRESS, timer); |
| if (!animated) { |
| fProgressBar.beginTask(totalWork); |
| } |
| if (name == null) |
| fTaskName = "";//$NON-NLS-1$ |
| else |
| fTaskName = name; |
| setMessage(fTaskName); |
| } |
| |
| /** |
| * Notifies that the work is done; that is, either the main task is completed or the |
| * user cancelled it. |
| * Done() can be called more than once; an implementation should be prepared to handle |
| * this case. |
| */ |
| public void done() { |
| |
| fStartTime = 0; |
| |
| if (fProgressBar != null) { |
| fProgressBar.sendRemainingWork(); |
| fProgressBar.done(); |
| } |
| setMessage(null); |
| |
| hideProgress(); |
| } |
| |
| /** |
| * Returns the status line's progress monitor |
| */ |
| public IProgressMonitor getProgressMonitor() { |
| return this; |
| } |
| |
| /** |
| * @private |
| */ |
| protected void handleDispose() { |
| if (fStopButtonCursor != null) { |
| fStopButtonCursor.dispose(); |
| fStopButtonCursor = null; |
| } |
| if (fProgressBar != null) { |
| fProgressBar.dispose(); |
| fProgressBar = null; |
| } |
| } |
| |
| /** |
| * Hides the Cancel button and ProgressIndicator. |
| * @private |
| */ |
| protected void hideProgress() { |
| |
| if (fProgressIsVisible && !isDisposed()) { |
| fProgressIsVisible = false; |
| fCancelEnabled = false; |
| fCancelButtonIsVisible = false; |
| if (fToolBar != null && !fToolBar.isDisposed()) |
| fToolBar.setVisible(false); |
| if (fProgressBarComposite != null |
| && !fProgressBarComposite.isDisposed()) |
| fProgressBarComposite.setVisible(false); |
| layout(); |
| } |
| } |
| |
| /** |
| * @see IProgressMonitor#internalWorked(double) |
| */ |
| public void internalWorked(double work) { |
| if (!fProgressIsVisible) { |
| if (System.currentTimeMillis() - fStartTime > DELAY_PROGRESS) |
| showProgress(); |
| } |
| |
| if (fProgressBar != null) { |
| fProgressBar.worked(work); |
| } |
| } |
| |
| /** |
| * Returns true if the user does some UI action to cancel this operation. |
| * (like hitting the Cancel button on the progress dialog). |
| * The long running operation typically polls isCanceled(). |
| */ |
| public boolean isCanceled() { |
| return fIsCanceled; |
| } |
| |
| /** |
| * Returns <code>true</true> if the ProgressIndication provides UI for canceling |
| * a long running operation. |
| */ |
| public boolean isCancelEnabled() { |
| return fCancelEnabled; |
| } |
| |
| /** |
| * Sets the cancel status. This method is usually called with the |
| * argument false if a client wants to abort a cancel action. |
| */ |
| public void setCanceled(boolean b) { |
| fIsCanceled = b; |
| if (fCancelButton != null) |
| fCancelButton.setEnabled(!b); |
| } |
| |
| /** |
| * Controls whether the ProgressIndication provides UI for canceling |
| * a long running operation. |
| * If the ProgressIndication is currently visible calling this method may have |
| * a direct effect on the layout because it will make a cancel button visible. |
| */ |
| public void setCancelEnabled(boolean enabled) { |
| fCancelEnabled = enabled; |
| if (fProgressIsVisible && !fCancelButtonIsVisible && enabled) { |
| showButton(); |
| layout(); |
| } |
| if (fCancelButton != null && !fCancelButton.isDisposed()) |
| fCancelButton.setEnabled(enabled); |
| } |
| |
| /** |
| * Sets the error message text to be displayed on the status line. |
| * The image on the status line is cleared. |
| * |
| * @param message the error message, or <code>null</code> for no error message |
| */ |
| public void setErrorMessage(String message) { |
| setErrorMessage(null, message); |
| } |
| |
| /** |
| * Sets an image and error message text to be displayed on the status line. |
| * |
| * @param image the image to use, or <code>null</code> for no image |
| * @param message the error message, or <code>null</code> for no error message |
| */ |
| public void setErrorMessage(Image image, String message) { |
| fErrorText = trim(message); |
| fErrorImage = image; |
| updateMessageLabel(); |
| } |
| |
| /** |
| * Applies the given font to this status line. |
| */ |
| public void setFont(Font font) { |
| super.setFont(font); |
| Control[] children = getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| children[i].setFont(font); |
| } |
| } |
| |
| /** |
| * Sets the message text to be displayed on the status line. |
| * The image on the status line is cleared. |
| * |
| * @param message the error message, or <code>null</code> for no error message |
| */ |
| public void setMessage(String message) { |
| setMessage(null, message); |
| } |
| |
| /** |
| * Sets an image and a message text to be displayed on the status line. |
| * |
| * @param image the image to use, or <code>null</code> for no image |
| * @param message the message, or <code>null</code> for no message |
| */ |
| public void setMessage(Image image, String message) { |
| fMessageText = trim(message); |
| fMessageImage = image; |
| updateMessageLabel(); |
| } |
| |
| /** |
| * @see IProgressMonitor#setTaskName(java.lang.String) |
| */ |
| public void setTaskName(String name) { |
| fTaskName = name; |
| } |
| |
| /** |
| * Makes the Cancel button visible. |
| * @private |
| */ |
| protected void showButton() { |
| if (fToolBar != null && !fToolBar.isDisposed()) { |
| fToolBar.setVisible(true); |
| fToolBar.setEnabled(true); |
| fToolBar.setCursor(fStopButtonCursor); |
| fCancelButtonIsVisible = true; |
| } |
| } |
| |
| /** |
| * Shows the Cancel button and ProgressIndicator. |
| * @private |
| */ |
| protected void showProgress() { |
| if (!fProgressIsVisible && !isDisposed()) { |
| fProgressIsVisible = true; |
| if (fCancelEnabled) |
| showButton(); |
| if (fProgressBarComposite != null |
| && !fProgressBarComposite.isDisposed()) |
| fProgressBarComposite.setVisible(true); |
| layout(); |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| void startTask(final long timestamp, final boolean animated) { |
| if (!fProgressIsVisible && fStartTime == timestamp) { |
| showProgress(); |
| if (animated) { |
| if (fProgressBar != null && !fProgressBar.isDisposed()) { |
| fProgressBar.beginAnimatedTask(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Notifies that a subtask of the main task is beginning. |
| * Subtasks are optional; the main task might not have subtasks. |
| * @param name the name (or description) of the subtask |
| * @see IProgressMonitor#subTask(String) |
| */ |
| public void subTask(String name) { |
| String text; |
| if (fTaskName.length() == 0) |
| text = name; |
| else |
| text = JFaceResources.format( |
| "Set_SubTask", new Object[] { fTaskName, name });//$NON-NLS-1$ |
| setMessage(text); |
| } |
| |
| /** |
| * Trims the message to be displayable in the status line. |
| * This just pulls out the first line of the message. |
| * Allows null. |
| */ |
| String trim(String message) { |
| if (message == null) |
| return null; |
| int cr = message.indexOf('\r'); |
| int lf = message.indexOf('\n'); |
| if (cr == -1 && lf == -1) |
| return message; |
| int len; |
| if (cr == -1) |
| len = lf; |
| else if (lf == -1) |
| len = cr; |
| else |
| len = Math.min(cr, lf); |
| return message.substring(0, len); |
| } |
| |
| /** |
| * Updates the message label widget. |
| */ |
| protected void updateMessageLabel() { |
| if (fMessageLabel != null && !fMessageLabel.isDisposed()) { |
| Display display = fMessageLabel.getDisplay(); |
| if ((fErrorText != null && fErrorText.length() > 0) |
| || fErrorImage != null) { |
| fMessageLabel.setForeground(JFaceColors.getErrorText(display)); |
| fMessageLabel.setText(fErrorText); |
| fMessageLabel.setImage(fErrorImage); |
| } else { |
| fMessageLabel.setForeground(display |
| .getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); |
| fMessageLabel.setText(fMessageText == null ? "" : fMessageText); //$NON-NLS-1$ |
| fMessageLabel.setImage(fMessageImage); |
| } |
| } |
| } |
| |
| /** |
| * @see IProgressMonitor#worked(int) |
| */ |
| public void worked(int work) { |
| internalWorked(work); |
| } |
| } |