TooltipManager.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.ge.gef.ui.editor;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.osate.ge.internal.diagram.runtime.DiagramElement;
import org.osate.ge.internal.services.ExtensionRegistryService;
import org.osate.ge.ui.TooltipContributor;
import org.osate.ge.ui.TooltipContributorContext;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
/**
* Handles showing and hiding tooltips. Populates the tooltips using registered {@link TooltipContributor} instances.
*/
public class TooltipManager {
private final ExtensionRegistryService extensionRegistry;
private Shell tooltipShell = null;
private DiagramElement tooltipElement = null;
private final Timeline showTimeline;
// Implemented as list because enter and exit events may not be received in FILO order.
private final List<DiagramElement> elementStack = new ArrayList<>();
/**
* Creates a new instance
* @param extensionRegistry the registry to use to retrieve the {@link TooltipContributor} instances.
*/
public TooltipManager(final ExtensionRegistryService extensionRegistry) {
this.extensionRegistry = Objects.requireNonNull(extensionRegistry, "extensionRegistry must not be null");
// Create timeline used to delay showing the tooltip
this.showTimeline = new Timeline();
this.showTimeline.getKeyFrames().add(new KeyFrame(Duration.millis(1000)));
this.showTimeline.setOnFinished(event -> showTooltipElement());
}
/**
* Called when the mouse cursor enters a JavaFX node which is the root of the diagram element's scene graph branch.
* @param diagramElement the diagram element which is associated with the entered node.
*/
public void mouseEnter(final DiagramElement diagramElement) {
// Remove it from the stack if it is already contained in it.
elementStack.remove(diagramElement);
elementStack.add(diagramElement);
update();
}
/**
* Called when the mouse cursor exits a JavaFX node which is the root of the diagram element's scene graph branch.
* @param diagramElement the diagram element which is associated with the exited node.
*/
public void mouseExit(final DiagramElement diagramElement) {
if (elementStack.isEmpty()) {
return;
}
elementStack.remove(diagramElement);
update();
}
/**
* Updates the displayed tooltip based on the state of the diagram element stack.
* If a new diagram element is at the top of the stack, it hides the current tooltip and then starts the animation that when completed will show a tooltip.
*/
private void update() {
if (elementStack.isEmpty()) {
hideTooltip();
return;
}
final DiagramElement newTooltipElement = topDiagramElement();
if (tooltipElement != newTooltipElement) {
// Hide existing tooltip if open
if (tooltipElement != null) {
hideTooltip();
}
// Start the showing timeline
tooltipElement = newTooltipElement;
showTimeline.play();
}
}
private void showTooltipElement() {
if (tooltipElement == null || tooltipElement.getBusinessObject() == null) {
return;
}
// Create new tooltip shell
final Display display = Display.getCurrent();
tooltipShell = new Shell(display.getActiveShell(), SWT.ON_TOP | SWT.TOOL | SWT.CENTER);
tooltipShell.setVisible(false);
tooltipShell.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
tooltipShell.setBackgroundMode(SWT.INHERIT_FORCE);
// Configure layout
final GridLayout tooltipLayout = GridLayoutFactory.fillDefaults().create();
tooltipLayout.marginLeft = 5;
tooltipLayout.marginRight = 5;
tooltipShell.setLayout(tooltipLayout);
// Call tooltip contributors
final TooltipContributorContext tooltipContext = new TooltipContributorContext(tooltipShell, tooltipElement);
for (final TooltipContributor tooltipContributor : extensionRegistry.getTooltipContributors()) {
tooltipContributor.addTooltipContents(tooltipContext);
}
// Show tooltip shell if something was contributed
if (tooltipShell.getChildren().length > 0) {
// Attempt to restrict the width of widget which are wider than the preferred maximum.
for (final Control tooltipChild : tooltipShell.getChildren()) {
final int maxTooltipWidth = 600;
if (tooltipChild.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > maxTooltipWidth) {
tooltipChild
.setLayoutData(GridDataFactory.fillDefaults().hint(maxTooltipWidth, SWT.DEFAULT).create());
}
}
final Point point = display.getCursorLocation();
tooltipShell.setLocation(point.x, point.y + 20);
tooltipShell.pack(true);
tooltipShell.setVisible(true);
}
}
/**
* Hides the current tooltip. Cancels any activate show animation.
*/
public void hideTooltip() {
showTimeline.stop();
if (tooltipShell != null) {
tooltipShell.dispose();
}
tooltipShell = null;
tooltipElement = null;
}
private DiagramElement topDiagramElement() {
return elementStack.get(elementStack.size() - 1);
}
}