/******************************************************************************* * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * emil.crumhorn@gmail.com - initial API and implementation * ziogianni@gmail.com - Bug 461333 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=461333 *******************************************************************************/ package org.eclipse.nebula.widgets.ganttchart; import java.util.StringTokenizer; import org.eclipse.nebula.widgets.ganttchart.utils.TextPainterHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Font; 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.graphics.Region; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Monitor; import org.eclipse.swt.widgets.Shell; public class AdvancedTooltipDialog { private static Shell _shell; public static void makeDialog(final AdvancedTooltip toolTip, final IColorManager colorManager, final Point location) { makeDialog(toolTip, colorManager, location, null, null, null); } public static void makeDialog(final AdvancedTooltip toolTip, final IColorManager colorManager, final Point location, final String titleOverride, final String contentOverride, final String helpOverride) { if (_shell != null && !_shell.isDisposed()) { _shell.dispose(); } _shell = new Shell(Display.getDefault().getActiveShell(), SWT.ON_TOP | SWT.TOOL | SWT.NO_TRIM | SWT.NO_FOCUS); _shell.setLayout(new FillLayout()); final Composite comp = new Composite(_shell, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED | SWT.NO_FOCUS); comp.addListener(SWT.MouseMove, new Listener() { public void handleEvent(final Event event) { kill(); } }); comp.addPaintListener(new PaintListener() { public void paintControl(final PaintEvent e) { final Region region = new Region(_shell.getDisplay()); final GC gc = e.gc; final Rectangle bounds = comp.getBounds(); // draw borders drawBorders(gc, colorManager, bounds); // this is the margins for all content int marginTop = 8; int marginLeft = 6; int marginRight = 6; int marginBottom = 12; int x = marginLeft; int y = marginTop; int xMax = 0; int yMax = 0; // == TITLE == // title is bold Font bold = null; final Font old = gc.getFont(); bold = Utils.applyBoldFont(old); String title = toolTip.getTitle(); if (titleOverride != null) { title = titleOverride; } if (title != null && title.length() > 0) { gc.setForeground(colorManager.getAdvancedTooltipTextColor()); gc.setFont(bold); final Point point = gc.stringExtent(title); TextPainterHelper.drawText(gc, title, x, y); // gc.drawString(title, x, y, true); gc.setFont(old); y += point.y; xMax = Math.max(xMax, x + point.x); yMax = Math.max(yMax, y); } final Image bigImage = toolTip.getImage(); int imageY = y; if (bigImage != null) { // draw the image, as well as tell the normal text where it // will have to go depending on image size // space it first, regardless of size x += 9; final Rectangle imBounds = bigImage.getBounds(); // we push it down a bit, but these don't add to the overall // y position as the image // is (somewhat) horizontally aligned with the content text gc.drawImage(bigImage, x, y + 12); x += imBounds.width; imageY += imBounds.height + 12; } // == DRAW TEXT == int textY = y; String content = toolTip.getContent(); if (contentOverride != null) { content = contentOverride; } if (content != null && content.length() > 0) { // if we had an image, space out this text, otherwise a // little less if (bigImage == null) { x += 8; } else { x += 13; } // first we space it vertically textY += 13; final StringTokenizer tokenizer = new StringTokenizer(content, "\n"); //$NON-NLS-1$ int widestLine = 0; while (tokenizer.hasMoreTokens()) { final String token = tokenizer.nextToken(); final Point extent = TextPainterHelper.drawText(gc, token, x, textY); textY += extent.y; widestLine = Math.max(widestLine, extent.x); } x += widestLine; } // now add the image height to Y unless the text Y was bigger y = Math.max(textY, imageY); xMax = Math.max(xMax, x); yMax = Math.max(yMax, y); if (toolTip.getHelpImage() != null || toolTip.getHelpText() != null) { y += 8; // draw divider gc.setForeground(colorManager.getAdvancedTooltipDividerColor()); gc.drawLine(marginLeft, y, marginLeft + xMax, y); y++; gc.setForeground(colorManager.getAdvancedTooltipDividerShadowColor()); gc.drawLine(marginLeft, y, marginLeft + xMax, y); y += 7; gc.setForeground(colorManager.getAdvancedTooltipTextColor()); gc.setFont(bold); int curX = marginLeft; int widthUsed = 0; if (toolTip.getHelpImage() != null) { gc.drawImage(toolTip.getHelpImage(), marginLeft, y); curX += toolTip.getHelpImage().getBounds().width; curX += 9; widthUsed += curX - marginLeft; } if (toolTip.getHelpText() != null) { final Point helpSize = gc.stringExtent(toolTip.getHelpText()); gc.drawString(toolTip.getHelpText(), curX, y, true); widthUsed += helpSize.x; } xMax = Math.max(xMax, widthUsed); yMax = Math.max(yMax, y); } xMax += marginLeft + marginRight; yMax += marginTop + marginBottom; region.add(0, 0, xMax, yMax); region.subtract(0, 0, 1, 1); region.subtract(xMax - 1, yMax - 1, 1, 1); region.subtract(0, yMax - 1, 1, 1); region.subtract(xMax - 1, 0, 1, 1); // bug fix #240164 - Macs redraw when you set a region, guess OS X will just have // square shells instead, no big deal if (GanttComposite._osType != Constants.OS_MAC) { _shell.setRegion(region); } final Rectangle size = region.getBounds(); _shell.setSize(size.width, size.height); if (bold != null) { bold.dispose(); } Monitor active = null; try { active = Display.getDefault().getActiveShell().getMonitor(); } catch (Exception err) { active = Display.getDefault().getPrimaryMonitor(); } int totalXBounds = 0; final Monitor [] all = Display.getDefault().getMonitors(); for (int i = 0; i < all.length; i++) { if (all[i] == active) { break; } totalXBounds += all[i].getBounds().width; } final Rectangle maxBounds = active.getBounds(); final int shellHeight = _shell.getSize().y; final int shellWidth = _shell.getSize().x; final Point location = _shell.getLocation(); if ((location.y + shellHeight) > maxBounds.height) { location.y = maxBounds.height-shellHeight; } if ((location.x + shellWidth) > totalXBounds) { location.x = totalXBounds - shellWidth; } _shell.setLocation(location); } }); _shell.pack(); _shell.setLocation(location); _shell.setVisible(true); // bug fix #240164 - for some reason the bounds fetched at the beginning are off on Macs, // it seems it calls the redraw much sooner than on windows (before creating the shell, which rather // makes sense, but it's not what we had planned) // which causes things to become only drawn in a corner of the shell, thus, we force a redraw // after displaying the shell, which fixes the issue as it now can fetch the right bounds if (GanttComposite._osType == Constants.OS_MAC) { Display.getDefault().asyncExec(new Runnable() { public void run() { if (_shell != null && !_shell.isDisposed()) { _shell.redraw(); } } }); } } private static void drawBorders(final GC gc, final IColorManager colorManager, final Rectangle bounds) { gc.setForeground(colorManager.getAdvancedTooltipInnerFillTopColor()); gc.setBackground(colorManager.getAdvancedTooltipInnerFillBottomColor()); gc.fillGradientRectangle(bounds.x, bounds.y, bounds.width, bounds.height, true); // draw border gc.setForeground(colorManager.getAdvancedTooltipBorderColor()); gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); // what would would the world be without faded gradient corners? boring! // so let's draw a few. // draw corners gc.setForeground(colorManager.getAdvancedTooltipShadowCornerOuterColor()); // top left gc.drawLine(bounds.x + 1, bounds.y, bounds.x + 1, bounds.y); gc.drawLine(bounds.x, bounds.y + 1, bounds.x, bounds.y + 1); // top right gc.drawLine(bounds.x + bounds.width - 2, bounds.y, bounds.x + bounds.width - 2, bounds.y); gc.drawLine(bounds.x + bounds.width - 1, bounds.y + 1, bounds.x + bounds.width - 1, bounds.y + 1); // bottom right gc.drawLine(bounds.x + bounds.width - 1, bounds.y + bounds.height - 2, bounds.x + bounds.width - 1, bounds.y + bounds.height - 2); gc.drawLine(bounds.x + bounds.width - 2, bounds.y + bounds.height - 1, bounds.x + bounds.width - 2, bounds.y + bounds.height - 1); // bottom left gc.drawLine(bounds.x + 1, bounds.y + bounds.height - 1, bounds.x + 1, bounds.y + bounds.height - 1); gc.drawLine(bounds.x, bounds.y + bounds.height - 2, bounds.x, bounds.y + bounds.height - 2); // shadowed corner inside the above gc.setForeground(colorManager.getAdvancedTooltipShadowCornerInnerColor()); // top left gc.drawLine(bounds.x + 2, bounds.y, bounds.x + 2, bounds.y); gc.drawLine(bounds.x, bounds.y + 2, bounds.x, bounds.y + 2); // top right gc.drawLine(bounds.x + bounds.width - 3, bounds.y, bounds.x + bounds.width - 3, bounds.y); gc.drawLine(bounds.x + bounds.width - 1, bounds.y + 2, bounds.x + bounds.width - 1, bounds.y + 2); // bottom right gc.drawLine(bounds.x + bounds.width - 1, bounds.y + bounds.height - 3, bounds.x + bounds.width - 1, bounds.y + bounds.height - 3); gc.drawLine(bounds.x + bounds.width - 3, bounds.y + bounds.height - 1, bounds.x + bounds.width - 3, bounds.y + bounds.height - 1); // bottom left gc.drawLine(bounds.x + 2, bounds.y + bounds.height - 1, bounds.x + 2, bounds.y + bounds.height - 1); gc.drawLine(bounds.x, bounds.y + bounds.height - 3, bounds.x, bounds.y + bounds.height - 3); // draw inner corner pixel in each corner gc.setForeground(colorManager.getAdvancedTooltipShadowInnerCornerColor()); // top left gc.drawLine(bounds.x + 1, bounds.y + 1, bounds.x + 1, bounds.y + 1); // top right gc.drawLine(bounds.x + bounds.width - 2, bounds.y + 1, bounds.x + bounds.width - 2, bounds.y + 1); // bottom right gc.drawLine(bounds.x + bounds.width - 2, bounds.y + bounds.height - 2, bounds.x + bounds.width - 2, bounds.y + bounds.height - 2); // bottom left gc.drawLine(bounds.x + 1, bounds.y + bounds.height - 2, bounds.x + 1, bounds.y + bounds.height - 2); } public static void kill() { if (_shell != null && !_shell.isDisposed()) { _shell.dispose(); } } public static boolean isActive() { return (_shell != null && !_shell.isDisposed()); } }