1 /*******************************************************************************
2  *  Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Ralf M Petter <ralf.petter@gmail.com> - Bug 509719
14  *******************************************************************************/
15 package org.eclipse.ui.internal.forms.widgets;
16 
17 import java.util.Hashtable;
18 
19 import org.eclipse.core.runtime.Assert;
20 import org.eclipse.core.runtime.ListenerList;
21 import org.eclipse.jface.action.IMenuManager;
22 import org.eclipse.jface.action.IToolBarManager;
23 import org.eclipse.jface.action.ToolBarManager;
24 import org.eclipse.jface.dialogs.Dialog;
25 import org.eclipse.jface.dialogs.IMessageProvider;
26 import org.eclipse.jface.resource.JFaceResources;
27 import org.eclipse.swt.SWT;
28 import org.eclipse.swt.custom.CLabel;
29 import org.eclipse.swt.dnd.DragSourceListener;
30 import org.eclipse.swt.dnd.DropTargetListener;
31 import org.eclipse.swt.dnd.Transfer;
32 import org.eclipse.swt.events.MouseEvent;
33 import org.eclipse.swt.events.MouseTrackListener;
34 import org.eclipse.swt.graphics.Color;
35 import org.eclipse.swt.graphics.Font;
36 import org.eclipse.swt.graphics.FontMetrics;
37 import org.eclipse.swt.graphics.GC;
38 import org.eclipse.swt.graphics.Image;
39 import org.eclipse.swt.graphics.Point;
40 import org.eclipse.swt.graphics.Rectangle;
41 import org.eclipse.swt.widgets.Canvas;
42 import org.eclipse.swt.widgets.Composite;
43 import org.eclipse.swt.widgets.Control;
44 import org.eclipse.swt.widgets.Layout;
45 import org.eclipse.swt.widgets.ToolBar;
46 import org.eclipse.ui.forms.IFormColors;
47 import org.eclipse.ui.forms.IMessage;
48 import org.eclipse.ui.forms.events.IHyperlinkListener;
49 import org.eclipse.ui.forms.widgets.Hyperlink;
50 import org.eclipse.ui.forms.widgets.ILayoutExtension;
51 import org.eclipse.ui.forms.widgets.SizeCache;
52 import org.eclipse.ui.internal.forms.IMessageToolTipManager;
53 import org.eclipse.ui.internal.forms.MessageManager;
54 
55 /**
56  * Form header moved out of the form class.
57  */
58 public class FormHeading extends Canvas {
59 	private static final int TITLE_HMARGIN = 1;
60 	private static final int SPACING = 5;
61 	private static final int VSPACING = 5;
62 	private static final int HMARGIN = 6;
63 	private static final int VMARGIN = 1;
64 	private static final int CLIENT_MARGIN = 1;
65 
66 	private static final int SEPARATOR = 1 << 1;
67 	private static final int BOTTOM_TOOLBAR = 1 << 2;
68 	private static final int BACKGROUND_IMAGE_TILED = 1 << 3;
69 	private static final int SEPARATOR_HEIGHT = 2;
70 	private static final int MESSAGE_AREA_LIMIT = 50;
71 	static IMessage[] NULL_MESSAGE_ARRAY = new IMessage[] {};
72 
73 	public static final String COLOR_BASE_BG = "baseBg"; //$NON-NLS-1$
74 
75 	private Image backgroundImage;
76 
77 	private Image gradientImage;
78 
79 	Hashtable<String, Color> colors = new Hashtable<>();
80 
81 	private int flags;
82 
83 	private GradientInfo gradientInfo;
84 
85 	private ToolBarManager toolBarManager;
86 
87 	private SizeCache toolbarCache = new SizeCache();
88 
89 	private SizeCache clientCache = new SizeCache();
90 
91 	private SizeCache messageCache = new SizeCache();
92 
93 	private SizeCache titleRegionCache = new SizeCache();
94 
95 	private TitleRegion titleRegion;
96 
97 	private MessageRegion messageRegion;
98 
99 	private IMessageToolTipManager messageToolTipManager = new DefaultMessageToolTipManager();
100 
101 	private Control headClient;
102 
103 	private class DefaultMessageToolTipManager implements
104 			IMessageToolTipManager {
105 		@Override
createToolTip(Control control, boolean imageLabel)106 		public void createToolTip(Control control, boolean imageLabel) {
107 		}
108 
109 		@Override
update()110 		public void update() {
111 			String details = getMessageType() == 0 ? null : MessageManager
112 					.createDetails(getChildrenMessages());
113 			if (messageRegion != null)
114 				messageRegion.updateToolTip(details);
115 			if (getMessageType() > 0
116 					&& (details == null || details.length() == 0))
117 				details = getMessage();
118 			titleRegion.updateToolTip(details);
119 		}
120 	}
121 
122 	private static class GradientInfo {
123 		Color[] gradientColors;
124 
125 		int[] percents;
126 
127 		boolean vertical;
128 	}
129 
130 	private class FormHeadingLayout extends Layout implements ILayoutExtension {
131 		private static final int MIN_WIDTH = -2;
132 
133 		@Override
computeMinimumWidth(Composite composite, boolean flushCache)134 		public int computeMinimumWidth(Composite composite, boolean flushCache) {
135 			return layout(composite, false, 0, 0, MIN_WIDTH, SWT.DEFAULT, flushCache).x;
136 		}
137 
138 		@Override
computeMaximumWidth(Composite composite, boolean flushCache)139 		public int computeMaximumWidth(Composite composite, boolean flushCache) {
140 			return computeSize(composite, SWT.DEFAULT, SWT.DEFAULT, flushCache).x;
141 		}
142 
143 		@Override
computeSize(Composite composite, int wHint, int hHint, boolean flushCache)144 		public Point computeSize(Composite composite, int wHint, int hHint,
145 				boolean flushCache) {
146 			return layout(composite, false, 0, 0, wHint, hHint, flushCache);
147 		}
148 
149 		@Override
layout(Composite composite, boolean flushCache)150 		protected void layout(Composite composite, boolean flushCache) {
151 			Rectangle rect = composite.getClientArea();
152 			layout(composite, true, rect.x, rect.y, rect.width, rect.height,
153 					flushCache);
154 		}
155 
layout(Composite composite, boolean move, int x, int y, int width, int height, boolean flushCache)156 		private Point layout(Composite composite, boolean move, int x, int y,
157 				int width, int height, boolean flushCache) {
158 			titleRegionCache.setControl(titleRegion);
159 
160 			Point tsize = null;
161 			Point msize = null;
162 			Point tbsize = null;
163 			Point clsize = null;
164 
165 			if (flushCache) {
166 				clientCache.flush();
167 				messageCache.flush();
168 				toolbarCache.flush();
169 				titleRegionCache.flush();
170 			}
171 			if (hasToolBar()) {
172 				ToolBar tb = toolBarManager.getControl();
173 				toolbarCache.setControl(tb);
174 				tbsize = toolbarCache.computeSize(SWT.DEFAULT, SWT.DEFAULT);
175 			}
176 			if (headClient != null) {
177 				clientCache.setControl(headClient);
178 				int clientWidthHint = width;
179 				if (clientWidthHint != SWT.DEFAULT && clientWidthHint != MIN_WIDTH) {
180 					clientWidthHint -= HMARGIN * 2;
181 					if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
182 						clientWidthHint -= tbsize.x + SPACING;
183 				}
184 				clsize = computeSize(clientCache, clientWidthHint);
185 			}
186 			int totalFlexWidth = width;
187 			int flexWidth = totalFlexWidth;
188 			if (totalFlexWidth != SWT.DEFAULT && totalFlexWidth != MIN_WIDTH) {
189 				totalFlexWidth -= TITLE_HMARGIN * 2;
190 				// complete right margin
191 				if (hasToolBar() && getToolBarAlignment() == SWT.TOP
192 						|| hasMessageRegion())
193 					totalFlexWidth -= SPACING;
194 				// subtract tool bar
195 				if (hasToolBar() && getToolBarAlignment() == SWT.TOP)
196 					totalFlexWidth -= tbsize.x + SPACING;
197 				flexWidth = totalFlexWidth;
198 				if (hasMessageRegion()) {
199 					// remove message region spacing and divide by 2
200 					flexWidth -= SPACING;
201 					// flexWidth /= 2;
202 				}
203 			}
204 			/*
205 			 * // compute text and message sizes tsize =
206 			 * titleRegion.computeSize(flexWidth, SWT.DEFAULT); if (flexWidth !=
207 			 * SWT.DEFAULT && tsize.x < flexWidth) flexWidth += flexWidth -
208 			 * tsize.x;
209 			 *
210 			 * if (hasMessageRegion()) {
211 			 * messageCache.setControl(messageRegion.getMessageControl()); msize =
212 			 * messageCache.computeSize(flexWidth, SWT.DEFAULT); int maxWidth =
213 			 * messageCache.computeSize(SWT.DEFAULT, SWT.DEFAULT).x; if
214 			 * (maxWidth < msize.x) { msize.x = maxWidth; // recompute title
215 			 * with the reclaimed width int tflexWidth = totalFlexWidth -
216 			 * SPACING - msize.x; tsize = titleRegion.computeSize(tflexWidth,
217 			 * SWT.DEFAULT); } }
218 			 */
219 			if (!hasMessageRegion()) {
220 				tsize = computeSize(titleRegionCache, flexWidth);
221 			} else {
222 				// Total flexible area in the first row is flexWidth.
223 				// Try natural widths of title and
224 				Point tsizeNatural = titleRegionCache.computeSize(SWT.DEFAULT,
225 						SWT.DEFAULT);
226 				messageCache.setControl(messageRegion.getMessageControl());
227 				Point msizeNatural = messageCache.computeSize(SWT.DEFAULT,
228 						SWT.DEFAULT);
229 				// try to fit all
230 				tsize = tsizeNatural;
231 				msize = msizeNatural;
232 				if (flexWidth != SWT.DEFAULT && flexWidth != MIN_WIDTH) {
233 					int needed = tsizeNatural.x + msizeNatural.x;
234 					if (needed > flexWidth) {
235 						// too big - try to limit the message
236 						int mwidth = flexWidth - tsizeNatural.x;
237 						if (mwidth >= MESSAGE_AREA_LIMIT) {
238 							msize.x = mwidth;
239 						} else {
240 							// message is squeezed to the limit
241 							int flex = flexWidth - MESSAGE_AREA_LIMIT;
242 							tsize = titleRegion.computeSize(flex, SWT.DEFAULT);
243 							msize.x = MESSAGE_AREA_LIMIT;
244 						}
245 					}
246 				}
247 			}
248 
249 			Point size = new Point(width, height);
250 			if (!move) {
251 				// compute sizes
252 				int width1 = 2 * TITLE_HMARGIN;
253 				width1 += tsize.x;
254 				if (msize != null)
255 					width1 += SPACING + msize.x;
256 				if (tbsize != null && getToolBarAlignment() == SWT.TOP)
257 					width1 += SPACING + tbsize.x;
258 				if (msize != null
259 						|| (tbsize != null && getToolBarAlignment() == SWT.TOP))
260 					width1 += SPACING;
261 				size.x = width1;
262 				if (clsize != null) {
263 					int width2 = clsize.x;
264 					if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
265 						width2 += SPACING + tbsize.x;
266 					width2 += 2 * HMARGIN;
267 					size.x = Math.max(width1, width2);
268 				}
269 				// height, first row
270 				size.y = tsize.y;
271 				if (msize != null)
272 					size.y = Math.max(msize.y, size.y);
273 				if (tbsize != null && getToolBarAlignment() == SWT.TOP)
274 					size.y = Math.max(tbsize.y, size.y);
275 				if (size.y > 0)
276 					size.y += VMARGIN * 2;
277 				// add second row
278 				int height2 = 0;
279 				if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM)
280 					height2 = tbsize.y;
281 				if (clsize != null)
282 					height2 = Math.max(height2, clsize.y);
283 				if (height2 > 0)
284 					size.y += VSPACING + height2 + CLIENT_MARGIN;
285 				// add separator
286 				if (size.y > 0 && isSeparatorVisible())
287 					size.y += SEPARATOR_HEIGHT;
288 			} else {
289 				// position controls
290 				int xloc = x;
291 				int yloc = y + VMARGIN;
292 				int row1Height = tsize.y;
293 				if (hasMessageRegion())
294 					row1Height = Math.max(row1Height, msize.y);
295 				if (hasToolBar() && getToolBarAlignment() == SWT.TOP)
296 					row1Height = Math.max(row1Height, tbsize.y);
297 				titleRegion.setBounds(xloc,
298 				// yloc + row1Height / 2 - tsize.y / 2,
299 						yloc, tsize.x, tsize.y);
300 				xloc += tsize.x;
301 
302 				if (hasMessageRegion()) {
303 					xloc += SPACING;
304 					int messageOffset = 0;
305 					if (tsize.y > 0) {
306 						// space between title area and title text
307 						int titleLeadingSpace = (tsize.y - titleRegion.getFontHeight()) / 2;
308 						// space between message control and message text
309 						int messageLeadingSpace = (msize.y - messageRegion.getFontHeight()) / 2;
310 						// how much to offset the message so baselines align
311 						messageOffset = (titleLeadingSpace + titleRegion.getFontBaselineHeight())
312 							- (messageLeadingSpace + messageRegion.getFontBaselineHeight());
313 					}
314 
315 					messageRegion
316 							.getMessageControl()
317 							.setBounds(
318 									xloc,
319 									tsize.y > 0 ? (yloc + messageOffset)
320 											: (yloc + row1Height / 2 - msize.y / 2),
321 									msize.x, msize.y);
322 					xloc += msize.x;
323 				}
324 				if (toolBarManager != null)
325 					toolBarManager.getControl().setVisible(
326 							!toolBarManager.isEmpty());
327 				if (tbsize != null && getToolBarAlignment() == SWT.TOP) {
328 					ToolBar tbar = toolBarManager.getControl();
329 					tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc
330 							+ row1Height - 1 - tbsize.y, tbsize.x, tbsize.y);
331 				}
332 				// second row
333 				xloc = HMARGIN;
334 				yloc += row1Height + VSPACING;
335 				int tw = 0;
336 
337 				if (tbsize != null && getToolBarAlignment() == SWT.BOTTOM) {
338 					ToolBar tbar = toolBarManager.getControl();
339 					tbar.setBounds(x + width - 1 - tbsize.x - HMARGIN, yloc,
340 							tbsize.x, tbsize.y);
341 					tw = tbsize.x + SPACING;
342 				}
343 				if (headClient != null) {
344 					int carea = width - HMARGIN * 2 - tw;
345 					headClient.setBounds(xloc, yloc, carea, clsize.y);
346 				}
347 			}
348 			return size;
349 		}
350 
351 		/**
352 		 * Computes the preferred or minimum size of the given client cache.
353 		 *
354 		 * @param clientCache
355 		 *            size cache for the control whose size is being computed
356 		 * @param wHint
357 		 *            the width of the control, in pixels, or SWT.DEFAULT if the
358 		 *            preferred size is being computed, or MIN_WIDTH if the minimum size
359 		 *            is being computed
360 		 */
computeSize(SizeCache clientCache, int wHint)361 		private Point computeSize(SizeCache clientCache, int wHint) {
362 			if (wHint == MIN_WIDTH) {
363 				int minWidth = clientCache.computeMinimumWidth();
364 				return clientCache.computeSize(minWidth, SWT.DEFAULT);
365 			}
366 			return clientCache.computeSize(wHint, SWT.DEFAULT);
367 		}
368 	}
369 
370 	@Override
forceFocus()371 	public boolean forceFocus() {
372 		return false;
373 	}
374 
hasToolBar()375 	private boolean hasToolBar() {
376 		return toolBarManager != null && !toolBarManager.isEmpty();
377 	}
378 
hasMessageRegion()379 	private boolean hasMessageRegion() {
380 		return messageRegion != null && !messageRegion.isEmpty();
381 	}
382 
383 	private class MessageRegion {
384 		private String message;
385 		private int messageType;
386 		private CLabel messageLabel;
387 		private IMessage[] messages;
388 		private Hyperlink messageHyperlink;
389 		private ListenerList<IHyperlinkListener> listeners;
390 		private Color fg;
391 		private int fontHeight = -1;
392 		private int fontBaselineHeight = -1;
393 
MessageRegion()394 		public MessageRegion() {
395 		}
396 
isDisposed()397 		public boolean isDisposed() {
398 			Control c = getMessageControl();
399 			return c != null && c.isDisposed();
400 		}
401 
isEmpty()402 		public boolean isEmpty() {
403 			Control c = getMessageControl();
404 			if (c == null)
405 				return true;
406 			return !c.getVisible();
407 		}
408 
getFontHeight()409 		public int getFontHeight() {
410 			if (fontHeight == -1) {
411 				Control c = getMessageControl();
412 				if (c == null)
413 					return 0;
414 				GC gc = new GC(c.getDisplay());
415 				gc.setFont(c.getFont());
416 				fontHeight = gc.getFontMetrics().getHeight();
417 				gc.dispose();
418 			}
419 			return fontHeight;
420 		}
421 
getFontBaselineHeight()422 		public int getFontBaselineHeight() {
423 			if (fontBaselineHeight == -1) {
424 				Control c = getMessageControl();
425 				if (c == null)
426 					return 0;
427 				GC gc = new GC(c.getDisplay());
428 				gc.setFont(c.getFont());
429 				FontMetrics fm = gc.getFontMetrics();
430 				fontBaselineHeight = fm.getHeight() - fm.getDescent();
431 				gc.dispose();
432 			}
433 			return fontBaselineHeight;
434 		}
435 
showMessage(String newMessage, int newType, IMessage[] messages)436 		public void showMessage(String newMessage, int newType,
437 				IMessage[] messages) {
438 			Control oldControl = getMessageControl();
439 			int oldType = messageType;
440 			this.message = newMessage;
441 			this.messageType = newType;
442 			this.messages = messages;
443 			if (newMessage == null) {
444 				// clearing of the message
445 				if (oldControl != null && oldControl.getVisible())
446 					oldControl.setVisible(false);
447 				if (oldType != newType)
448 					updateForeground();
449 				return;
450 			}
451 			ensureControlExists();
452 			if (needHyperlink()) {
453 				messageHyperlink.setText(newMessage);
454 				messageHyperlink.setHref(messages);
455 			} else {
456 				messageLabel.setText(newMessage);
457 			}
458 			if (oldType != newType)
459 				updateForeground();
460 		}
461 
updateToolTip(String toolTip)462 		public void updateToolTip(String toolTip) {
463 			Control control = getMessageControl();
464 			if (control != null)
465 				control.setToolTipText(toolTip);
466 		}
467 
getMessage()468 		public String getMessage() {
469 			return message;
470 		}
471 
getMessageType()472 		public int getMessageType() {
473 			return messageType;
474 		}
475 
getChildrenMessages()476 		public IMessage[] getChildrenMessages() {
477 			return messages;
478 		}
479 
getMessageControl()480 		public Control getMessageControl() {
481 			if (needHyperlink() && messageHyperlink != null)
482 				return messageHyperlink;
483 			return messageLabel;
484 		}
485 
getMessageImage()486 		public Image getMessageImage() {
487 			switch (messageType) {
488 			case IMessageProvider.INFORMATION:
489 				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_INFO);
490 			case IMessageProvider.WARNING:
491 				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING);
492 			case IMessageProvider.ERROR:
493 				return JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_ERROR);
494 			default:
495 				return null;
496 			}
497 		}
498 
addMessageHyperlinkListener(IHyperlinkListener listener)499 		public void addMessageHyperlinkListener(IHyperlinkListener listener) {
500 			if (listeners == null)
501 				listeners = new ListenerList<>();
502 			listeners.add(listener);
503 			ensureControlExists();
504 			if (messageHyperlink != null)
505 				messageHyperlink.addHyperlinkListener(listener);
506 			if (listeners.size() == 1)
507 				updateForeground();
508 		}
509 
removeMessageHyperlinkListener(IHyperlinkListener listener)510 		private void removeMessageHyperlinkListener(IHyperlinkListener listener) {
511 			if (listeners != null) {
512 				listeners.remove(listener);
513 				if (messageHyperlink != null)
514 					messageHyperlink.removeHyperlinkListener(listener);
515 				if (listeners.isEmpty())
516 					listeners = null;
517 				ensureControlExists();
518 				if (listeners == null && !isDisposed())
519 					updateForeground();
520 			}
521 		}
522 
ensureControlExists()523 		private void ensureControlExists() {
524 			if (needHyperlink()) {
525 				if (messageLabel != null)
526 					messageLabel.setVisible(false);
527 				if (messageHyperlink == null) {
528 					messageHyperlink = new Hyperlink(FormHeading.this, SWT.NULL);
529 					messageHyperlink.setUnderlined(true);
530 					messageHyperlink.setBackground(getBackground());
531 					messageHyperlink.setText(message);
532 					messageHyperlink.setHref(messages);
533 					for (IHyperlinkListener element : listeners)
534 						messageHyperlink
535 								.addHyperlinkListener(element);
536 					if (messageToolTipManager != null)
537 						messageToolTipManager.createToolTip(messageHyperlink, false);
538 				} else if (!messageHyperlink.getVisible()) {
539 					messageHyperlink.setText(message);
540 					messageHyperlink.setHref(messages);
541 					messageHyperlink.setVisible(true);
542 				}
543 			} else {
544 				// need a label
545 				if (messageHyperlink != null)
546 					messageHyperlink.setVisible(false);
547 				if (messageLabel == null) {
548 					messageLabel = new CLabel(FormHeading.this, SWT.NULL);
549 					messageLabel.setBackground(getBackground());
550 					messageLabel.setText(message);
551 					if (messageToolTipManager != null)
552 						messageToolTipManager.createToolTip(messageLabel, false);
553 				} else if (!messageLabel.getVisible()) {
554 					messageLabel.setText(message);
555 					messageLabel.setVisible(true);
556 				}
557 			}
558 			updateForeground();
559 			layout(true);
560 		}
561 
needHyperlink()562 		private boolean needHyperlink() {
563 			return messageType > 0 && listeners != null;
564 		}
565 
setBackground(Color bg)566 		public void setBackground(Color bg) {
567 			if (messageHyperlink != null)
568 				messageHyperlink.setBackground(bg);
569 			if (messageLabel != null)
570 				messageLabel.setBackground(bg);
571 		}
572 
setForeground(Color fg)573 		public void setForeground(Color fg) {
574 			this.fg = fg;
575 			updateForeground();
576 		}
577 
updateForeground()578 		private void updateForeground() {
579 			Color theFg;
580 
581 			switch (messageType) {
582 			case IMessageProvider.ERROR:
583 				theFg = getDisplay().getSystemColor(SWT.COLOR_RED);
584 				break;
585 			case IMessageProvider.WARNING:
586 				theFg = getDisplay().getSystemColor(SWT.COLOR_DARK_YELLOW);
587 				break;
588 			default:
589 				theFg = fg;
590 			}
591 			getMessageControl().setForeground(theFg);
592 		}
593 	}
594 
595 	/**
596 	 * Creates the form content control as a child of the provided parent.
597 	 *
598 	 * @param parent
599 	 *            the parent widget
600 	 */
FormHeading(Composite parent, int style)601 	public FormHeading(Composite parent, int style) {
602 		super(parent, style);
603 		setBackgroundMode(SWT.INHERIT_DEFAULT);
604 		addListener(SWT.Paint, e -> onPaint(e.gc));
605 		addListener(SWT.Dispose, e -> {
606 			if (gradientImage != null) {
607 				FormImages.getInstance().markFinished(gradientImage, getDisplay());
608 				gradientImage = null;
609 			}
610 		});
611 		addListener(SWT.Resize, e -> {
612 			if (gradientInfo != null || (backgroundImage != null && !isBackgroundImageTiled()))
613 				updateGradientImage();
614 		});
615 		addMouseMoveListener(e -> updateTitleRegionHoverState(e));
616 		addMouseTrackListener(new MouseTrackListener() {
617 			@Override
618 			public void mouseEnter(MouseEvent e) {
619 				updateTitleRegionHoverState(e);
620 			}
621 
622 			@Override
623 			public void mouseExit(MouseEvent e) {
624 				titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
625 			}
626 
627 			@Override
628 			public void mouseHover(MouseEvent e) {
629 			}
630 		});
631 		super.setLayout(new FormHeadingLayout());
632 		titleRegion = new TitleRegion(this);
633 	}
634 
635 	/**
636 	 * Fully delegates the size computation to the internal layout manager.
637 	 */
638 	@Override
computeSize(int wHint, int hHint, boolean changed)639 	public final Point computeSize(int wHint, int hHint, boolean changed) {
640 		return ((FormHeadingLayout) getLayout()).computeSize(this, wHint,
641 				hHint, changed);
642 	}
643 
644 	/**
645 	 * Prevents from changing the custom control layout.
646 	 */
647 	@Override
setLayout(Layout layout)648 	public final void setLayout(Layout layout) {
649 	}
650 
651 	/**
652 	 * Returns the title text that will be rendered at the top of the form.
653 	 *
654 	 * @return the title text
655 	 */
getText()656 	public String getText() {
657 		return titleRegion.getText();
658 	}
659 
660 	/**
661 	 * Returns the title image that will be rendered to the left of the title.
662 	 *
663 	 * @return the title image
664 	 * @since 3.2
665 	 */
getImage()666 	public Image getImage() {
667 		return titleRegion.getImage();
668 	}
669 
670 	/**
671 	 * Sets the background color of the header.
672 	 */
673 	@Override
setBackground(Color bg)674 	public void setBackground(Color bg) {
675 		super.setBackground(bg);
676 		internalSetBackground(bg);
677 	}
678 
internalSetBackground(Color bg)679 	private void internalSetBackground(Color bg) {
680 		titleRegion.setBackground(bg);
681 		if (messageRegion != null)
682 			messageRegion.setBackground(bg);
683 		if (toolBarManager != null)
684 			toolBarManager.getControl().setBackground(bg);
685 		putColor(COLOR_BASE_BG, bg);
686 	}
687 
688 	/**
689 	 * Sets the foreground color of the header.
690 	 */
691 	@Override
setForeground(Color fg)692 	public void setForeground(Color fg) {
693 		super.setForeground(fg);
694 		titleRegion.setForeground(fg);
695 		if (messageRegion != null)
696 			messageRegion.setForeground(fg);
697 	}
698 
699 	/**
700 	 * Sets the text to be rendered at the top of the form above the body as a
701 	 * title.
702 	 *
703 	 * @param text
704 	 *            the title text
705 	 */
setText(String text)706 	public void setText(String text) {
707 		titleRegion.setText(text);
708 	}
709 
710 	/**
711 	 * Sets whether ther text in the title region should be selectable.
712 	 * <p>
713 	 * Note: If {@link #addDragSupport(int, Transfer[], DragSourceListener) drag
714 	 * support} is also enabled, text selection has priority. Dragging still works
715 	 * in the non-text parts of the title area.
716 	 *
717 	 * @param selectable whether the title text should be selectable
718 	 */
setTextSelectable(boolean selectable)719 	public void setTextSelectable(boolean selectable) {
720 		titleRegion.setTextSelectable(selectable);
721 	}
722 
723 	@Override
setFont(Font font)724 	public void setFont(Font font) {
725 		super.setFont(font);
726 		titleRegion.setFont(font);
727 	}
728 
729 	/**
730 	 * Sets the image to be rendered to the left of the title.
731 	 *
732 	 * @param image
733 	 *            the title image or <code>null</code> to show no image.
734 	 * @since 3.2
735 	 */
setImage(Image image)736 	public void setImage(Image image) {
737 		titleRegion.setImage(image);
738 		if (messageRegion != null)
739 			titleRegion.updateImage(messageRegion.getMessageImage(), true);
740 		else
741 			titleRegion.updateImage(null, true);
742 	}
743 
setTextBackground(Color[] gradientColors, int[] percents, boolean vertical)744 	public void setTextBackground(Color[] gradientColors, int[] percents,
745 			boolean vertical) {
746 		if (gradientColors != null) {
747 			gradientInfo = new GradientInfo();
748 			gradientInfo.gradientColors = gradientColors;
749 			gradientInfo.percents = percents;
750 			gradientInfo.vertical = vertical;
751 			setBackground(null);
752 			updateGradientImage();
753 		} else {
754 			// reset
755 			gradientInfo = null;
756 			if (gradientImage != null) {
757 				FormImages.getInstance().markFinished(gradientImage, getDisplay());
758 				gradientImage = null;
759 				setBackgroundImage(null);
760 			}
761 		}
762 	}
763 
setHeadingBackgroundImage(Image image)764 	public void setHeadingBackgroundImage(Image image) {
765 		this.backgroundImage = image;
766 		if (image != null)
767 			setBackground(null);
768 		if (isBackgroundImageTiled()) {
769 			setBackgroundImage(image);
770 		} else
771 			updateGradientImage();
772 	}
773 
getHeadingBackgroundImage()774 	public Image getHeadingBackgroundImage() {
775 		return backgroundImage;
776 	}
777 
setBackgroundImageTiled(boolean tiled)778 	public void setBackgroundImageTiled(boolean tiled) {
779 		if (tiled)
780 			flags |= BACKGROUND_IMAGE_TILED;
781 		else
782 			flags &= ~BACKGROUND_IMAGE_TILED;
783 		setHeadingBackgroundImage(this.backgroundImage);
784 	}
785 
isBackgroundImageTiled()786 	public boolean isBackgroundImageTiled() {
787 		return (flags & BACKGROUND_IMAGE_TILED) != 0;
788 	}
789 
790 	@Override
setBackgroundImage(Image image)791 	public void setBackgroundImage(Image image) {
792 		super.setBackgroundImage(image);
793 		if (image != null) {
794 			internalSetBackground(null);
795 		}
796 	}
797 
798 	/**
799 	 * Returns the tool bar manager that is used to manage tool items in the
800 	 * form's title area.
801 	 *
802 	 * @return form tool bar manager
803 	 */
getToolBarManager()804 	public IToolBarManager getToolBarManager() {
805 		if (toolBarManager == null) {
806 			toolBarManager = new ToolBarManager(SWT.FLAT);
807 			ToolBar toolbar = toolBarManager.createControl(this);
808 			toolbar.setBackground(getBackground());
809 			toolbar.setForeground(getForeground());
810 			toolbar.setCursor(FormsResources.getHandCursor());
811 			addDisposeListener(e -> {
812 				if (toolBarManager != null) {
813 					toolBarManager.dispose();
814 					toolBarManager.removeAll();
815 					toolBarManager = null;
816 				}
817 			});
818 		}
819 		return toolBarManager;
820 	}
821 
822 	/**
823 	 * Returns the menu manager that is used to manage tool items in the form's
824 	 * title area.
825 	 *
826 	 * @return form drop-down menu manager
827 	 * @since 3.3
828 	 */
getMenuManager()829 	public IMenuManager getMenuManager() {
830 		return titleRegion.getMenuManager();
831 	}
832 
833 	/**
834 	 * Updates the local tool bar manager if used. Does nothing if local tool
835 	 * bar manager has not been created yet.
836 	 */
updateToolBar()837 	public void updateToolBar() {
838 		if (toolBarManager != null)
839 			toolBarManager.update(false);
840 	}
841 
onPaint(GC gc)842 	private void onPaint(GC gc) {
843 		if (!isSeparatorVisible() && getBackgroundImage() == null)
844 			return;
845 		Rectangle carea = getClientArea();
846 		if (carea.width == 0 || carea.height == 0) {
847 			return;
848 		}
849 		Image buffer = new Image(getDisplay(), carea.width, carea.height);
850 		buffer.setBackground(getBackground());
851 		GC igc = new GC(buffer);
852 		igc.setBackground(getBackground());
853 		igc.fillRectangle(0, 0, carea.width, carea.height);
854 		if (getBackgroundImage() != null) {
855 			if (gradientInfo != null)
856 				drawBackground(igc, carea.x, carea.y, carea.width, carea.height);
857 			else {
858 				Image bgImage = getBackgroundImage();
859 				Rectangle ibounds = bgImage.getBounds();
860 				drawBackground(igc, carea.x, carea.y, ibounds.width,
861 						ibounds.height);
862 			}
863 		}
864 
865 		if (isSeparatorVisible()) {
866 			// bg separator
867 			if (hasColor(IFormColors.H_BOTTOM_KEYLINE1))
868 				igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE1));
869 			else
870 				igc.setForeground(getBackground());
871 			igc.drawLine(carea.x, carea.height - 2, carea.x + carea.width - 1,
872 					carea.height - 2);
873 			if (hasColor(IFormColors.H_BOTTOM_KEYLINE2))
874 				igc.setForeground(getColor(IFormColors.H_BOTTOM_KEYLINE2));
875 			else
876 				igc.setForeground(getForeground());
877 			igc.drawLine(carea.x, carea.height - 1, carea.x + carea.width - 1,
878 					carea.height - 1);
879 		}
880 		igc.dispose();
881 		gc.drawImage(buffer, carea.x, carea.y);
882 		buffer.dispose();
883 	}
884 
updateTitleRegionHoverState(MouseEvent e)885 	private void updateTitleRegionHoverState(MouseEvent e) {
886 		Rectangle titleRect = titleRegion.getBounds();
887 		titleRect.width += titleRect.x + 15;
888 		titleRect.height += titleRect.y + 15;
889 		titleRect.x = 0;
890 		titleRect.y = 0;
891 		if (titleRect.contains(e.x, e.y))
892 			titleRegion.setHoverState(TitleRegion.STATE_HOVER_LIGHT);
893 		else
894 			titleRegion.setHoverState(TitleRegion.STATE_NORMAL);
895 	}
896 
updateGradientImage()897 	private void updateGradientImage() {
898 		Rectangle rect = getBounds();
899 		Image oldGradientImage = gradientImage;
900 		gradientImage = null;
901 		if (gradientInfo != null) {
902 			gradientImage = FormImages.getInstance().getGradient(gradientInfo.gradientColors, gradientInfo.percents,
903 					gradientInfo.vertical ? rect.height : rect.width, gradientInfo.vertical, getColor(COLOR_BASE_BG), getDisplay());
904 		} else if (backgroundImage != null && !isBackgroundImageTiled()) {
905 			gradientImage = new Image(getDisplay(), Math.max(rect.width, 1),
906 					Math.max(rect.height, 1));
907 			gradientImage.setBackground(getBackground());
908 			GC gc = new GC(gradientImage);
909 			gc.drawImage(backgroundImage, 0, 0);
910 			gc.dispose();
911 		}
912 		if (oldGradientImage != null) {
913 			FormImages.getInstance().markFinished(oldGradientImage, getDisplay());
914 		}
915 		setBackgroundImage(gradientImage);
916 	}
917 
isSeparatorVisible()918 	public boolean isSeparatorVisible() {
919 		return (flags & SEPARATOR) != 0;
920 	}
921 
setSeparatorVisible(boolean addSeparator)922 	public void setSeparatorVisible(boolean addSeparator) {
923 		if (addSeparator)
924 			flags |= SEPARATOR;
925 		else
926 			flags &= ~SEPARATOR;
927 	}
928 
setToolBarAlignment(int alignment)929 	public void setToolBarAlignment(int alignment) {
930 		if (alignment == SWT.BOTTOM)
931 			flags |= BOTTOM_TOOLBAR;
932 		else
933 			flags &= ~BOTTOM_TOOLBAR;
934 	}
935 
getToolBarAlignment()936 	public int getToolBarAlignment() {
937 		return (flags & BOTTOM_TOOLBAR) != 0 ? SWT.BOTTOM : SWT.TOP;
938 	}
939 
addMessageHyperlinkListener(IHyperlinkListener listener)940 	public void addMessageHyperlinkListener(IHyperlinkListener listener) {
941 		ensureMessageRegionExists();
942 		messageRegion.addMessageHyperlinkListener(listener);
943 	}
944 
removeMessageHyperlinkListener(IHyperlinkListener listener)945 	public void removeMessageHyperlinkListener(IHyperlinkListener listener) {
946 		if (messageRegion != null)
947 			messageRegion.removeMessageHyperlinkListener(listener);
948 	}
949 
getMessage()950 	public String getMessage() {
951 		return messageRegion != null ? messageRegion.getMessage() : null;
952 	}
953 
getMessageType()954 	public int getMessageType() {
955 		return messageRegion != null ? messageRegion.getMessageType() : 0;
956 	}
957 
getChildrenMessages()958 	public IMessage[] getChildrenMessages() {
959 		return messageRegion != null ? messageRegion.getChildrenMessages()
960 				: NULL_MESSAGE_ARRAY;
961 	}
962 
ensureMessageRegionExists()963 	private void ensureMessageRegionExists() {
964 		// ensure message region exists
965 		if (messageRegion == null)
966 			messageRegion = new MessageRegion();
967 	}
968 
showMessage(String newMessage, int type, IMessage[] messages)969 	public void showMessage(String newMessage, int type, IMessage[] messages) {
970 		if (messageRegion == null) {
971 			// check the trivial case
972 			if (newMessage == null)
973 				return;
974 		} else if (messageRegion.isDisposed())
975 			return;
976 		ensureMessageRegionExists();
977 		messageRegion.showMessage(newMessage, type, messages);
978 		titleRegion.updateImage(messageRegion.getMessageImage(), false);
979 		if (messageToolTipManager != null)
980 			messageToolTipManager.update();
981 		layout();
982 		redraw();
983 	}
984 
985 	/**
986 	 * Tests if the form is in the 'busy' state.
987 	 *
988 	 * @return <code>true</code> if busy, <code>false</code> otherwise.
989 	 */
990 
isBusy()991 	public boolean isBusy() {
992 		return titleRegion.isBusy();
993 	}
994 
995 	/**
996 	 * Sets the form's busy state. Busy form will display 'busy' animation in
997 	 * the area of the title image.
998 	 *
999 	 * @param busy
1000 	 *            the form's busy state
1001 	 */
1002 
setBusy(boolean busy)1003 	public void setBusy(boolean busy) {
1004 		if (titleRegion.setBusy(busy))
1005 			layout();
1006 	}
1007 
getHeadClient()1008 	public Control getHeadClient() {
1009 		return headClient;
1010 	}
1011 
setHeadClient(Control headClient)1012 	public void setHeadClient(Control headClient) {
1013 		if (headClient != null)
1014 			Assert.isTrue(headClient.getParent() == this);
1015 		this.headClient = headClient;
1016 		layout();
1017 	}
1018 
putColor(String key, Color color)1019 	public void putColor(String key, Color color) {
1020 		if (color == null)
1021 			colors.remove(key);
1022 		else
1023 			colors.put(key, color);
1024 	}
1025 
getColor(String key)1026 	public Color getColor(String key) {
1027 		return colors.get(key);
1028 	}
1029 
hasColor(String key)1030 	public boolean hasColor(String key) {
1031 		return colors.containsKey(key);
1032 	}
1033 
addDragSupport(int operations, Transfer[] transferTypes, DragSourceListener listener)1034 	public void addDragSupport(int operations, Transfer[] transferTypes,
1035 			DragSourceListener listener) {
1036 		titleRegion.addDragSupport(operations, transferTypes, listener);
1037 	}
1038 
addDropSupport(int operations, Transfer[] transferTypes, DropTargetListener listener)1039 	public void addDropSupport(int operations, Transfer[] transferTypes,
1040 			DropTargetListener listener) {
1041 		titleRegion.addDropSupport(operations, transferTypes, listener);
1042 	}
1043 
getMessageToolTipManager()1044 	public IMessageToolTipManager getMessageToolTipManager() {
1045 		return messageToolTipManager;
1046 	}
1047 
setMessageToolTipManager( IMessageToolTipManager messageToolTipManager)1048 	public void setMessageToolTipManager(
1049 			IMessageToolTipManager messageToolTipManager) {
1050 		this.messageToolTipManager = messageToolTipManager;
1051 	}
1052 }
1053