diff --git a/application/org.openjdk.jmc.flightrecorder.metadata/src/main/java/org/openjdk/jmc/flightrecorder/metadata/MetadataPage.java b/application/org.openjdk.jmc.flightrecorder.metadata/src/main/java/org/openjdk/jmc/flightrecorder/metadata/MetadataPage.java index 55c1a2dbb..52c89f281 100644 --- a/application/org.openjdk.jmc.flightrecorder.metadata/src/main/java/org/openjdk/jmc/flightrecorder/metadata/MetadataPage.java +++ b/application/org.openjdk.jmc.flightrecorder.metadata/src/main/java/org/openjdk/jmc/flightrecorder/metadata/MetadataPage.java @@ -63,6 +63,7 @@ import org.openjdk.jmc.flightrecorder.ui.common.AbstractDataPage; import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit; import org.openjdk.jmc.flightrecorder.ui.common.LabeledPageFactory; +import org.openjdk.jmc.flightrecorder.ui.common.TreeExpandCollapseSupport; import org.openjdk.jmc.flightrecorder.ui.common.TypeLabelProvider; import org.openjdk.jmc.ui.TypeAppearance; import org.openjdk.jmc.ui.column.ColumnBuilder; @@ -179,7 +180,7 @@ public class MetadataUi implements IPageUI { MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); ColumnMenusFactory.addDefaultMenus(attributeTable, mm); ColumnViewerToolTipSupport.enableFor(viewer); - + TreeExpandCollapseSupport.installFor(viewer); viewer.setContentProvider(new MetadataContentProvider()); ColumnViewerToolTipSupport.enableFor(viewer); Text tableFilter = ColumnsFilter.addFilterControl(formBody, toolkit, attributeTable); diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/TreeExpandCollapseSupport.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/TreeExpandCollapseSupport.java new file mode 100644 index 000000000..c047e2500 --- /dev/null +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/common/TreeExpandCollapseSupport.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.openjdk.jmc.flightrecorder.ui.common; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; + +/** + * Enables double-click and enter key press to expand or collapse tree nodes. + */ +public final class TreeExpandCollapseSupport { + + /** + * Private since we don't want any instances. + */ + private TreeExpandCollapseSupport() { + } + + /** + * Installs a handler for both double click and enter key press. + * + * @param viewer + * the TreeViewer to update + */ + public static void installFor(TreeViewer viewer) { + viewer.addDoubleClickListener(event -> { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); + if (!selection.isEmpty()) { + Object element = selection.getFirstElement(); + if (element != null) { + boolean isExpanded = viewer.getExpandedState(element); + viewer.setExpandedState(element, !isExpanded); + } + } + }); + } +} diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/EventBrowserPage.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/EventBrowserPage.java index 8dd8b2dff..162ce9768 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/EventBrowserPage.java +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/EventBrowserPage.java @@ -89,6 +89,7 @@ import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; import org.openjdk.jmc.flightrecorder.ui.common.ItemList; import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder; +import org.openjdk.jmc.flightrecorder.ui.common.TreeExpandCollapseSupport; import org.openjdk.jmc.flightrecorder.ui.common.TypeFilterBuilder; import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; @@ -209,6 +210,7 @@ class EventBrowserUI implements IPageUI { treeSash = new SashForm(form.getBody(), SWT.HORIZONTAL); toolkit.adapt(treeSash); typeFilterTree = DataPageToolkit.buildEventTypeTree(treeSash, toolkit, this::onTypeChange, false); + TreeExpandCollapseSupport.installFor(typeFilterTree.getViewer()); MCContextMenuManager mm = typeFilterTree.getMenuManager(); IAction addPageAction = ActionToolkit.action(() -> DataPageToolkit.addPage(selectedTypes), Messages.EventBrowserPage_NEW_PAGE_USING_TYPES_ACTION, NEW_PAGE_ICON); diff --git a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadDumpsPage.java b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadDumpsPage.java index b6a233328..67228eb43 100644 --- a/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadDumpsPage.java +++ b/application/org.openjdk.jmc.flightrecorder.ui/src/main/java/org/openjdk/jmc/flightrecorder/ui/pages/ThreadDumpsPage.java @@ -92,6 +92,7 @@ import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector; import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants; +import org.openjdk.jmc.flightrecorder.ui.common.TreeExpandCollapseSupport; import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; import org.openjdk.jmc.ui.UIPlugin; import org.openjdk.jmc.ui.common.util.FilterMatcher; @@ -303,6 +304,7 @@ public Image getImage(Object element) { }; tree.setLabelProvider(labelProvider); + TreeExpandCollapseSupport.installFor(tree); tree.addSelectionChangedListener( s -> text.setText(joinSelection(((IStructuredSelection) s.getSelection()).toList()))); treeTextFilter = TreeFilterWithTextInput.addFilterControl(filterComposite, tree, diff --git a/application/org.openjdk.jmc.rjmx.ui/src/main/java/org/openjdk/jmc/rjmx/ui/internal/CombinedChartSectionPart.java b/application/org.openjdk.jmc.rjmx.ui/src/main/java/org/openjdk/jmc/rjmx/ui/internal/CombinedChartSectionPart.java index cd5b66568..13336c299 100644 --- a/application/org.openjdk.jmc.rjmx.ui/src/main/java/org/openjdk/jmc/rjmx/ui/internal/CombinedChartSectionPart.java +++ b/application/org.openjdk.jmc.rjmx.ui/src/main/java/org/openjdk/jmc/rjmx/ui/internal/CombinedChartSectionPart.java @@ -55,6 +55,8 @@ import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; @@ -534,12 +536,7 @@ private void createLegend(FormToolkit formToolkit, Composite container, IMRIServ public void checkStateChanged(CheckStateChangedEvent event) { MRI mri = (MRI) event.getElement(); boolean enable = event.getChecked(); - setEnabled(mri, enable); - if (enable) { - setQuantityKindFromAttribute(mri); - } - refreshAll(); - + updateLegendCheckedState(mri, enable); } }); @@ -560,7 +557,23 @@ public boolean isChecked(Object element) { legend.setLabelProvider(new AttributeLabelProvider(mds, mris)); legend.setInput(this); ColumnViewerToolTipSupport.enableFor(legend); - + legend.getTable().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.CR) { + IStructuredSelection selection = legend.getStructuredSelection(); + if (!selection.isEmpty()) { + Object element = selection.getFirstElement(); + if (element != null) { + MRI mri = (MRI) element; + boolean isChecked = legend.getChecked(element); + legend.setChecked(element, !isChecked); + updateLegendCheckedState(mri, legend.getChecked(element)); + } + } + } + } + }); GridData gd2 = new GridData(SWT.FILL, SWT.FILL, false, true); gd2.heightHint = 60; gd2.widthHint = 210; @@ -617,4 +630,11 @@ private void updateQuantityKind(KindOfQuantity currentKind) { } } + private void updateLegendCheckedState(MRI mri, boolean enable) { + setEnabled(mri, enable); + if (enable) { + setQuantityKindFromAttribute(mri); + } + refreshAll(); + } } diff --git a/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ActionUiToolkit.java b/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ActionUiToolkit.java index 92fbca572..479cf3171 100644 --- a/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ActionUiToolkit.java +++ b/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ActionUiToolkit.java @@ -48,7 +48,10 @@ import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; @@ -132,11 +135,24 @@ public static CheckboxTableViewer buildCheckboxViewer(Composite parent, Stream { IAction action = (IAction) e.getElement(); - if (action.isEnabled()) { - action.setChecked(e.getChecked()); - action.run(); - } else { - chartLegend.setChecked(action, action.isChecked()); + setCheckboxAction(chartLegend, action, e.getChecked()); + }); + chartLegend.getTable().addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.keyCode == SWT.CR) { + IStructuredSelection selection = chartLegend.getStructuredSelection(); + if (!selection.isEmpty()) { + Object element = selection.getFirstElement(); + if (element != null) { + IAction action = (IAction) element; + boolean isChecked = chartLegend.getChecked(element); + chartLegend.setChecked(element, !isChecked); + setCheckboxAction(chartLegend, action, chartLegend.getChecked(element)); + } + } + + } } }); // FIXME: Add a context menu for enablement, should that be done here or in the caller? @@ -193,4 +209,13 @@ public static Control buildToolBar(Composite parent, Stream actions, bo actions.forEach(tbm::add); return tbm.createControl(parent); } + + private static void setCheckboxAction(CheckboxTableViewer viewer, IAction action, boolean newState) { + if (action.isEnabled()) { + action.setChecked(newState); + action.run(); + } else { + viewer.setChecked(action, action.isChecked()); + } + } }