Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Konstantin Scheglov <scheglov_ke@nlmk.ru > - Fix for bug 41172 * [Dialogs] Bug with Image in TitleAreaDialog * Sebastian Davids <sdavids@gmx.de> - Fix for bug 82064 * [Dialogs] TitleAreaDialog#setTitleImage cannot be called before open() *******************************************************************************/ package org.eclipse.jface.dialogs; import org.eclipse.jface.resource.JFaceColors; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.window.ToolTip; import org.eclipse.swt.SWT; import org.eclipse.swt.accessibility.ACC; import org.eclipse.swt.accessibility.AccessibleAttributeAdapter; import org.eclipse.swt.accessibility.AccessibleAttributeEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; 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.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; /** * A dialog that has a title area for displaying a title and an image as well as * a common area for displaying a description, a message, or an error message. * <p> * This dialog class may be subclassed. */ public class TitleAreaDialog extends TrayDialog { /** * Image registry key for error message image. */ public static final String DLG_IMG_TITLE_ERROR = DLG_IMG_MESSAGE_ERROR; /** * Image registry key for banner image (value * <code>"dialog_title_banner_image"</code>). */ public static final String DLG_IMG_TITLE_BANNER = "dialog_title_banner_image";//$NON-NLS-1$ /** * Message type constant used to display an info icon with the message. * * @since 2.0 * @deprecated */ @Deprecated public static final String INFO_MESSAGE = "INFO_MESSAGE"; //$NON-NLS-1$ /** * Message type constant used to display a warning icon with the message. * * @since 2.0 * @deprecated */ @Deprecated public static final String WARNING_MESSAGE = "WARNING_MESSAGE"; //$NON-NLS-1$ // Space between an image and a label private static final int H_GAP_IMAGE = 5; // Minimum dialog width (in dialog units) private static final int MIN_DIALOG_WIDTH = 350; // Minimum dialog height (in dialog units) private static final int MIN_DIALOG_HEIGHT = 150; private Label titleLabel; private Label titleImageLabel; private Label bottomFillerLabel; private Label leftFillerLabel; private RGB titleAreaRGB; Color titleAreaColor; private String message = ""; //$NON-NLS-1$ private String errorMessage; private Text messageLabel; private Composite workArea; private Label messageImageLabel; private Image messageImage; private boolean showingError = false; private boolean titleImageLargest = true; private int messageLabelHeight; private Image titleAreaImage; private int xTrim; private int yTrim; /** * Instantiate a new title area dialog. * * @param parentShell * the parent SWT shell */ public TitleAreaDialog(Shell parentShell) { super(parentShell); } /* * @see Dialog.createContents(Composite) */ @Override protected Control createContents(Composite parent) { // create the overall composite Composite contents = new Composite(parent, SWT.NONE); contents.setLayoutData(new GridData(GridData.FILL_BOTH)); // initialize the dialog units initializeDialogUnits(contents); FormLayout layout = new FormLayout(); contents.setLayout(layout); // Now create a work area for the rest of the dialog workArea = new Composite(contents, SWT.NONE); GridLayout childLayout = new GridLayout(); childLayout.marginHeight = 0; childLayout.marginWidth = 0; childLayout.verticalSpacing = 0; workArea.setLayout(childLayout); Control top = createTitleArea(contents); resetWorkAreaAttachments(top); workArea.setFont(JFaceResources.getDialogFont()); // initialize the dialog units initializeDialogUnits(workArea); // create the dialog area and button bar dialogArea = createDialogArea(workArea); buttonBar = createButtonBar(workArea); // computing trim for later Rectangle rect = messageLabel.computeTrim(0, 0, 100, 100); xTrim = rect.width - 100; yTrim = rect.height - 100; // need to react to new size of title area getShell().addListener(SWT.Resize, event -> layoutForNewMessage(true)); return contents; } /** * Creates and returns the contents of the upper part of this dialog (above * the button bar). * <p> * The <code>Dialog</code> implementation of this framework method creates * and returns a new <code>Composite</code> with no margins and spacing. * Subclasses should override. * </p> * * @param parent * The parent composite to contain the dialog area * @return the dialog area control */ @Override protected Control createDialogArea(Composite parent) { // create the top level composite for the dialog area Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.marginHeight = 0; layout.marginWidth = 0; layout.verticalSpacing = 0; layout.horizontalSpacing = 0; composite.setLayout(layout); composite.setLayoutData(new GridData(GridData.FILL_BOTH)); composite.setFont(parent.getFont()); // Build the separator line Label titleBarSeparator = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR); titleBarSeparator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); return composite; } /** * Creates the dialog's title area. * * @param parent * the SWT parent for the title area widgets * @return Control with the highest x axis value. */ private Control createTitleArea(Composite parent) { // add a dispose listener parent.addDisposeListener(e -> { if (titleAreaColor != null) { titleAreaColor.dispose(); } }); // Determine the background color of the title bar Display display = parent.getDisplay(); Color background; Color foreground; if (titleAreaRGB != null) { titleAreaColor = new Color(display, titleAreaRGB); background = titleAreaColor; foreground = null; } else { background = JFaceColors.getBannerBackground(display); foreground = JFaceColors.getBannerForeground(display); } parent.setBackground(background); int verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); int horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); // Dialog image @ right titleImageLabel = new Label(parent, SWT.CENTER); titleImageLabel.setBackground(background); if (titleAreaImage == null) titleImageLabel.setImage(JFaceResources.getImage(DLG_IMG_TITLE_BANNER)); else titleImageLabel.setImage(titleAreaImage); FormData imageData = new FormData(); imageData.top = new FormAttachment(0, 0); // Note: do not use horizontalSpacing on the right as that would be a // regression from // the R2.x style where there was no margin on the right and images are // flush to the right // hand side. see reopened comments in 41172 imageData.right = new FormAttachment(100, 0); // horizontalSpacing titleImageLabel.setLayoutData(imageData); // Title label @ top, left titleLabel = new Label(parent, SWT.LEFT); JFaceColors.setColors(titleLabel, foreground, background); titleLabel.setFont(JFaceResources.getBannerFont()); titleLabel.setText(" ");//$NON-NLS-1$ FormData titleData = new FormData(); titleData.top = new FormAttachment(0, verticalSpacing); titleData.right = new FormAttachment(titleImageLabel); titleData.left = new FormAttachment(0, horizontalSpacing); titleLabel.setLayoutData(titleData); // Message image @ bottom, left messageImageLabel = new Label(parent, SWT.CENTER); messageImageLabel.setBackground(background); // Message label @ bottom, center messageLabel = new Text(parent, SWT.WRAP | SWT.READ_ONLY); JFaceColors.setColors(messageLabel, foreground, background); messageLabel.setText(" \n "); // two lines//$NON-NLS-1$ messageLabel.setFont(JFaceResources.getDialogFont()); // Bug 248410 - This snippet will only work with Windows screen readers. messageLabel.getAccessible().addAccessibleAttributeListener(new AccessibleAttributeAdapter() { @Override public void getAttributes(AccessibleAttributeEvent e) { e.attributes = new String[] { "container-live", //$NON-NLS-1$ "polite", "live", "polite", //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ "container-live-role", "status", }; //$NON-NLS-1$ //$NON-NLS-2$ } }); messageLabelHeight = messageLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; // Filler labels leftFillerLabel = new Label(parent, SWT.CENTER); leftFillerLabel.setBackground(background); bottomFillerLabel = new Label(parent, SWT.CENTER); bottomFillerLabel.setBackground(background); setLayoutsForNormalMessage(verticalSpacing, horizontalSpacing); determineTitleImageLargest(); if (titleImageLargest) return titleImageLabel; return messageLabel; } /** * Determine if the title image is larger than the title message and message * area. This is used for layout decisions. */ private void determineTitleImageLargest() { int titleY = titleImageLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; int verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); int labelY = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; labelY += verticalSpacing; labelY += messageLabelHeight; labelY += verticalSpacing; titleImageLargest = titleY > labelY; } /** * Set the layout values for the messageLabel, messageImageLabel and * fillerLabel for the case where there is a normal message. * * @param verticalSpacing * int The spacing between widgets on the vertical axis. * @param horizontalSpacing * int The spacing between widgets on the horizontal axis. */ private void setLayoutsForNormalMessage(int verticalSpacing, int horizontalSpacing) { FormData messageImageData = new FormData(); messageImageData.top = new FormAttachment(titleLabel, verticalSpacing); messageImageData.left = new FormAttachment(0, H_GAP_IMAGE); messageImageLabel.setLayoutData(messageImageData); FormData messageLabelData = new FormData(); messageLabelData.top = new FormAttachment(titleLabel, verticalSpacing); messageLabelData.right = new FormAttachment(titleImageLabel); messageLabelData.left = new FormAttachment(messageImageLabel, horizontalSpacing); messageLabelData.height = messageLabelHeight; if (titleImageLargest) messageLabelData.bottom = new FormAttachment(titleImageLabel, 0, SWT.BOTTOM); messageLabel.setLayoutData(messageLabelData); FormData fillerData = new FormData(); fillerData.left = new FormAttachment(0, horizontalSpacing); fillerData.top = new FormAttachment(messageImageLabel, 0); fillerData.bottom = new FormAttachment(messageLabel, 0, SWT.BOTTOM); bottomFillerLabel.setLayoutData(fillerData); FormData data = new FormData(); data.top = new FormAttachment(messageImageLabel, 0, SWT.TOP); data.left = new FormAttachment(0, 0); data.bottom = new FormAttachment(messageImageLabel, 0, SWT.BOTTOM); data.right = new FormAttachment(messageImageLabel, 0); leftFillerLabel.setLayoutData(data); } /** * The <code>TitleAreaDialog</code> implementation of this * <code>Window</code> methods returns an initial size which is at least * some reasonable minimum. * * @return the initial size of the dialog */ @Override protected Point getInitialSize() { Point shellSize = super.getInitialSize(); return new Point(Math.max(convertHorizontalDLUsToPixels(MIN_DIALOG_WIDTH), shellSize.x), Math.max(convertVerticalDLUsToPixels(MIN_DIALOG_HEIGHT), shellSize.y)); } /** * Retained for backward compatibility. * * Returns the title area composite. There is no composite in this * implementation so the shell is returned. * * @return Composite * @deprecated */ @Deprecated protected Composite getTitleArea() { return getShell(); } /** * Returns the title image label. * * @return the title image label */ protected Label getTitleImageLabel() { return titleImageLabel; } /** * Display the given error message. The currently displayed message is saved * and will be redisplayed when the error message is set to * <code>null</code>. * * @param newErrorMessage * the newErrorMessage to display or <code>null</code> */ public void setErrorMessage(String newErrorMessage) { // Any change? if (errorMessage == null ? newErrorMessage == null : errorMessage.equals(newErrorMessage)) return; errorMessage = newErrorMessage; // Clear or set error message. if (errorMessage == null) { if (showingError) { // we were previously showing an error showingError = false; } // show the message // avoid calling setMessage in case it is overridden to call // setErrorMessage, // which would result in a recursive infinite loop if (message == null) // this should probably never happen since // setMessage does this conversion.... message = ""; //$NON-NLS-1$ updateMessage(message); messageImageLabel.setImage(messageImage); setImageLabelVisible(messageImage != null); } else { // Add in a space for layout purposes but do not // change the instance variable String displayedErrorMessage = " " + errorMessage; //$NON-NLS-1$ updateMessage(displayedErrorMessage); if (!showingError) { // we were not previously showing an error showingError = true; messageImageLabel.setImage(JFaceResources.getImage(DLG_IMG_TITLE_ERROR)); setImageLabelVisible(true); } } layoutForNewMessage(false); } /** * Re-layout the labels for the new message. * * @param forceLayout * <code>true</code> to force a layout of the shell */ private void layoutForNewMessage(boolean forceLayout) { int verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); int horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); // If there are no images then layout as normal if (errorMessage == null && messageImage == null) { setImageLabelVisible(false); setLayoutsForNormalMessage(verticalSpacing, horizontalSpacing); } else { messageImageLabel.setVisible(true); bottomFillerLabel.setVisible(true); leftFillerLabel.setVisible(true); /** * Note that we do not use horizontalSpacing here as when the * background of the messages changes there will be gaps between the * icon label and the message that are the background color of the * shell. We add a leading space elsewhere to compendate for this. */ FormData data = new FormData(); data.left = new FormAttachment(0, H_GAP_IMAGE); data.top = new FormAttachment(titleLabel, verticalSpacing); messageImageLabel.setLayoutData(data); data = new FormData(); data.top = new FormAttachment(messageImageLabel, 0); data.left = new FormAttachment(0, 0); data.bottom = new FormAttachment(messageLabel, 0, SWT.BOTTOM); data.right = new FormAttachment(messageImageLabel, 0, SWT.RIGHT); bottomFillerLabel.setLayoutData(data); data = new FormData(); data.top = new FormAttachment(messageImageLabel, 0, SWT.TOP); data.left = new FormAttachment(0, 0); data.bottom = new FormAttachment(messageImageLabel, 0, SWT.BOTTOM); data.right = new FormAttachment(messageImageLabel, 0); leftFillerLabel.setLayoutData(data); FormData messageLabelData = new FormData(); messageLabelData.top = new FormAttachment(titleLabel, verticalSpacing); messageLabelData.right = new FormAttachment(titleImageLabel); messageLabelData.left = new FormAttachment(messageImageLabel, 0); messageLabelData.height = messageLabelHeight; if (titleImageLargest) messageLabelData.bottom = new FormAttachment(titleImageLabel, 0, SWT.BOTTOM); messageLabel.setLayoutData(messageLabelData); } if (forceLayout) { getShell().layout(); } else { // Do not layout before the dialog area has been created // to avoid incomplete calculations. if (dialogArea != null) workArea.getParent().layout(true); } int messageLabelUnclippedHeight = messageLabel.computeSize(messageLabel.getSize().x - xTrim, SWT.DEFAULT, true).y; boolean messageLabelClipped = messageLabelUnclippedHeight > messageLabel.getSize().y - yTrim; if (messageLabel.getData() instanceof ToolTip) { ToolTip toolTip = (ToolTip) messageLabel.getData(); toolTip.hide(); toolTip.deactivate(); messageLabel.setData(null); } if (messageLabelClipped) { ToolTip tooltip = new ToolTip(messageLabel, ToolTip.NO_RECREATE, false) { @Override protected Composite createToolTipContentArea(Event event, Composite parent) { Composite result = new Composite(parent, SWT.NONE); result.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); result.setLayout(new GridLayout()); Text text = new Text(result, SWT.WRAP); text.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); text.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); text.setText(messageLabel.getText()); GridData gridData = new GridData(); gridData.widthHint = messageLabel.getSize().x; text.setLayoutData(gridData); Dialog.applyDialogFont(result); return result; } @Override public Point getLocation(Point tipSize, Event event) { return messageLabel.getShell().toDisplay(messageLabel.getLocation()); } }; messageLabel.setData(tooltip); tooltip.setPopupDelay(0); tooltip.activate(); } } /** * Set the message text. If the message line currently displays an error, * the message is saved and will be redisplayed when the error message is * set to <code>null</code>. * <p> * Shortcut for <code>setMessage(newMessage, IMessageProvider.NONE)</code> * </p> * This method should be called after the dialog has been opened as it * updates the message label immediately. * * @param newMessage * the message, or <code>null</code> to clear the message */ public void setMessage(String newMessage) { setMessage(newMessage, IMessageProvider.NONE); } /** * Sets the message for this dialog with an indication of what type of * message it is. * <p> * The valid message types are one of <code>NONE</code>, * <code>INFORMATION</code>,<code>WARNING</code>, or * <code>ERROR</code>. * </p> * <p> * Note that for backward compatibility, a message of type * <code>ERROR</code> is different than an error message (set using * <code>setErrorMessage</code>). An error message overrides the current * message until the error message is cleared. This method replaces the * current message and does not affect the error message. * </p> * * @param newMessage * the message, or <code>null</code> to clear the message * @param newType * the message type * @since 2.0 */ public void setMessage(String newMessage, int newType) { Image newImage = null; if (newMessage != null) { switch (newType) { case IMessageProvider.NONE: break; case IMessageProvider.INFORMATION: newImage = JFaceResources.getImage(DLG_IMG_MESSAGE_INFO); break; case IMessageProvider.WARNING: newImage = JFaceResources.getImage(DLG_IMG_MESSAGE_WARNING); break; case IMessageProvider.ERROR: newImage = JFaceResources.getImage(DLG_IMG_MESSAGE_ERROR); break; } } showMessage(newMessage, newImage); } /** * Show the new message and image. * * @param newMessage * @param newImage */ private void showMessage(String newMessage, Image newImage) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=249915 if (newMessage == null) newMessage = ""; //$NON-NLS-1$ // Any change? if (message.equals(newMessage) && messageImage == newImage) { return; } message = newMessage; // Message string to be shown - if there is an image then add in // a space to the message for layout purposes String shownMessage = (newImage == null) ? message : " " + message; //$NON-NLS-1$ messageImage = newImage; if (!showingError) { // we are not showing an error updateMessage(shownMessage); messageImageLabel.setImage(messageImage); setImageLabelVisible(messageImage != null); layoutForNewMessage(false); } } /** * Update the contents of the messageLabel. * * @param newMessage * the message to use */ private void updateMessage(String newMessage) { String oldMessage = messageLabel.getText(); messageLabel.setText(newMessage); // Bug 248410 - This snippet will only work with Windows screen readers. messageLabel.getAccessible().sendEvent(ACC.EVENT_ATTRIBUTE_CHANGED, null); messageLabel.getAccessible().sendEvent(ACC.EVENT_TEXT_CHANGED, new Object[] { Integer.valueOf(ACC.TEXT_DELETE), Integer.valueOf(0), Integer.valueOf(oldMessage.length()), oldMessage }); messageLabel.getAccessible().sendEvent(ACC.EVENT_TEXT_CHANGED, new Object[] { Integer.valueOf(ACC.TEXT_INSERT), Integer.valueOf(0), Integer.valueOf(newMessage.length()), newMessage }); } /** * Sets the title to be shown in the title area of this dialog. * * @param newTitle * the title show */ public void setTitle(String newTitle) { if (titleLabel == null) return; String title = newTitle; if (title == null) title = "";//$NON-NLS-1$ titleLabel.setText(title); } /** * Sets the title bar color for this dialog. * * @param color * the title bar color */ public void setTitleAreaColor(RGB color) { titleAreaRGB = color; } /** * Sets the title image to be shown in the title area of this dialog. * * @param newTitleImage * the title image to be shown */ public void setTitleImage(Image newTitleImage) { titleAreaImage = newTitleImage; if (titleImageLabel != null) { titleImageLabel.setImage(newTitleImage); determineTitleImageLargest(); Control top; if (titleImageLargest) top = titleImageLabel; else top = messageLabel; resetWorkAreaAttachments(top); } } /** * Make the label used for displaying error images visible depending on * boolean. * * @param visible * If <code>true</code> make the image visible, if not then * make it not visible. */ private void setImageLabelVisible(boolean visible) { messageImageLabel.setVisible(visible); bottomFillerLabel.setVisible(visible); leftFillerLabel.setVisible(visible); } /** * Reset the attachment of the workArea to now attach to top as the top * control. * * @param top */ private void resetWorkAreaAttachments(Control top) { FormData childData = new FormData(); childData.top = new FormAttachment(top); childData.right = new FormAttachment(100, 0); childData.left = new FormAttachment(0, 0); childData.bottom = new FormAttachment(100, 0); workArea.setLayoutData(childData); } /** * Returns the current message text for this dialog. This message is * displayed in the message line of the dialog when the error message * is <code>null</code>. If there is a non-null error message, this * message is not shown, but is stored so that it can be shown in * the message line whenever {@link #setErrorMessage(String)} is called with * a <code>null</code> parameter. * * @return the message text, which is never <code>null</code>. * * @see #setMessage(String) * @see #setErrorMessage(String) * * @since 3.6 */ public String getMessage() { return message; } /** * Returns the current error message being shown in the dialog, or * <code>null</code> if there is no error message being shown. * * @return the error message, which may be <code>null</code>. * * @see #setErrorMessage(String) * @see #setMessage(String) * * @since 3.6 */ public String getErrorMessage() { return errorMessage; } }