/*******************************************************************************
 *  Copyright (c) 2000, 2016 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
 *     Ralf M Petter <ralf.petter@gmail.com> - Bug 509719
 *******************************************************************************/
package org.eclipse.ui.internal.forms.widgets;

import java.util.Hashtable;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.e4.ui.css.swt.dom.WidgetElement;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTargetListener;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.forms.IFormColors;
import org.eclipse.ui.forms.IMessage;
import org.eclipse.ui.forms.events.IHyperlinkListener;
import org.eclipse.ui.forms.widgets.Hyperlink;
import org.eclipse.ui.forms.widgets.ILayoutExtension;
import org.eclipse.ui.forms.widgets.SizeCache;
import org.eclipse.ui.internal.forms.IMessageToolTipManager;
import org.eclipse.ui.internal.forms.MessageManager;

/**
 * Form header moved out of the form class.
 */
public class FormHeading extends Canvas {
	private static final int TITLE_HMARGIN = 1;
	private static final int SPACING = 5;
	private static final int VSPACING = 5;
	private static final int HMARGIN = 6;
	private static final int VMARGIN = 1;
	private static final int CLIENT_MARGIN = 1;

	private static final int SEPARATOR = 1 << 1;
	private static final int BOTTOM_TOOLBAR = 1 << 2;
	private static final int BACKGROUND_IMAGE_TILED = 1 << 3;
	private static final int SEPARATOR_HEIGHT = 2;
	private static final int MESSAGE_AREA_LIMIT = 50;
	static IMessage[] NULL_MESSAGE_ARRAY = new IMessage[] {};

	public static final String COLOR_BASE_BG = "baseBg"; //$NON-NLS-1$

	private Image backgroundImage;

	private Image gradientImage;

	Hashtable<String, Color> colors = new Hashtable<>();

	private int flags;

	private GradientInfo gradientInfo;

	private ToolBarManager toolBarManager;

	private SizeCache toolbarCache = new SizeCache();

	private SizeCache clientCache = new SizeCache();

	private SizeCache messageCache = new SizeCache();

	private SizeCache titleRegionCache = new SizeCache();

	private TitleRegion titleRegion;

	private MessageRegion messageRegion;

	private IMessageToolTipManager messageToolTipManager = new DefaultMessageToolTipManager();

	private Control headClient;

	private class DefaultMessageToolTipManager implements
			IMessageToolTipManager {
		@Override
		public void createToolTip(Control control, boolean imageLabel) {
		}

		@Override
		public void update() {
			String details = getMessageType() == 0 ? null : MessageManager
					.createDetails(getChildrenMessages());
			if (messageRegion != null)
				messageRegion.updateToolTip(details);
			if (getMessageType() > 0
					&& (details == null || details.isEmpty()))
				details = getMessage();
			titleRegion.updateToolTip(details);
		}
	}

	private static class GradientInfo {
		Color[] gradientColors;

		int[] percents;

		boolean vertical;
	}

	private class FormHeadingLayout extends Layout implements ILayoutExtension {
		private static final int MIN_WIDTH = -2;

		@Override
		public int computeMinimumWidth(Composite composite, boolean flushCache) {
			return layout(composite, false, 0, 0, MIN_WIDTH, SWT.DEFAULT, flushCache).x;
		}

		@Override
		public int computeMaximumWidth(Composite composite, boolean flushCache) {
			return computeSize(composite, SWT.DEFAULT, SWT.DEFAULT, flushCache).x;
		}

		@Override
		public Point computeSize(Composite composite, int wHint, int hHint,
				boolean flushCache) {
			return layout(composite, false, 0, 0, wHint, hHint, flushCache);
		}

		@Override
		protected void layout(Composite composite, boolean flushCache) {
			Rectangle rect = composite.getClientArea();
			layout(composite, true, rect.x, rect.y, rect.width, rect.height,
					flushCache);
		}

		private Point layout(Composite composite, boolean move, int x, int y,
				int width, int height, boolean flushCache) {
			titleRegionCache.setControl(titleRegion);

			Point tsize = null;
			Point msize = null;
			Point tbsize = null;
			Point clsize = null;

			if (flushCache) {
				clientCache.flush();
				messageCache.flush();
				toolbarCache.flush();
				titleRegionCache.flush();
			}
			if (hasToolBar()) {
				ToolBar tb = toolBarManager.getControl();
				toolbarCache.setControl(tb);
				tbsize = toolbarCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
			}
			if (headClient != null) {
				clientCache.setControl(headClient);
				int clientWidthHint = width;
				if (clientWidthHint != SWT.DEFAULT && clientWidthHint != MIN_WIDTH) {
					clientWidthHint -= HMARGIN * 2;
					if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
						clientWidthHint -= tbsize.x + SPACING;
				}
				clsize = computeSize(clientCache, clientWidthHint);
			}
			int totalFlexWidth = width;
			int flexWidth = totalFlexWidth;
			if (totalFlexWidth != SWT.DEFAULT && totalFlexWidth != MIN_WIDTH) {
				totalFlexWidth -= TITLE_HMARGIN * 2;
				// complete right margin
				if (hasToolBar() && getToolBarAlignment() == SWT.TOP
						|| hasMessageRegion())
					totalFlexWidth -= SPACING;
				// subtract tool bar
				if (hasToolBar() && getToolBarAlignment() == SWT.TOP)
					totalFlexWidth -= tbsize.x + SPACING;
				flexWidth = totalFlexWidth;
				if (hasMessageRegion()) {
					// remove message region spacing and divide by 2
					flexWidth -= SPACING;
					// flexWidth /= 2;
				}
			}
			/*
			 * // compute text and message sizes tsize =
			 * titleRegion.computeSize(flexWidth, SWT.DEFAULT); if (flexWidth !=
			 * SWT.DEFAULT && tsize.x < flexWidth) flexWidth += flexWidth -
			 * tsize.x;
			 *
			 * if (hasMessageRegion()) {
			 * messageCache.setControl(messageRegion.getMessageControl()); msize =
			 * messageCache.computeSize(flexWidth, SWT.DEFAULT); int maxWidth =
			 * messageCache.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; if
			 * (maxWidth < msize.x) { msize.x = maxWidth; // recompute title
			 * with the reclaimed width int tflexWidth = totalFlexWidth -
			 * SPACING - msize.x; tsize = titleRegion.computeSize(tflexWidth,
			 * SWT.DEFAULT); } }
			 */
			if (!hasMessageRegion()) {
				tsize = computeSize(titleRegionCache, flexWidth);
			} else {
				// Total flexible area in the first row is flexWidth.
				// Try natural widths of title and
				Point tsizeNatural = titleRegionCache.computeSize(SWT.DEFAULT,
						SWT.DEFAULT);
				messageCache.setControl(messageRegion.getMessageControl());
				Point msizeNatural = messageCache.computeSize(SWT.DEFAULT,
						SWT.DEFAULT);
				// try to fit all
				tsize = tsizeNatural;
				msize = msizeNatural;
				if (flexWidth != SWT.DEFAULT && flexWidth != MIN_WIDTH) {
					int needed = tsizeNatural.x + msizeNatural.x;
					if (needed > flexWidth) {
						// too big - try to limit the message
						int mwidth = flexWidth - tsizeNatural.x;
						if (mwidth >= MESSAGE_AREA_LIMIT) {
							msize.x = mwidth;
						} else {
							// message is squeezed to the limit
							int flex = flexWidth - MESSAGE_AREA_LIMIT;
							tsize = titleRegion.computeSize(flex, SWT.DEFAULT);
							msize.x = MESSAGE_AREA_LIMIT;
						}
					}
				}
			}

			Point size = new Point(width, height);
			if (!move) {
				// compute sizes
				int width1 = 2 * TITLE_HMARGIN;
				width1 += tsize.x;
				if (msize != null)
					width1 += SPACING + msize.x;
				if (tbsize != null && getToolBarAlignment() == SWT.TOP)
					width1 += SPACING + tbsize.x;
				if (msize != null
						|| (tbsize != null && getToolBarAlignment() == SWT.TOP))
					width1 += SPACING;
				size.x = width1;
				if (clsize != null) {
					int width2 = clsize.x;
					if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
						width2 += SPACING + tbsize.x;
					width2 += 2 * HMARGIN;
					size.x = Math.max(width1, width2);
				}
				// height, first row
				size.y = tsize.y;
				if (msize != null)
					size.y = Math.max(msize.y, size.y);
				if (tbsize != null && getToolBarAlignment() == SWT.TOP)
					size.y = Math.max(tbsize.y, size.y);
				if (size.y > 0)
					size.y += VMARGIN * 2;
				// add second row
				int height2 = 0;
				if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
					height2 = tbsize.y;
				if (clsize != null)
					height2 = Math.max(height2, clsize.y);
				if (height2 > 0)
					size.y += VSPACING + height2 + CLIENT_MARGIN;
				// add separator
				if (size.y > 0 && isSeparatorVisible())
					size.y += SEPARATOR_HEIGHT;
			} else {
				// position controls
				int xloc = x;
				int yloc = y + VMARGIN;
				int row1Height = tsize.y;
				if (hasMessageRegion())
					row1Height = Math.max(row1Height, msize.y);
				if (hasToolBar() && getToolBarAlignment() == SWT.TOP)
					row1Height = Math.max(row1Height, tbsize.y);
				titleRegion.setBounds(xloc,
				// yloc + row1Height / 2 - tsize.y / 2,
						yloc, tsize.x, tsize.y);
				xloc += tsize.x;

				if (hasMessageRegion()) {
					xloc += SPACING;
					int messageOffset = 0;
					if (tsize.y > 0) {
						// space between title area and title text
						int titleLeadingSpace = (tsize.y - titleRegion.getFontHeight()) / 2;
						// space between message control and message text
						int messageLeadingSpace = (msize.y - messageRegion.getFontHeight()) / 2;
						// how much to offset the message so baselines align
						messageOffset = (titleLeadingSpace + titleRegion.getFontBaselineHeight())
							- (messageLeadingSpace + messageRegion.getFontBaselineHeight());
					}

					messageRegion
							.getMessageControl()
							.setBounds(
									xloc,
									tsize.y > 0 ? (yloc + messageOffset)
											: (yloc + row1Height / 2 - msize.y / 2),
									msize.x, msize.y);
					xloc += msize.x;
				}
				if (toolBarManager != null)
					toolBarManager.getControl().setVisible(
							!toolBarManager.isEmpty());
				if (tbsize != null && getToolBarAlignment() == SWT.TOP) {
					ToolBar tbar = toolBarManager.getControl();
					tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc
							+ row1Height - 1 - tbsize.y, tbsize.x, tbsize.y);
				}
				// second row
				xloc = HMARGIN;
				yloc += row1Height + VSPACING;
				int tw = 0;

				if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM) {
					ToolBar tbar = toolBarManager.getControl();
					tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc,
							tbsize.x, tbsize.y);
					tw = tbsize.x + SPACING;
				}
				if (headClient != null) {
					int carea = width - HMARGIN * 2 - tw;
					headClient.setBounds(xloc, yloc, carea, clsize.y);
				}
			}
			return size;
		}

		/**
		 * Computes the preferred or minimum size of the given client cache.
		 *
		 * @param clientCache
		 *            size cache for the control whose size is being computed
		 * @param wHint
		 *            the width of the control, in pixels, or SWT.DEFAULT if the
		 *            preferred size is being computed, or MIN_WIDTH if the minimum size
		 *            is being computed
		 */
		private Point computeSize(SizeCache clientCache, int wHint) {
			if (wHint == MIN_WIDTH) {
				int minWidth = clientCache.computeMinimumWidth();
				return clientCache.computeSize(minWidth, SWT.DEFAULT);
			}
			return clientCache.computeSize(wHint, SWT.DEFAULT);
		}
	}

	@Override
	public boolean forceFocus() {
		return false;
	}

	private boolean hasToolBar() {
		return toolBarManager != null && !toolBarManager.isEmpty();
	}

	private boolean hasMessageRegion() {
		return messageRegion != null && !messageRegion.isEmpty();
	}

	private class MessageRegion {
		private String message;
		private int messageType;
		private CLabel messageLabel;
		private IMessage[] messages;
		private Hyperlink messageHyperlink;
		private ListenerList<IHyperlinkListener> listeners;
		private int fontHeight = -1;
		private int fontBaselineHeight = -1;

		public MessageRegion() {
		}

		public boolean isDisposed() {
			Control c = getMessageControl();
			return c != null && c.isDisposed();
		}

		public boolean isEmpty() {
			Control c = getMessageControl();
			if (c == null)
				return true;
			return !c.getVisible();
		}

		public int getFontHeight() {
			if (fontHeight == -1) {
				Control c = getMessageControl();
				if (c == null)
					return 0;
				GC gc = new GC(c.getDisplay());
				gc.setFont(c.getFont());
				fontHeight = gc.getFontMetrics().getHeight();
				gc.dispose();
			}
			return fontHeight;
		}

		public int getFontBaselineHeight() {
			if (fontBaselineHeight == -1) {
				Control c = getMessageControl();
				if (c == null)
					return 0;
				GC gc = new GC(c.getDisplay());
				gc.setFont(c.getFont());
				FontMetrics fm = gc.getFontMetrics();
				fontBaselineHeight = fm.getHeight() - fm.getDescent();
				gc.dispose();
			}
			return fontBaselineHeight;
		}

		public void showMessage(String newMessage, int newType,
				IMessage[] messages) {
			Control oldControl = getMessageControl();
			int oldType = messageType;
			this.message = newMessage;
			this.messageType = newType;
			this.messages = messages;
			if (newMessage == null) {
				// clearing of the message
				if (oldControl != null && oldControl.getVisible())
					oldControl.setVisible(false);
				if (oldType != newType)
					updateForeground();
				return;
			}
			ensureControlExists();
			if (needHyperlink()) {
				messageHyperlink.setText(newMessage);
				messageHyperlink.setHref(messages);
			} else {
				messageLabel.setText(newMessage);
			}
			if (oldType != newType)
				updateForeground();
		}

		public void updateToolTip(String toolTip) {
			Control control = getMessageControl();
			if (control != null)
				control.setToolTipText(toolTip);
		}

		public String getMessage() {
			return message;
		}

		public int getMessageType() {
			return messageType;
		}

		public IMessage[] getChildrenMessages() {
			return messages;
		}

		public Control getMessageControl() {
			if (needHyperlink() && messageHyperlink != null)
				return messageHyperlink;
			return messageLabel;
		}

		public Image getMessageImage() {
			switch (messageType) {
			case IMessageProvider.INFORMATION:
				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO);
			case IMessageProvider.WARNING:
				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING);
			case IMessageProvider.ERROR:
				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
			default:
				return null;
			}
		}

		public void addMessageHyperlinkListener(IHyperlinkListener listener) {
			if (listeners == null)
				listeners = new ListenerList<>();
			listeners.add(listener);
			ensureControlExists();
			if (messageHyperlink != null)
				messageHyperlink.addHyperlinkListener(listener);
			if (listeners.size() == 1)
				updateForeground();
		}

		private void removeMessageHyperlinkListener(IHyperlinkListener listener) {
			if (listeners != null) {
				listeners.remove(listener);
				if (messageHyperlink != null)
					messageHyperlink.removeHyperlinkListener(listener);
				if (listeners.isEmpty())
					listeners = null;
				ensureControlExists();
				if (listeners == null && !isDisposed())
					updateForeground();
			}
		}

		private void ensureControlExists() {
			if (needHyperlink()) {
				if (messageLabel != null)
					messageLabel.setVisible(false);
				if (messageHyperlink == null) {
					messageHyperlink = new Hyperlink(FormHeading.this, SWT.NULL);
					messageHyperlink.setUnderlined(true);
					messageHyperlink.setBackground(getBackground());
					messageHyperlink.setText(message);
					messageHyperlink.setHref(messages);
					for (IHyperlinkListener element : listeners)
						messageHyperlink
								.addHyperlinkListener(element);
					if (messageToolTipManager != null)
						messageToolTipManager.createToolTip(messageHyperlink, false);
				} else if (!messageHyperlink.getVisible()) {
					messageHyperlink.setText(message);
					messageHyperlink.setHref(messages);
					messageHyperlink.setVisible(true);
				}
			} else {
				// need a label
				if (messageHyperlink != null)
					messageHyperlink.setVisible(false);
				if (messageLabel == null) {
					messageLabel = new CLabel(FormHeading.this, SWT.NULL);
					messageLabel.setBackground(getBackground());
					messageLabel.setText(message);
					if (messageToolTipManager != null)
						messageToolTipManager.createToolTip(messageLabel, false);
				} else if (!messageLabel.getVisible()) {
					messageLabel.setText(message);
					messageLabel.setVisible(true);
				}
			}
			updateForeground();
			layout(true);
		}

		private boolean needHyperlink() {
			return messageType > 0 && listeners != null;
		}

		public void setBackground(Color bg) {
			if (messageHyperlink != null)
				messageHyperlink.setBackground(bg);
			if (messageLabel != null)
				messageLabel.setBackground(bg);
		}

		public void setForeground(Color fg) {
			updateForeground();
		}

		private void updateForeground() {
			Color theFg;
			String cssClassName = null;
			ColorRegistry colorRegistry = JFaceResources.getColorRegistry();

			switch (messageType) {
			case IMessageProvider.ERROR:
				theFg = colorRegistry.get("org.eclipse.ui.workbench.FORM_HEADING_ERROR_COLOR"); //$NON-NLS-1$
				cssClassName = "MPartFormHeaderCLabelError"; //$NON-NLS-1$
				break;
			case IMessageProvider.WARNING:
				theFg = colorRegistry.get("org.eclipse.ui.workbench.FORM_HEADING_WARNING_COLOR"); //$NON-NLS-1$
				cssClassName = "MPartFormHeaderCLabelWarning"; //$NON-NLS-1$
				break;
			default:
				theFg = colorRegistry.get("org.eclipse.ui.workbench.FORM_HEADING_INFO_COLOR"); //$NON-NLS-1$
				cssClassName = "MPartFormHeaderCLabelInfo"; //$NON-NLS-1$
			}
			getMessageControl().setForeground(theFg);
			WidgetElement.setCSSClass(getMessageControl(), cssClassName);
		}
	}

	/**
	 * Creates the form content control as a child of the provided parent.
	 *
	 * @param parent
	 *            the parent widget
	 */
	public FormHeading(Composite parent, int style) {
		super(parent, style);
		setBackgroundMode(SWT.INHERIT_DEFAULT);
		addListener(SWT.Paint, e -> onPaint(e.gc));
		addListener(SWT.Dispose, e -> {
			if (gradientImage != null) {
				FormImages.getInstance().markFinished(gradientImage, getDisplay());
				gradientImage = null;
			}
		});
		addListener(SWT.Resize, e -> {
			if (gradientInfo != null || (backgroundImage != null && !isBackgroundImageTiled()))
				updateGradientImage();
		});
		addMouseMoveListener(this::updateTitleRegionHoverState);
		addMouseTrackListener(new MouseTrackListener() {
			@Override
			public void mouseEnter(MouseEvent e) {
				updateTitleRegionHoverState(e);
			}

			@Override
			public void mouseExit(MouseEvent e) {
				titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
			}

			@Override
			public void mouseHover(MouseEvent e) {
			}
		});
		super.setLayout(new FormHeadingLayout());
		titleRegion = new TitleRegion(this);
	}

	/**
	 * Fully delegates the size computation to the internal layout manager.
	 */
	@Override
	public final Point computeSize(int wHint, int hHint, boolean changed) {
		return ((FormHeadingLayout) getLayout()).computeSize(this, wHint,
				hHint, changed);
	}

	/**
	 * Prevents from changing the custom control layout.
	 */
	@Override
	public final void setLayout(Layout layout) {
	}

	/**
	 * Returns the title text that will be rendered at the top of the form.
	 *
	 * @return the title text
	 */
	public String getText() {
		return titleRegion.getText();
	}

	/**
	 * Returns the title image that will be rendered to the left of the title.
	 *
	 * @return the title image
	 * @since 3.2
	 */
	public Image getImage() {
		return titleRegion.getImage();
	}

	/**
	 * Sets the background color of the header.
	 */
	@Override
	public void setBackground(Color bg) {
		super.setBackground(bg);
		internalSetBackground(bg);
	}

	private void internalSetBackground(Color bg) {
		titleRegion.setBackground(bg);
		if (messageRegion != null)
			messageRegion.setBackground(bg);
		if (toolBarManager != null)
			toolBarManager.getControl().setBackground(bg);
		putColor(COLOR_BASE_BG, bg);
	}

	/**
	 * Sets the foreground color of the header.
	 */
	@Override
	public void setForeground(Color fg) {
		super.setForeground(fg);
		titleRegion.setForeground(fg);
		if (messageRegion != null)
			messageRegion.setForeground(fg);
	}

	/**
	 * Sets the text to be rendered at the top of the form above the body as a
	 * title.
	 *
	 * @param text
	 *            the title text
	 */
	public void setText(String text) {
		titleRegion.setText(text);
	}

	/**
	 * Sets whether ther text in the title region should be selectable.
	 * <p>
	 * Note: If {@link #addDragSupport(int, Transfer[], DragSourceListener) drag
	 * support} is also enabled, text selection has priority. Dragging still works
	 * in the non-text parts of the title area.
	 *
	 * @param selectable whether the title text should be selectable
	 */
	public void setTextSelectable(boolean selectable) {
		titleRegion.setTextSelectable(selectable);
	}

	@Override
	public void setFont(Font font) {
		super.setFont(font);
		titleRegion.setFont(font);
	}

	/**
	 * Sets the image to be rendered to the left of the title.
	 *
	 * @param image
	 *            the title image or <code>null</code> to show no image.
	 * @since 3.2
	 */
	public void setImage(Image image) {
		titleRegion.setImage(image);
		if (messageRegion != null)
			titleRegion.updateImage(messageRegion.getMessageImage(), true);
		else
			titleRegion.updateImage(null, true);
	}

	public void setTextBackground(Color[] gradientColors, int[] percents,
			boolean vertical) {
		if (gradientColors != null) {
			gradientInfo = new GradientInfo();
			gradientInfo.gradientColors = gradientColors;
			gradientInfo.percents = percents;
			gradientInfo.vertical = vertical;
			setBackground(null);
			updateGradientImage();
		} else {
			// reset
			gradientInfo = null;
			if (gradientImage != null) {
				FormImages.getInstance().markFinished(gradientImage, getDisplay());
				gradientImage = null;
				setBackgroundImage(null);
			}
		}
	}

	public void setHeadingBackgroundImage(Image image) {
		this.backgroundImage = image;
		if (image != null)
			setBackground(null);
		if (isBackgroundImageTiled()) {
			setBackgroundImage(image);
		} else
			updateGradientImage();
	}

	public Image getHeadingBackgroundImage() {
		return backgroundImage;
	}

	public void setBackgroundImageTiled(boolean tiled) {
		if (tiled)
			flags |= BACKGROUND_IMAGE_TILED;
		else
			flags &= ~BACKGROUND_IMAGE_TILED;
		setHeadingBackgroundImage(this.backgroundImage);
	}

	public boolean isBackgroundImageTiled() {
		return (flags & BACKGROUND_IMAGE_TILED) != 0;
	}

	@Override
	public void setBackgroundImage(Image image) {
		super.setBackgroundImage(image);
		if (image != null) {
			internalSetBackground(null);
		}
	}

	/**
	 * Returns the tool bar manager that is used to manage tool items in the
	 * form's title area.
	 *
	 * @return form tool bar manager
	 */
	public IToolBarManager getToolBarManager() {
		if (toolBarManager == null) {
			toolBarManager = new ToolBarManager(SWT.FLAT);
			ToolBar toolbar = toolBarManager.createControl(this);
			toolbar.setBackground(getBackground());
			toolbar.setForeground(getForeground());
			toolbar.setCursor(FormsResources.getHandCursor());
			addDisposeListener(e -> {
				if (toolBarManager != null) {
					toolBarManager.dispose();
					toolBarManager.removeAll();
					toolBarManager = null;
				}
			});
		}
		return toolBarManager;
	}

	/**
	 * Returns the menu manager that is used to manage tool items in the form's
	 * title area.
	 *
	 * @return form drop-down menu manager
	 * @since 3.3
	 */
	public IMenuManager getMenuManager() {
		return titleRegion.getMenuManager();
	}

	/**
	 * Updates the local tool bar manager if used. Does nothing if local tool
	 * bar manager has not been created yet.
	 */
	public void updateToolBar() {
		if (toolBarManager != null)
			toolBarManager.update(false);
	}

	private void onPaint(GC gc) {
		if (!isSeparatorVisible() && getBackgroundImage() == null)
			return;
		Rectangle carea = getClientArea();
		if (carea.width == 0 || carea.height == 0) {
			return;
		}
		Image buffer = new Image(getDisplay(), carea.width, carea.height);
		buffer.setBackground(getBackground());
		GC igc = new GC(buffer);
		igc.setBackground(getBackground());
		igc.fillRectangle(0, 0, carea.width, carea.height);
		if (getBackgroundImage() != null) {
			if (gradientInfo != null)
				drawBackground(igc, carea.x, carea.y, carea.width, carea.height);
			else {
				Image bgImage = getBackgroundImage();
				Rectangle ibounds = bgImage.getBounds();
				drawBackground(igc, carea.x, carea.y, ibounds.width,
						ibounds.height);
			}
		}

		if (isSeparatorVisible()) {
			// bg separator
			if (hasColor(IFormColors.H_BOTTOM_KEYLINE1))
				igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE1));
			else
				igc.setForeground(getBackground());
			igc.drawLine(carea.x, carea.height - 2, carea.x + carea.width - 1,
					carea.height - 2);
			if (hasColor(IFormColors.H_BOTTOM_KEYLINE2))
				igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE2));
			else
				igc.setForeground(getForeground());
			igc.drawLine(carea.x, carea.height - 1, carea.x + carea.width - 1,
					carea.height - 1);
		}
		igc.dispose();
		gc.drawImage(buffer, carea.x, carea.y);
		buffer.dispose();
	}

	private void updateTitleRegionHoverState(MouseEvent e) {
		Rectangle titleRect = titleRegion.getBounds();
		titleRect.width += titleRect.x + 15;
		titleRect.height += titleRect.y + 15;
		titleRect.x = 0;
		titleRect.y = 0;
		if (titleRect.contains(e.x, e.y))
			titleRegion.setHoverState(TitleRegion.STATE_HOVER_LIGHT);
		else
			titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
	}

	private void updateGradientImage() {
		Rectangle rect = getBounds();
		Image oldGradientImage = gradientImage;
		gradientImage = null;
		if (gradientInfo != null) {
			gradientImage = FormImages.getInstance().getGradient(gradientInfo.gradientColors, gradientInfo.percents,
					gradientInfo.vertical ? rect.height : rect.width, gradientInfo.vertical, getColor(COLOR_BASE_BG), getDisplay());
		} else if (backgroundImage != null && !isBackgroundImageTiled()) {
			gradientImage = new Image(getDisplay(), Math.max(rect.width, 1),
					Math.max(rect.height, 1));
			gradientImage.setBackground(getBackground());
			GC gc = new GC(gradientImage);
			gc.drawImage(backgroundImage, 0, 0);
			gc.dispose();
		}
		if (oldGradientImage != null) {
			FormImages.getInstance().markFinished(oldGradientImage, getDisplay());
		}
		setBackgroundImage(gradientImage);
	}

	public boolean isSeparatorVisible() {
		return (flags & SEPARATOR) != 0;
	}

	public void setSeparatorVisible(boolean addSeparator) {
		if (addSeparator)
			flags |= SEPARATOR;
		else
			flags &= ~SEPARATOR;
	}

	public void setToolBarAlignment(int alignment) {
		if (alignment == SWT.BOTTOM)
			flags |= BOTTOM_TOOLBAR;
		else
			flags &= ~BOTTOM_TOOLBAR;
	}

	public int getToolBarAlignment() {
		return (flags & BOTTOM_TOOLBAR) != 0 ? SWT.BOTTOM : SWT.TOP;
	}

	public void addMessageHyperlinkListener(IHyperlinkListener listener) {
		ensureMessageRegionExists();
		messageRegion.addMessageHyperlinkListener(listener);
	}

	public void removeMessageHyperlinkListener(IHyperlinkListener listener) {
		if (messageRegion != null)
			messageRegion.removeMessageHyperlinkListener(listener);
	}

	public String getMessage() {
		return messageRegion != null ? messageRegion.getMessage() : null;
	}

	public int getMessageType() {
		return messageRegion != null ? messageRegion.getMessageType() : 0;
	}

	public IMessage[] getChildrenMessages() {
		return messageRegion != null ? messageRegion.getChildrenMessages()
				: NULL_MESSAGE_ARRAY;
	}

	private void ensureMessageRegionExists() {
		// ensure message region exists
		if (messageRegion == null)
			messageRegion = new MessageRegion();
	}

	public void showMessage(String newMessage, int type, IMessage[] messages) {
		if (messageRegion == null) {
			// check the trivial case
			if (newMessage == null)
				return;
		} else if (messageRegion.isDisposed())
			return;
		ensureMessageRegionExists();
		messageRegion.showMessage(newMessage, type, messages);
		titleRegion.updateImage(messageRegion.getMessageImage(), false);
		if (messageToolTipManager != null)
			messageToolTipManager.update();
		layout();
		redraw();
	}

	/**
	 * Tests if the form is in the 'busy' state.
	 *
	 * @return <code>true</code> if busy, <code>false</code> otherwise.
	 */

	public boolean isBusy() {
		return titleRegion.isBusy();
	}

	/**
	 * Sets the form's busy state. Busy form will display 'busy' animation in
	 * the area of the title image.
	 *
	 * @param busy
	 *            the form's busy state
	 */

	public void setBusy(boolean busy) {
		if (titleRegion.setBusy(busy))
			layout();
	}

	public Control getHeadClient() {
		return headClient;
	}

	public void setHeadClient(Control headClient) {
		if (headClient != null)
			Assert.isTrue(headClient.getParent() == this);
		this.headClient = headClient;
		layout();
	}

	public void putColor(String key, Color color) {
		if (color == null)
			colors.remove(key);
		else
			colors.put(key, color);
	}

	public Color getColor(String key) {
		return colors.get(key);
	}

	public boolean hasColor(String key) {
		return colors.containsKey(key);
	}

	public void addDragSupport(int operations, Transfer[] transferTypes,
			DragSourceListener listener) {
		titleRegion.addDragSupport(operations, transferTypes, listener);
	}

	public void addDropSupport(int operations, Transfer[] transferTypes,
			DropTargetListener listener) {
		titleRegion.addDropSupport(operations, transferTypes, listener);
	}

	public IMessageToolTipManager getMessageToolTipManager() {
		return messageToolTipManager;
	}

	public void setMessageToolTipManager(
			IMessageToolTipManager messageToolTipManager) {
		this.messageToolTipManager = messageToolTipManager;
	}
}
