/******************************************************************************* * Copyright (c) 2000, 2006 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.resource.*; import org.eclipse.swt.SWT; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; /** * 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; /** visibility state of the progressbar */ protected boolean fProgressIsVisible = false; /** visibility state of the cancle button */ protected boolean fCancelButtonIsVisible = false; /** enablement state of the cancle button */ protected boolean fCancelEnabled = false; /** name of the task */ protected String fTaskName; /** is the task is cancled */ protected boolean fIsCanceled; /** the start time of the task */ protected long fStartTime; // private Cursor fStopButtonCursor; /** the message text */ protected String fMessageText; /** the message image */ protected Image fMessageImage; /** the error text */ protected String fErrorText; /** the error image */ protected Image fErrorImage; /** the message label */ // protected CLabel fMessageLabel; protected Label fMessageLabel; /** the composite parent of the progress bar */ protected Composite fProgressBarComposite; /** the progress bar */ // protected ProgressIndicator fProgressBar; /** the toolbar */ protected ToolBar fToolBar; /** the cancle button */ protected ToolItem fCancelButton; /** stop image descriptor */ 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. * * @param parent the parent for this Composite * @param style the style used to create this widget */ public StatusLine(Composite parent, int style) { super(parent, style); initImage(); 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); fMessageLabel = new Label(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.setText("Stop"); //$NON-NLS-1$ 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); //////////////////////////////////////////////////////////////////////// // TODO [fappel]: replace this with valid progress bar... Label progressBarFake = new Label( fProgressBarComposite, SWT.NONE ) { public Point computeSize( int hint, int hint2, boolean changed ) { Point original = super.computeSize( hint, hint2, changed ); return new Point( original.x, 10 ); } }; progressBarFake.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL)); //////////////////////////////////////////////////////////////////////// // fProgressBar = new ProgressIndicator(fProgressBarComposite); // fProgressBar.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL // | GridData.GRAB_VERTICAL)); // fStopButtonCursor = new Cursor(getDisplay(), SWT.CURSOR_ARROW); } private void initImage() { String path = "images/stop.gif"; ImageDescriptor stopImage = ImageDescriptor.createFromFile(StatusLine.class, path);//$NON-NLS-1$ String imageKey = "org.eclipse.jface.parts.StatusLine.stopImage"; ImageRegistry imageRegistry = JFaceResources.getImageRegistry(); if( imageRegistry.get( imageKey ) == null ) { imageRegistry.put(imageKey, stopImage);//$NON-NLS-1$ } } /** * 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); timer.run(); // 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 * @return {@link IProgressMonitor} the 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. * @return <code>true</true> if the ProgressIndication provides UI for canceling */ 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. * * @param enabled <code>true</true> if cancel should be enabled */ 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); } }