/*******************************************************************************
 * 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
 *     Remy Chi Jian Suen <remy.suen@gmail.com> - Bug 218553 [JFace] mis-spelling of their in applyDialogFont(...)
 *******************************************************************************/
package org.eclipse.jface.dialogs;

import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;

import java.util.Arrays;
import java.util.HashMap;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.jface.window.SameShellProvider;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * A dialog is a specialized window used for narrow-focused communication with
 * the user.
 * <p>
 * Dialogs are usually modal. Consequently, it is generally bad practice to open
 * a dialog without a parent. A modal dialog without a parent is not prevented
 * from disappearing behind the application's other windows, making it very
 * confusing for the user.
 * </p>
 * <p>
 * If more than one modal dialog is open, the second one should be
 * parented off of the shell of the first one. Otherwise, it is possible that the
 * OS will give focus to the first dialog, potentially blocking the UI.
 * </p>
 * <p>
 * This class also moves the default button to the right if required, see
 * {@link #initializeBounds()}.
 * </p>
 */
public abstract class Dialog extends Window {
	/**
	 * Image registry key for error image (value
	 * <code>"dialog_error_image"</code>).
	 *
	 * @deprecated use
	 *             org.eclipse.swt.widgets.Display.getSystemImage(SWT.ICON_ERROR)
	 */
	@Deprecated
	public static final String DLG_IMG_ERROR = "dialog_error_image"; //$NON-NLS-1$

	/**
	 * Image registry key for info image (value <code>"dialog_info_image"</code>).
	 *
	 * @deprecated use
	 *             org.eclipse.swt.widgets.Display.getSystemImage(SWT.ICON_INFORMATION)
	 */
	@Deprecated
	public static final String DLG_IMG_INFO = "dialog_info_imageg"; //$NON-NLS-1$

	/**
	 * Image registry key for question image (value
	 * <code>"dialog_question_image"</code>).
	 *
	 * @deprecated org.eclipse.swt.widgets.Display.getSystemImage(SWT.ICON_QUESTION)
	 */
	@Deprecated
	public static final String DLG_IMG_QUESTION = "dialog_question_image"; //$NON-NLS-1$

	/**
	 * Image registry key for warning image (value
	 * <code>"dialog_warning_image"</code>).
	 *
	 * @deprecated use
	 *             org.eclipse.swt.widgets.Display.getSystemImage(SWT.ICON_WARNING)
	 */
	@Deprecated
	public static final String DLG_IMG_WARNING = "dialog_warning_image"; //$NON-NLS-1$

	/**
	 * Image registry key for info message image (value
	 * <code>"dialog_messasge_info_image"</code>).
	 *
	 * @since 2.0
	 */
	public static final String DLG_IMG_MESSAGE_INFO = "dialog_messasge_info_image"; //$NON-NLS-1$

	/**
	 * Image registry key for info message image (value
	 * <code>"dialog_messasge_warning_image"</code>).
	 *
	 * @since 2.0
	 */
	public static final String DLG_IMG_MESSAGE_WARNING = "dialog_messasge_warning_image"; //$NON-NLS-1$

	/**
	 * Image registry key for info message image (value
	 * <code>"dialog_message_error_image"</code>).
	 *
	 * @since 2.0
	 */
	public static final String DLG_IMG_MESSAGE_ERROR = "dialog_message_error_image"; //$NON-NLS-1$

	/**
	 * Image registry key for help image (value
	 * <code>"dialog_help_image"</code>).
	 *
	 * @since 3.2
	 */
	public static final String DLG_IMG_HELP = "dialog_help_image"; //$NON-NLS-1$

	/**
	 * The ellipsis is the string that is used to represent shortened text.
	 *
	 * @since 3.0
	 */
	public static final String ELLIPSIS = "..."; //$NON-NLS-1$

	/**
	 * The dialog settings key name for stored dialog x location.
	 *
	 * @since 3.2
	 */
	private static final String DIALOG_ORIGIN_X = "DIALOG_X_ORIGIN"; //$NON-NLS-1$

	/**
	 * The dialog settings key name for stored dialog y location.
	 *
	 * @since 3.2
	 */
	private static final String DIALOG_ORIGIN_Y = "DIALOG_Y_ORIGIN"; //$NON-NLS-1$

	/**
	 * The dialog settings key name for stored dialog width.
	 *
	 * @since 3.2
	 */
	private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$

	/**
	 * The dialog settings key name for stored dialog height.
	 *
	 * @since 3.2
	 */
	private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$

	/**
	 * The dialog settings key name for the font used when the dialog
	 * height and width was stored.
	 *
	 *@since 3.2
	 */
	private static final String DIALOG_FONT_DATA = "DIALOG_FONT_NAME"; //$NON-NLS-1$

	/**
	 * A value that can be used for stored dialog width or height that
	 * indicates that the default bounds should be used.
	 *
	 * @since 3.2
	 */
	public static final int DIALOG_DEFAULT_BOUNDS = -1;

	/**
	 * Constants that can be used for specifying the strategy for persisting
	 * dialog bounds.  These constants represent bit masks that can be used
	 * together.
	 *
	 *@since 3.2
	 */

	/**
	 * Persist the last location of the dialog.
	 * @since 3.2
	 */
	public static final int DIALOG_PERSISTLOCATION = 0x0001;
	/**
	 * Persist the last known size of the dialog.
	 * @since 3.2
	 */
	public static final int DIALOG_PERSISTSIZE = 0x0002;

	/**
	 * The dialog area; <code>null</code> until dialog is layed out.
	 */
	protected Control dialogArea;

	/**
	 * The button bar; <code>null</code> until dialog is layed out.
	 */
	public Control buttonBar;

	/**
	 * Collection of buttons created by the <code>createButton</code> method.
	 */
	private HashMap<Integer, Button> buttons = new HashMap<>();

	/**
	 * Font metrics to use for determining pixel sizes.
	 */
	private FontMetrics fontMetrics;

	/**
	 * Number of horizontal dialog units per character, value <code>4</code>.
	 */
	private static final int HORIZONTAL_DIALOG_UNIT_PER_CHAR = 4;

	/**
	 * Number of vertical dialog units per character, value <code>8</code>.
	 */
	private static final int VERTICAL_DIALOG_UNITS_PER_CHAR = 8;

	/**
	 * Returns the number of pixels corresponding to the height of the given
	 * number of characters.
	 * <p>
	 * The required <code>FontMetrics</code> parameter may be created in the
	 * following way: <code>
	 * 	GC gc = new GC(control);
	 *	gc.setFont(control.getFont());
	 *	fontMetrics = gc.getFontMetrics();
	 *	gc.dispose();
	 * </code>
	 * </p>
	 *
	 * @param fontMetrics
	 *            used in performing the conversion
	 * @param chars
	 *            the number of characters
	 * @return the number of pixels
	 * @since 2.0
	 */
	public static int convertHeightInCharsToPixels(FontMetrics fontMetrics,
			int chars) {
		return fontMetrics.getHeight() * chars;
	}

	/**
	 * Returns the number of pixels corresponding to the given number of
	 * horizontal dialog units.
	 * <p>
	 * The required <code>FontMetrics</code> parameter may be created in the
	 * following way: <code>
	 * 	GC gc = new GC(control);
	 *	gc.setFont(control.getFont());
	 *	fontMetrics = gc.getFontMetrics();
	 *	gc.dispose();
	 * </code>
	 * </p>
	 *
	 * @param fontMetrics
	 *            used in performing the conversion
	 * @param dlus
	 *            the number of horizontal dialog units
	 * @return the number of pixels
	 * @since 2.0
	 */
	public static int convertHorizontalDLUsToPixels(FontMetrics fontMetrics,
			int dlus) {
		// round to the nearest pixel
		return (fontMetrics.getAverageCharWidth() * dlus + HORIZONTAL_DIALOG_UNIT_PER_CHAR / 2)
				/ HORIZONTAL_DIALOG_UNIT_PER_CHAR;
	}

	/**
	 * Returns the number of pixels corresponding to the given number of
	 * vertical dialog units.
	 * <p>
	 * The required <code>FontMetrics</code> parameter may be created in the
	 * following way: <code>
	 * 	GC gc = new GC(control);
	 *	gc.setFont(control.getFont());
	 *	fontMetrics = gc.getFontMetrics();
	 *	gc.dispose();
	 * </code>
	 * </p>
	 *
	 * @param fontMetrics
	 *            used in performing the conversion
	 * @param dlus
	 *            the number of vertical dialog units
	 * @return the number of pixels
	 * @since 2.0
	 */
	public static int convertVerticalDLUsToPixels(FontMetrics fontMetrics,
			int dlus) {
		// round to the nearest pixel
		return (fontMetrics.getHeight() * dlus + VERTICAL_DIALOG_UNITS_PER_CHAR / 2)
				/ VERTICAL_DIALOG_UNITS_PER_CHAR;
	}

	/**
	 * Returns the number of pixels corresponding to the width of the given
	 * number of characters.
	 * <p>
	 * The required <code>FontMetrics</code> parameter may be created in the
	 * following way: <code>
	 * 	GC gc = new GC(control);
	 *	gc.setFont(control.getFont());
	 *	fontMetrics = gc.getFontMetrics();
	 *	gc.dispose();
	 * </code>
	 * </p>
	 *
	 * @param fontMetrics
	 *            used in performing the conversion
	 * @param chars
	 *            the number of characters
	 * @return the number of pixels
	 * @since 2.0
	 */
	public static int convertWidthInCharsToPixels(FontMetrics fontMetrics,
			int chars) {
		return fontMetrics.getAverageCharWidth() * chars;
	}

	/**
	 * Shortens the given text <code>textValue</code> so that its width in
	 * pixels does not exceed the width of the given control. Overrides
	 * characters in the center of the original string with an ellipsis ("...")
	 * if necessary. If a <code>null</code> value is given, <code>null</code>
	 * is returned.
	 *
	 * @param textValue
	 *            the original string or <code>null</code>
	 * @param control
	 *            the control the string will be displayed on
	 * @return the string to display, or <code>null</code> if null was passed
	 *         in
	 *
	 * @since 3.0
	 */
	public static String shortenText(String textValue, Control control) {
		if (textValue == null) {
			return null;
		}
		GC gc = new GC(control);
		int maxWidth = control.getBounds().width - 5;
		int maxExtent = gc.textExtent(textValue).x;
		if (maxExtent < maxWidth) {
			gc.dispose();
			return textValue;
		}
		int length = textValue.length();
		int charsToClip = Math.round(0.95f*length * (1 - ((float)maxWidth/maxExtent)));
		int pivot = length / 2;
		int start = pivot - (charsToClip/2);
		int end = pivot + (charsToClip/2) + 1;
		while (start >= 0 && end < length) {
			String s1 = textValue.substring(0, start);
			String s2 = textValue.substring(end, length);
			String s = s1 + ELLIPSIS + s2;
			int l = gc.textExtent(s).x;
			if (l < maxWidth) {
				gc.dispose();
				return s;
			}
			start--;
			end++;
		}
		gc.dispose();
		return textValue;
	}

	/**
	 * Create a default instance of the blocked handler which does not do
	 * anything.
	 */
	public static IDialogBlockedHandler blockedHandler = new IDialogBlockedHandler() {

		@Override
		public void clearBlocked() {
			// No default behavior
		}

		@Override
		public void showBlocked(IProgressMonitor blocking,
				IStatus blockingStatus, String blockedName) {
			// No default behavior
		}

		@Override
		public void showBlocked(Shell parentShell, IProgressMonitor blocking,
				IStatus blockingStatus, String blockedName) {
			// No default behavior
		}
	};

	/**
	 * Creates a dialog instance. Note that the window will have no visual
	 * representation (no widgets) until it is told to open. By default,
	 * <code>open</code> blocks for dialogs.
	 *
	 * @param parentShell
	 *            the parent shell, or <code>null</code> to create a top-level
	 *            shell
	 */
	protected Dialog(Shell parentShell) {
		this(new SameShellProvider(parentShell));
		if (parentShell == null && Policy.DEBUG_DIALOG_NO_PARENT) {
			Policy.getLog().log(
					new Status(IStatus.INFO, Policy.JFACE, IStatus.INFO, this
							.getClass()
							+ " created with no shell",//$NON-NLS-1$
							new Exception()));
		}
	}

	/**
	 * Creates a dialog with the given parent.
	 *
	 * @param parentShell
	 *            object that returns the current parent shell
	 *
	 * @since 3.1
	 */
	protected Dialog(IShellProvider parentShell) {
		super(parentShell);
		if (isResizable()) {
			setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.MAX | SWT.RESIZE
					| getDefaultOrientation());
		} else {
			setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL
					| getDefaultOrientation());
		}
		setBlockOnOpen(true);
	}

	/**
	 * Notifies that this dialog's button with the given id has been pressed.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method calls
	 * <code>okPressed</code> if the ok button is the pressed, and
	 * <code>cancelPressed</code> if the cancel button is the pressed. All
	 * other button presses are ignored. Subclasses may override to handle other
	 * buttons, but should call <code>super.buttonPressed</code> if the
	 * default handling of the ok and cancel buttons is desired.
	 * </p>
	 *
	 * @param buttonId
	 *            the id of the button that was pressed (see
	 *            <code>IDialogConstants.*_ID</code> constants)
	 */
	protected void buttonPressed(int buttonId) {
		if (IDialogConstants.OK_ID == buttonId) {
			okPressed();
		} else if (IDialogConstants.CANCEL_ID == buttonId) {
			cancelPressed();
		}
	}

	/**
	 * Notifies that the cancel button of this dialog has been pressed.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method sets
	 * this dialog's return code to <code>Window.CANCEL</code> and closes the
	 * dialog. Subclasses may override if desired.
	 * </p>
	 */
	protected void cancelPressed() {
		setReturnCode(CANCEL);
		close();
	}

	/**
	 * Returns the number of pixels corresponding to the height of the given
	 * number of characters.
	 * <p>
	 * This method may only be called after <code>initializeDialogUnits</code>
	 * has been called.
	 * </p>
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @param chars
	 *            the number of characters
	 * @return the number of pixels
	 */
	protected int convertHeightInCharsToPixels(int chars) {
		// test for failure to initialize for backward compatibility
		if (fontMetrics == null) {
			return 0;
		}
		return convertHeightInCharsToPixels(fontMetrics, chars);
	}

	/**
	 * Returns the number of pixels corresponding to the given number of
	 * horizontal dialog units.
	 * <p>
	 * This method may only be called after <code>initializeDialogUnits</code>
	 * has been called.
	 * </p>
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @param dlus
	 *            the number of horizontal dialog units
	 * @return the number of pixels
	 */
	protected int convertHorizontalDLUsToPixels(int dlus) {
		// test for failure to initialize for backward compatibility
		if (fontMetrics == null) {
			return 0;
		}
		return convertHorizontalDLUsToPixels(fontMetrics, dlus);
	}

	/**
	 * Returns the number of pixels corresponding to the given number of
	 * vertical dialog units.
	 * <p>
	 * This method may only be called after <code>initializeDialogUnits</code>
	 * has been called.
	 * </p>
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @param dlus
	 *            the number of vertical dialog units
	 * @return the number of pixels
	 */
	protected int convertVerticalDLUsToPixels(int dlus) {
		// test for failure to initialize for backward compatibility
		if (fontMetrics == null) {
			return 0;
		}
		return convertVerticalDLUsToPixels(fontMetrics, dlus);
	}

	/**
	 * Returns the number of pixels corresponding to the width of the given
	 * number of characters.
	 * <p>
	 * This method may only be called after <code>initializeDialogUnits</code>
	 * has been called.
	 * </p>
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @param chars
	 *            the number of characters
	 * @return the number of pixels
	 */
	protected int convertWidthInCharsToPixels(int chars) {
		// test for failure to initialize for backward compatibility
		if (fontMetrics == null) {
			return 0;
		}
		return convertWidthInCharsToPixels(fontMetrics, chars);
	}

	/**
	 * Creates a new button with the given id.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method creates
	 * a standard push button, registers it for selection events including
	 * button presses, and registers default buttons with its shell. The button
	 * id is stored as the button's client data. If the button id is
	 * <code>IDialogConstants.CANCEL_ID</code>, the new button will be
	 * accessible from <code>getCancelButton()</code>. If the button id is
	 * <code>IDialogConstants.OK_ID</code>, the new button will be accesible
	 * from <code>getOKButton()</code>. Note that the parent's layout is
	 * assumed to be a <code>GridLayout</code> and the number of columns in
	 * this layout is incremented. Subclasses may override.
	 * </p>
	 * <p>
	 * Note: The common button order is: <b>{other buttons}</b>, <b>OK</b>, <b>Cancel</b>.
	 * On some platforms, {@link #initializeBounds()} will move the default button to the right.
	 * </p>
	 *
	 * @param parent
	 *            the parent composite
	 * @param id
	 *            the id of the button (see <code>IDialogConstants.*_ID</code>
	 *            constants for standard dialog button ids)
	 * @param label
	 *            the label from the button
	 * @param defaultButton
	 *            <code>true</code> if the button is to be the default button,
	 *            and <code>false</code> otherwise
	 *
	 * @return the new button
	 *
	 * @see #getCancelButton
	 * @see #getOKButton()
	 */
	protected Button createButton(Composite parent, int id, String label, boolean defaultButton) {
		// increment the number of columns in the button bar
		((GridLayout) parent.getLayout()).numColumns++;
		Button button = new Button(parent, SWT.PUSH);
		button.setText(label);
		button.setFont(JFaceResources.getDialogFont());
		button.setData(Integer.valueOf(id));
		button.addSelectionListener(widgetSelectedAdapter(event -> buttonPressed(((Integer) event.widget.getData()).intValue())));
		if (defaultButton) {
			Shell shell = parent.getShell();
			if (shell != null) {
				shell.setDefaultButton(button);
			}
		}
		buttons.put(Integer.valueOf(id), button);
		setButtonLayoutData(button);
		return button;
	}

	/**
	 * Creates and returns the contents of this dialog's button bar.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method lays
	 * out a button bar and calls the <code>createButtonsForButtonBar</code>
	 * framework method to populate it. Subclasses may override.
	 * </p>
	 * <p>
	 * The returned control's layout data must be an instance of
	 * <code>GridData</code>.
	 * </p>
	 *
	 * @param parent
	 *            the parent composite to contain the button bar
	 * @return the button bar control
	 */
	protected Control createButtonBar(Composite parent) {
		Composite composite = new Composite(parent, SWT.NONE);
		// create a layout with spacing and margins appropriate for the font
		// size.
		GridLayout layout = new GridLayout();
		layout.numColumns = 0; // this is incremented by createButton
		layout.makeColumnsEqualWidth = true;
		layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
		layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
		layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
		layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
		composite.setLayout(layout);
		GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END
				| GridData.VERTICAL_ALIGN_CENTER);
		composite.setLayoutData(data);
		composite.setFont(parent.getFont());

		// Add the buttons to the button bar.
		createButtonsForButtonBar(composite);
		return composite;
	}

	/**
	 * Adds buttons to this dialog's button bar.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method adds
	 * standard ok and cancel buttons using the <code>createButton</code>
	 * framework method. These standard buttons will be accessible from
	 * <code>getCancelButton</code>, and <code>getOKButton</code>.
	 * Subclasses may override.
	 * </p>
	 * <p>
	 * Note: The common button order is: <b>{other buttons}</b>, <b>OK</b>, <b>Cancel</b>.
	 * On some platforms, {@link #initializeBounds()} will move the default button to the right.
	 * </p>
	 *
	 * @param parent
	 *            the button bar composite
	 */
	protected void createButtonsForButtonBar(Composite parent) {
		// create OK and Cancel buttons by default
		createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
		createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * The implementation in {@link #Dialog} also moves the
	 * {@link Shell#getDefaultButton() default button} in the
	 * {@link #createButtonBar(Composite) button bar} to the right
	 * if that's required by the
	 * {@link Display#getDismissalAlignment() platform convention}.
	 * </p>
	 */
	@Override
	protected void initializeBounds() {
		// UI guidelines:
		// https://developer.gnome.org/hig/stable/dialogs.html.en#primary-buttons
		// https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/WindowDialogs.html#//apple_ref/doc/uid/20000957-CH43-SW5
		// https://msdn.microsoft.com/en-us/library/windows/desktop/dn742499(v=vs.85).aspx#win_dialog_box_image25
		Shell shell = getShell();
		if (shell != null) {
			if (shell.getDisplay().getDismissalAlignment() == SWT.RIGHT) {
				// make the default button the right-most button
				Button defaultButton = shell.getDefaultButton();
				if (defaultButton != null
						&& isContained(buttonBar, defaultButton)) {
					defaultButton.moveBelow(null);
					defaultButton.getParent().layout();
				}
			}
		}

		super.initializeBounds();
	}

	/**
	 * Returns true if the given Control is a direct or indirect child of
	 * container.
	 *
	 * @param container
	 *            the potential parent
	 * @param control
	 * @return boolean <code>true</code> if control is a child of container
	 */
	private boolean isContained(Control container, Control control) {
		Composite parent;
		while ((parent = control.getParent()) != null) {
			if (parent == container) {
				return true;
			}
			control = parent;
		}
		return false;
	}

	/**
	 * The <code>Dialog</code> implementation of this <code>Window</code>
	 * method creates and lays out the top level composite for the dialog, and
	 * determines the appropriate horizontal and vertical dialog units based on
	 * the font size. It then calls the <code>createDialogArea</code> and
	 * <code>createButtonBar</code> methods to create the dialog area and
	 * button bar, respectively. Overriding <code>createDialogArea</code> and
	 * <code>createButtonBar</code> are recommended rather than overriding
	 * this method.
	 */
	@Override
	protected Control createContents(Composite parent) {
		// create the top level composite for the dialog
		Composite composite = new Composite(parent, 0);
		GridLayout layout = new GridLayout();
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		layout.verticalSpacing = 0;
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		applyDialogFont(composite);
		// initialize the dialog units
		initializeDialogUnits(composite);
		// create the dialog area and button bar
		dialogArea = createDialogArea(composite);
		buttonBar = createButtonBar(composite);

		return composite;
	}

	/**
	 * 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 standard margins and
	 * spacing.
	 * </p>
	 * <p>
	 * The returned control's layout data must be an instance of
	 * <code>GridData</code>. This method must not modify the parent's
	 * layout.
	 * </p>
	 * <p>
	 * Subclasses must override this method but may call <code>super</code> as
	 * in the following example:
	 * </p>
	 *
	 * <pre>
	 * Composite composite = (Composite) super.createDialogArea(parent);
	 * //add controls to composite as necessary
	 * return composite;
	 * </pre>
	 *
	 * @param parent
	 *            the parent composite to contain the dialog area
	 * @return the dialog area control
	 */
	protected Control createDialogArea(Composite parent) {
		// create a composite with standard margins and spacing
		Composite composite = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
		layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
		layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
		layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
		composite.setLayout(layout);
		composite.setLayoutData(new GridData(GridData.FILL_BOTH));
		applyDialogFont(composite);
		return composite;
	}

	/**
	 * Returns the button created by the method <code>createButton</code> for
	 * the specified ID as defined on <code>IDialogConstants</code>. If
	 * <code>createButton</code> was never called with this ID, or if
	 * <code>createButton</code> is overridden, this method will return
	 * <code>null</code>.
	 *
	 * @param id
	 *            the id of the button to look for
	 *
	 * @return the button for the ID or <code>null</code>
	 *
	 * @see #createButton(Composite, int, String, boolean)
	 * @since 2.0
	 */
	protected Button getButton(int id) {
		return buttons.get(Integer.valueOf(id));
	}

	/**
	 * Returns the button bar control.
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @return the button bar, or <code>null</code> if the button bar has not
	 *         been created yet
	 */
	protected Control getButtonBar() {
		return buttonBar;
	}

	/**
	 * Returns the button created when <code>createButton</code> is called
	 * with an ID of <code>IDialogConstants.CANCEL_ID</code>. If
	 * <code>createButton</code> was never called with this parameter, or if
	 * <code>createButton</code> is overridden, <code>getCancelButton</code>
	 * will return <code>null</code>.
	 *
	 * @return the cancel button or <code>null</code>
	 *
	 * @see #createButton(Composite, int, String, boolean)
	 * @since 2.0
	 * @deprecated Use <code>getButton(IDialogConstants.CANCEL_ID)</code>
	 *             instead. This method will be removed soon.
	 */
	@Deprecated
	protected Button getCancelButton() {
		return getButton(IDialogConstants.CANCEL_ID);
	}

	/**
	 * Returns the dialog area control.
	 * <p>
	 * Clients may call this framework method, but should not override it.
	 * </p>
	 *
	 * @return the dialog area, or <code>null</code> if the dialog area has
	 *         not been created yet
	 */
	protected Control getDialogArea() {
		return dialogArea;
	}

	/**
	 * Returns the standard dialog image with the given key. Note that these
	 * images are managed by the dialog framework, and must not be disposed by
	 * another party.
	 *
	 * @param key
	 *            one of the <code>Dialog.DLG_IMG_* </code> constants
	 * @return the standard dialog image
	 *
	 * NOTE: Dialog does not use the following images in the registry
	 * DLG_IMG_ERROR DLG_IMG_INFO DLG_IMG_QUESTION DLG_IMG_WARNING
	 *
	 * They are now coming directly from SWT, see ImageRegistry. For backwards
	 * compatibility they are still supported, however new code should use SWT
	 * for these.
	 *
	 * @see Display#getSystemImage(int)
	 */
	public static Image getImage(String key) {
		return JFaceResources.getImageRegistry().get(key);
	}

	/**
	 * Returns the button created when <code>createButton</code> is called
	 * with an ID of <code>IDialogConstants.OK_ID</code>. If
	 * <code>createButton</code> was never called with this parameter, or if
	 * <code>createButton</code> is overridden, <code>getOKButton</code>
	 * will return <code>null</code>.
	 *
	 * @return the OK button or <code>null</code>
	 *
	 * @see #createButton(Composite, int, String, boolean)
	 * @since 2.0
	 * @deprecated Use <code>getButton(IDialogConstants.OK_ID)</code> instead.
	 *             This method will be removed soon.
	 */
	@Deprecated
	protected Button getOKButton() {
		return getButton(IDialogConstants.OK_ID);
	}

	/**
	 * Initializes the computation of horizontal and vertical dialog units based
	 * on the size of current font.
	 * <p>
	 * This method must be called before any of the dialog unit based conversion
	 * methods are called.
	 * </p>
	 *
	 * @param control
	 *            a control from which to obtain the current font
	 */
	protected void initializeDialogUnits(Control control) {
		// Compute and store a font metric
		GC gc = new GC(control);
		gc.setFont(JFaceResources.getDialogFont());
		fontMetrics = gc.getFontMetrics();
		gc.dispose();
	}

	/**
	 * Notifies that the ok button of this dialog has been pressed.
	 * <p>
	 * The <code>Dialog</code> implementation of this framework method sets
	 * this dialog's return code to <code>Window.OK</code> and closes the
	 * dialog. Subclasses may override.
	 * </p>
	 */
	protected void okPressed() {
		setReturnCode(OK);
		close();
	}

	/**
	 * Set the layout data of the button to a GridData with appropriate heights
	 * and widths.
	 *
	 * @param button
	 */
	protected void setButtonLayoutData(Button button) {
		GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
		int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
		Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		data.widthHint = Math.max(widthHint, minSize.x);
		button.setLayoutData(data);
	}

	/**
	 * Set the layout data of the button to a FormData with appropriate heights
	 * and widths.
	 *
	 * @param button
	 */
	protected void setButtonLayoutFormData(Button button) {
		FormData data = new FormData();
		int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH);
		Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		data.width = Math.max(widthHint, minSize.x);
		button.setLayoutData(data);
	}

	/**
	 * @see org.eclipse.jface.window.Window#close()
	 */
	@Override
	public boolean close() {
		if (getShell() != null && !getShell().isDisposed()) {
			saveDialogBounds(getShell());
		}

		boolean returnValue = super.close();
		if (returnValue) {
			buttons = new HashMap<>();
			buttonBar = null;
			dialogArea = null;
		}
		return returnValue;
	}

	/**
	 * Applies the dialog font to all controls that currently have the default
	 * font.
	 *
	 * @param control
	 *            the control to apply the font to. Font will also be applied to
	 *            its children. If the control is <code>null</code> nothing
	 *            happens.
	 */
	public static void applyDialogFont(Control control) {
		if (control == null || dialogFontIsDefault()) {
			return;
		}
		Font dialogFont = JFaceResources.getDialogFont();
		applyDialogFont(control, dialogFont);
	}

	/**
	 * Sets the dialog font on the control and any of its children if their font
	 * is not otherwise set.
	 *
	 * @param control
	 *            the control to apply the font to. Font will also be applied to
	 *            its children.
	 * @param dialogFont
	 *            the dialog font to set
	 */
	private static void applyDialogFont(Control control, Font dialogFont) {
		if (hasDefaultFont(control)) {
			control.setFont(dialogFont);
		}
		if (control instanceof Composite) {
			Control[] children = ((Composite) control).getChildren();
			for (Control element : children) {
				applyDialogFont(element, dialogFont);
			}
		}
	}

	/**
	 * Return whether or not this control has the same font as it's default.
	 *
	 * @param control
	 *            Control
	 * @return boolean
	 */
	private static boolean hasDefaultFont(Control control) {
		FontData[] controlFontData = control.getFont().getFontData();
		FontData[] defaultFontData = getDefaultFont(control).getFontData();
		if (controlFontData.length == defaultFontData.length) {
			for (int i = 0; i < controlFontData.length; i++) {
				if (controlFontData[i].equals(defaultFontData[i])) {
					continue;
				}
				return false;
			}
			return true;
		}
		return false;
	}

	/**
	 * Get the default font for this type of control.
	 *
	 * @param control
	 * @return the default font
	 */
	private static Font getDefaultFont(Control control) {
		String fontName = "DEFAULT_FONT_" + control.getClass().getName(); //$NON-NLS-1$
		if (JFaceResources.getFontRegistry().hasValueFor(fontName)) {
			return JFaceResources.getFontRegistry().get(fontName);
		}
		Font cached = control.getFont();
		control.setFont(null);
		Font defaultFont = control.getFont();
		control.setFont(cached);
		JFaceResources.getFontRegistry().put(fontName,
				defaultFont.getFontData());
		return defaultFont;
	}

	/**
	 * Return whether or not the dialog font is currently the same as the
	 * default font.
	 *
	 * @return boolean if the two are the same
	 */
	protected static boolean dialogFontIsDefault() {
		FontData[] dialogFontData = JFaceResources.getFontRegistry()
				.getFontData(JFaceResources.DIALOG_FONT);
		FontData[] defaultFontData = JFaceResources.getFontRegistry()
				.getFontData(JFaceResources.DEFAULT_FONT);
		return Arrays.equals(dialogFontData, defaultFontData);
	}

	@Override
	public void create() {
		super.create();
		applyDialogFont(buttonBar);
	}

	/**
	 * Get the IDialogBlockedHandler to be used by WizardDialogs and
	 * ModalContexts.
	 *
	 * @return Returns the blockedHandler.
	 */
	public static IDialogBlockedHandler getBlockedHandler() {
		return blockedHandler;
	}

	/**
	 * Set the IDialogBlockedHandler to be used by WizardDialogs and
	 * ModalContexts.
	 *
	 * @param blockedHandler
	 *            The blockedHandler for the dialogs.
	 */
	public static void setBlockedHandler(IDialogBlockedHandler blockedHandler) {
		Dialog.blockedHandler = blockedHandler;
	}

	/**
	 * Gets the dialog settings that should be used for remembering the bounds of
	 * of the dialog, according to the dialog bounds strategy.
	 *
	 * @return settings the dialog settings used to store the dialog's location
	 *         and/or size, or <code>null</code> if the dialog's bounds should
	 *         never be stored.
	 *
	 * @since 3.2
	 * @see Dialog#getDialogBoundsStrategy()
	 */
	protected IDialogSettings getDialogBoundsSettings() {
		return null;
	}

	/**
	 * Get the integer constant that describes the strategy for persisting the
	 * dialog bounds. This strategy is ignored if the implementer does not also
	 * specify the dialog settings for storing the bounds in
	 * Dialog.getDialogBoundsSettings().
	 *
	 * @return the constant describing the strategy for persisting the dialog
	 *         bounds.
	 *
	 * @since 3.2
	 * @see Dialog#DIALOG_PERSISTLOCATION
	 * @see Dialog#DIALOG_PERSISTSIZE
	 * @see Dialog#getDialogBoundsSettings()
	 */
	protected int getDialogBoundsStrategy() {
		return DIALOG_PERSISTLOCATION | DIALOG_PERSISTSIZE;
	}

	/**
	 * Saves the bounds of the shell in the appropriate dialog settings. The
	 * bounds are recorded relative to the parent shell, if there is one, or
	 * display coordinates if there is no parent shell.
	 *
	 * @param shell
	 *            The shell whose bounds are to be stored
	 *
	 * @since 3.2
	 */
	private void saveDialogBounds(Shell shell) {
		IDialogSettings settings = getDialogBoundsSettings();
		if (settings != null) {
			Point shellLocation = shell.getLocation();
			Point shellSize = shell.getSize();
			Shell parent = getParentShell();
			if (parent != null) {
				Point parentLocation = parent.getLocation();
				shellLocation.x -= parentLocation.x;
				shellLocation.y -= parentLocation.y;
			}
			int strategy = getDialogBoundsStrategy();
			if ((strategy & DIALOG_PERSISTLOCATION) != 0) {
				settings.put(DIALOG_ORIGIN_X, shellLocation.x);
				settings.put(DIALOG_ORIGIN_Y, shellLocation.y);
			}
			if ((strategy & DIALOG_PERSISTSIZE) != 0) {
				settings.put(DIALOG_WIDTH, shellSize.x);
				settings.put(DIALOG_HEIGHT, shellSize.y);
				FontData [] fontDatas = JFaceResources.getDialogFont().getFontData();
				if (fontDatas.length > 0) {
					settings.put(DIALOG_FONT_DATA, fontDatas[0].toString());
				}
			}
		}
	}

	/**
	 * Returns the initial size to use for the shell. Overridden
	 * to check whether a size has been stored in dialog settings.
	 * If a size has been stored, it is returned.
	 *
	 * @return the initial size of the shell
	 *
	 * @since 3.2
	 * @see #getDialogBoundsSettings()
	 * @see #getDialogBoundsStrategy()
	 */
	@Override
	protected Point getInitialSize() {
		Point result = super.getInitialSize();

		// Check the dialog settings for a stored size.
		if ((getDialogBoundsStrategy() & DIALOG_PERSISTSIZE)!= 0) {
			IDialogSettings settings = getDialogBoundsSettings();
			if (settings != null) {
				// Check that the dialog font matches the font used
				// when the bounds was stored.  If the font has changed,
				// we do not honor the stored settings.
				// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132821
				boolean useStoredBounds = true;
				String previousDialogFontData = settings.get(DIALOG_FONT_DATA);
				// There is a previously stored font, so we will check it.
				// Note that if we haven't stored the font before, then we will
				// use the stored bounds.  This allows restoring of dialog bounds
				// that were stored before we started storing the fontdata.
				if (previousDialogFontData != null && previousDialogFontData.length() > 0) {
					FontData [] fontDatas = JFaceResources.getDialogFont().getFontData();
					if (fontDatas.length > 0) {
						String currentDialogFontData = fontDatas[0].toString();
						useStoredBounds = currentDialogFontData.equalsIgnoreCase(previousDialogFontData);
					}
				}
				if (useStoredBounds) {
					try {
						// Get the stored width and height.
						int width = settings.getInt(DIALOG_WIDTH);
						if (width != DIALOG_DEFAULT_BOUNDS) {
							result.x = width;
						}
						int height = settings.getInt(DIALOG_HEIGHT);
						if (height != DIALOG_DEFAULT_BOUNDS) {
							result.y = height;
						}

					} catch (NumberFormatException e) {
					}
				}
			}
		}
		// No attempt is made to constrain the bounds. The default
		// constraining behavior in Window will be used.
		return result;
	}

	/**
	 * Returns the initial location to use for the shell. Overridden
	 * to check whether the bounds of the dialog have been stored in
	 * dialog settings.  If a location has been stored, it is returned.
	 *
	 * @param initialSize
	 *            the initial size of the shell, as returned by
	 *            <code>getInitialSize</code>.
	 * @return the initial location of the shell
	 *
	 * @since 3.2
	 * @see #getDialogBoundsSettings()
	 * @see #getDialogBoundsStrategy()
	 */
	@Override
	protected Point getInitialLocation(Point initialSize) {
		Point result = super.getInitialLocation(initialSize);
		if ((getDialogBoundsStrategy() & DIALOG_PERSISTLOCATION)!= 0) {
			IDialogSettings settings = getDialogBoundsSettings();
			if (settings != null) {
				try {
					int x = settings.getInt(DIALOG_ORIGIN_X);
					int y = settings.getInt(DIALOG_ORIGIN_Y);
					result = new Point(x, y);
					// The coordinates were stored relative to the parent shell.
					// Convert to display coordinates.
					Shell parent = getParentShell();
					if (parent != null) {
						Point parentLocation = parent.getLocation();
						result.x += parentLocation.x;
						result.y += parentLocation.y;
					}
				} catch (NumberFormatException e) {
				}
			}
		}
		// No attempt is made to constrain the bounds. The default
		// constraining behavior in Window will be used.
		return result;
	}

	/**
	 * Returns a boolean indicating whether the dialog should be
	 * considered resizable when the shell style is initially
	 * set.
	 *
	 * This method is used to ensure that all style
	 * bits appropriate for resizable dialogs are added to the
	 * shell style.  Individual dialogs may always set the shell
	 * style to ensure that a dialog is resizable, but using this
	 * method ensures that resizable dialogs will be created with
	 * the same set of style bits.
	 *
	 * Style bits will never be removed based on the return value
	 * of this method.  For example, if a dialog returns
	 * <code>false</code>, but also sets a style bit for a
	 * SWT.RESIZE border, the style bit will be honored.
	 *
	 * @return a boolean indicating whether the dialog is
	 * resizable and should have the default style bits for
	 * resizable dialogs
	 *
	 * @since 3.4
	 */
	protected boolean isResizable() {
		return false;
	}
}
