package com.insightfullogic.honest_profiler.ports.javafx.util; import static com.insightfullogic.honest_profiler.ports.javafx.util.DialogUtil.showExportDialog; import static com.insightfullogic.honest_profiler.ports.javafx.util.MenuUtil.addMenuItem; import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.CTXMENU_TREE_COLLAPSE; import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.CTXMENU_TREE_EXPANDFIRSTONLY; import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.CTXMENU_TREE_EXPANDFULLY; import static com.insightfullogic.honest_profiler.ports.javafx.util.ResourceUtil.CTXMENU_TREE_EXPORTSUBTREE; import static com.insightfullogic.honest_profiler.ports.javafx.util.TreeUtil.collapseFully; import static com.insightfullogic.honest_profiler.ports.javafx.util.TreeUtil.expandFirstOnly; import static com.insightfullogic.honest_profiler.ports.javafx.util.TreeUtil.expandFully; import static com.insightfullogic.honest_profiler.ports.javafx.util.report.ReportUtil.writeStack; import com.insightfullogic.honest_profiler.core.aggregation.result.straight.Node; import com.insightfullogic.honest_profiler.ports.javafx.model.ApplicationContext; import javafx.beans.binding.ObjectBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.Property; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.scene.control.ContextMenu; import javafx.scene.control.TreeCell; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeTableCell; import javafx.scene.control.TreeTableRow; import javafx.scene.control.TreeTableView; /** * Utility class for dynamically constructing and binding {@link ContextMenu}s. */ public final class ContextMenuUtil { // Class Methods /** * Binds a dynamically generated {@link ContextMenu} to a {@link TreeTableCell}. The menu provides expand and * collapse options for a tree node. * <p> * * @param <T> the type of the items in the containing {@link TreeTableView} * @param <U> the type of the item contained in the {@link TreeTableCell} * @param appCtx the {@link ApplicationContext} of the application * @param cell the {@link TreeTableCell} for which the {@link ContextMenu} should be displayed when it is * right-clicked */ public static <T, U> void bindContextMenuForTreeTableCell(ApplicationContext appCtx, TreeTableCell<T, ?> cell) { bindContextMenuForTreeTableCell( appCtx, cell.contextMenuProperty(), cell.tableRowProperty() ); } /** * Binds a dynamically generated {@link ContextMenu} to a {@link TreeCell}. The menu provides expand and collapse * options for a tree node. * <p> * * @param <T> the type of the item contained in the {@link TreeCell} * @param appCtx the {@link ApplicationContext} of the application * @param cell the {@link TreeCell} for which the {@link ContextMenu} should be displayed when it is right-clicked */ public static <T> void bindContextMenuForTreeCell(ApplicationContext appCtx, TreeCell<T> cell) { bindContextMenu( appCtx, cell.contextMenuProperty(), cell.treeItemProperty()); } /** * Helper method which provides extra logic for extracting the {@link TreeItem} {@link Property} from a * {@link TreeTableCell}, which itself has no {@link TreeItem} property. The {@link TreeItem} {@link Property} we * want to bind can be found in the containing {@link TreeTableRow} instead. * <p> * * @param <T> the type of the item contained in the {@link TreeTableRow} * @param appCtx the {@link ApplicationContext} of the application * @param ctxMenuProperty the {@link ContextMenu} {@link Property} of the {@link TreeTableCell} * @param tableRowProperty the {@link TreeTableRow} {@link Property} of the {@link TreeTableCell} */ private static <T> void bindContextMenuForTreeTableCell(ApplicationContext appCtx, ObjectProperty<ContextMenu> ctxMenuProperty, ReadOnlyObjectProperty<TreeTableRow<T>> tableRowProperty) { tableRowProperty.addListener((property, oldValue, newValue) -> { // If the containing TreeTableRow disappears, unbind the context menu if any if (newValue == null) { ctxMenuProperty.unbind(); return; } // Otherwise, bind the ContextMenu to the TreeItem Property of the containing TreeTable row. bindContextMenu(appCtx, ctxMenuProperty, newValue.treeItemProperty()); }); } /** * Helper method which binds the dynamical computation of a {@link ContextMenu} to a {@link TreeItem} * {@link Property}. * <p> * * @param <T> the type of the item contained in the {@link TreeItem} * @param appCtx the {@link ApplicationContext} of the application * @param ctxMenuProperty the {@link ContextMenu} {@link Property} of the {@link TreeTableCell} or {@link TreeCell} * @param treeItemProperty the {@link TreeItem} {@link Property} */ private static <T> void bindContextMenu(ApplicationContext appCtx, ObjectProperty<ContextMenu> ctxMenuProperty, ReadOnlyObjectProperty<TreeItem<T>> treeItemProperty) { ctxMenuProperty.bind(new ObjectBinding<ContextMenu>() { { // Fire when the encapsulated TreeItem instance changes super.bind(treeItemProperty); } @Override protected ContextMenu computeValue() { // No TreeItemProperty == no ContextMenu if (treeItemProperty == null) { return null; } TreeItem<?> treeItem = treeItemProperty.get(); // No or Empty TreeItem == no ContextMenu, otherwise construct the menu. return (treeItem == null || treeItem.getChildren().size() == 0) ? null : getContextMenu(appCtx, treeItem); } }); } /** * Constructs the {@link ContextMenu} for a {@link TreeItem}. * <p> * Based on the type of the Object contained in the {@link TreeItem}, menu options can be added. * <p> * The following options are always provided : Expand All (selected node and all its descendants), Expand First Only * (recursively expand only the first child) and Collapse All (collapse the entire subtree). * <p> * For {@link Node}s, the menu will (when fixed) add an Export To File option. * <p> * * @param <T> the type of the item contained in the {@link TreeItem} * @param appCtx the {@link ApplicationContext} of the application * @param treeItem the {@link TreeItem} for which the {@link ContextMenu} is constructed * @return the constructed {@link ContextMenu} */ private static <T> ContextMenu getContextMenu(ApplicationContext appCtx, TreeItem<T> treeItem) { ContextMenu menu = new ContextMenu(); addMenuItem( menu.getItems(), appCtx.textFor(CTXMENU_TREE_EXPANDFULLY), info -> expandFully(treeItem)); addMenuItem( menu.getItems(), appCtx.textFor(CTXMENU_TREE_EXPANDFIRSTONLY), info -> expandFirstOnly(treeItem)); addMenuItem( menu.getItems(), appCtx.textFor(CTXMENU_TREE_COLLAPSE), info -> collapseFully(treeItem)); // TODO FIX - ProfileNodes are no longer used. if (treeItem.getValue() instanceof Node) { addMenuItem( menu.getItems(), appCtx.textFor(CTXMENU_TREE_EXPORTSUBTREE), info -> showExportDialog( appCtx, menu.getScene().getWindow(), "stack_profile.txt", out -> writeStack(out, (Node)treeItem.getValue()) )); } return menu; } // Instance Constructors /** * Private Constructor for utility class. */ private ContextMenuUtil() { // Private Constructor for utility class } }