1 /* 2 * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. 3 * 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * The contents of this file are subject to the terms of either the Universal Permissive License 7 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 8 * 9 * or the following license: 10 * 11 * Redistribution and use in source and binary forms, with or without modification, are permitted 12 * provided that the following conditions are met: 13 * 14 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 15 * and the following disclaimer. 16 * 17 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 18 * conditions and the following disclaimer in the documentation and/or other materials provided with 19 * the distribution. 20 * 21 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 22 * endorse or promote products derived from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 30 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 31 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 package org.openjdk.jmc.flightrecorder.ui.pages; 34 35 import java.util.List; 36 import java.util.Optional; 37 38 import org.eclipse.jface.action.IAction; 39 import org.eclipse.jface.viewers.CheckboxTableViewer; 40 import org.eclipse.jface.viewers.IStructuredSelection; 41 import org.eclipse.jface.viewers.StructuredSelection; 42 import org.eclipse.osgi.util.NLS; 43 import org.eclipse.swt.SWT; 44 import org.eclipse.swt.custom.SashForm; 45 import org.eclipse.swt.graphics.Image; 46 import org.eclipse.swt.layout.GridData; 47 import org.eclipse.swt.layout.GridLayout; 48 import org.eclipse.swt.widgets.Composite; 49 import org.eclipse.ui.forms.widgets.Form; 50 import org.eclipse.ui.forms.widgets.FormToolkit; 51 52 import org.openjdk.jmc.common.IState; 53 import org.openjdk.jmc.common.IWritableState; 54 import org.openjdk.jmc.common.item.IAttribute; 55 import org.openjdk.jmc.common.item.IItemCollection; 56 import org.openjdk.jmc.common.item.IItemFilter; 57 import org.openjdk.jmc.common.unit.IQuantity; 58 import org.openjdk.jmc.common.unit.IRange; 59 import org.openjdk.jmc.flightrecorder.JfrAttributes; 60 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 61 import org.openjdk.jmc.flightrecorder.ui.IPageUI; 62 import org.openjdk.jmc.flightrecorder.ui.StreamModel; 63 import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit; 64 import org.openjdk.jmc.flightrecorder.ui.common.FilterComponent; 65 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector; 66 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState; 67 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram; 68 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.HistogramSelection; 69 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 70 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; 71 import org.openjdk.jmc.ui.charts.IXDataRenderer; 72 import org.openjdk.jmc.ui.charts.RendererToolkit; 73 import org.openjdk.jmc.ui.charts.XYChart; 74 import org.openjdk.jmc.ui.column.ColumnMenusFactory; 75 import org.openjdk.jmc.ui.handlers.ActionToolkit; 76 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 77 import org.openjdk.jmc.ui.misc.ActionUiToolkit; 78 import org.openjdk.jmc.ui.misc.ChartCanvas; 79 import org.openjdk.jmc.ui.misc.PersistableSashForm; 80 81 abstract class ChartAndTableUI implements IPageUI { 82 83 private static final String SASH = "sash"; //$NON-NLS-1$ 84 private static final String TABLE = "table"; //$NON-NLS-1$ 85 private static final String CHART = "chart"; //$NON-NLS-1$ 86 private static final String SELECTED = "selected"; //$NON-NLS-1$ 87 private final IItemFilter pageFilter; 88 private final StreamModel model; 89 protected CheckboxTableViewer chartLegend; 90 protected final Form form; 91 protected final Composite chartContainer; 92 protected final ChartCanvas chartCanvas; 93 protected final FilterComponent tableFilterComponent; 94 protected final ItemHistogram table; 95 protected final SashForm sash; 96 private final IPageContainer pageContainer; 97 protected List<IAction> allChartSeriesActions; 98 private IItemCollection selectionItems; 99 private IRange<IQuantity> timeRange; 100 protected XYChart chart; 101 protected FlavorSelector flavorSelector; 102 103 ChartAndTableUI(IItemFilter pageFilter, StreamModel model, Composite parent, FormToolkit toolkit, 104 IPageContainer pageContainer, IState state, String sectionTitle, IItemFilter tableFilter, Image icon, 105 FlavorSelectorState flavorSelectorState) { 106 this.pageFilter = pageFilter; 107 this.model = model; 108 this.pageContainer = pageContainer; 109 form = DataPageToolkit.createForm(parent, toolkit, sectionTitle, icon); 110 sash = new SashForm(form.getBody(), SWT.VERTICAL); 111 toolkit.adapt(sash); 112 113 table = buildHistogram(sash, state.getChild(TABLE)); 114 MCContextMenuManager mm = MCContextMenuManager.create(table.getManager().getViewer().getControl()); 115 ColumnMenusFactory.addDefaultMenus(table.getManager(), mm); 116 table.getManager().getViewer().addSelectionChangedListener(e -> buildChart()); 117 table.getManager().getViewer() 118 .addSelectionChangedListener(e -> pageContainer.showSelection(table.getSelection().getItems())); 119 SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), table, 120 NLS.bind(Messages.ChartAndTableUI_HISTOGRAM_SELECTION, sectionTitle), mm); 121 tableFilterComponent = FilterComponent.createFilterComponent(table.getManager().getViewer().getControl(), 122 table.getManager(), tableFilter, model.getItems().apply(pageFilter), 123 pageContainer.getSelectionStore()::getSelections, this::onFilterChange); 124 mm.add(tableFilterComponent.getShowFilterAction()); 125 mm.add(tableFilterComponent.getShowSearchAction()); 126 127 chartContainer = toolkit.createComposite(sash); 128 chartContainer.setLayout(new GridLayout(2, false)); 129 chartCanvas = new ChartCanvas(chartContainer); 130 chartCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 131 132 allChartSeriesActions = initializeChartConfiguration(state); 133 IState chartState = state.getChild(CHART); 134 ActionToolkit.loadCheckState(chartState, allChartSeriesActions.stream()); 135 chartLegend = ActionUiToolkit.buildCheckboxViewer(chartContainer, allChartSeriesActions.stream()); 136 GridData gd = new GridData(SWT.FILL, SWT.FILL, false, true); 137 gd.widthHint = 180; 138 chartLegend.getControl().setLayoutData(gd); 139 PersistableSashForm.loadState(sash, state.getChild(SASH)); 140 DataPageToolkit.createChartTimestampTooltip(chartCanvas); 141 142 chart = new XYChart(pageContainer.getRecordingRange(), RendererToolkit.empty(), 180); 143 DataPageToolkit.setChart(chartCanvas, chart, pageContainer::showSelection); 144 SelectionStoreActionToolkit.addSelectionStoreRangeActions(pageContainer.getSelectionStore(), chart, 145 JfrAttributes.LIFETIME, NLS.bind(Messages.ChartAndTableUI_TIMELINE_SELECTION, form.getText()), 146 chartCanvas.getContextMenu()); 147 buildChart(); 148 149 if (chartState != null) { 150 final String legendSelection = chartState.getAttribute(SELECTED); 151 152 if (legendSelection != null) { 153 allChartSeriesActions.stream().filter(ia -> legendSelection.equals(ia.getId())).findFirst() 154 .ifPresent(a -> chartLegend.setSelection(new StructuredSelection(a))); 155 } 156 } 157 158 flavorSelector = FlavorSelector.itemsWithTimerange(form, pageFilter, model.getItems(), pageContainer, 159 this::onFlavorSelected, this::onSetRange, flavorSelectorState); 160 } 161 162 protected void onFilterChange(IItemFilter filter) { 163 IItemCollection items = getItems(); 164 if (tableFilterComponent.isVisible()) { 165 table.show(items.apply(filter)); 166 tableFilterComponent.setColor(table.getAllRows().getRowCount()); 167 } else { 168 table.show(items); 169 } 170 } 171 172 @Override 173 public void saveTo(IWritableState writableState) { 174 PersistableSashForm.saveState(sash, writableState.createChild(SASH)); 175 table.getManager().getSettings().saveState(writableState.createChild(TABLE)); 176 IWritableState chartState = writableState.createChild(CHART); 177 178 ActionToolkit.saveCheckState(chartState, allChartSeriesActions.stream()); 179 Object legendSelection = ((IStructuredSelection) chartLegend.getSelection()).getFirstElement(); 180 if (legendSelection != null) { 181 chartState.putString(SELECTED, ((IAction) legendSelection).getId()); 182 } 183 } 184 185 private void onSetRange(Boolean useRange) { 186 IRange<IQuantity> range = useRange ? timeRange : pageContainer.getRecordingRange(); 187 chart.setVisibleRange(range.getStart(), range.getEnd()); 188 buildChart(); 189 } 190 191 private void onFlavorSelected(IItemCollection items, IRange<IQuantity> timeRange) { 192 this.selectionItems = items; 193 this.timeRange = timeRange; 194 table.show(getItems()); 195 196 if (selectionItems != null) { 197 Object[] tableInput = ((Object[]) table.getManager().getViewer().getInput()); 198 if (tableInput != null) { 199 table.getManager().getViewer().setSelection(new StructuredSelection(tableInput)); 200 } else { 201 table.getManager().getViewer().setSelection(null); 202 } 203 } 204 } 205 206 protected void buildChart() { 207 IXDataRenderer rendererRoot = getChartRenderer(getItems(), table.getSelection()); 208 chartCanvas.replaceRenderer(rendererRoot); 209 } 210 211 private IItemCollection getItems() { 212 return selectionItems != null ? selectionItems.apply(pageFilter) : model.getItems().apply(pageFilter); 213 } 214 215 protected boolean isAttributeEnabled(IAttribute<IQuantity> attr) { 216 Optional<IAction> action = allChartSeriesActions.stream().filter(a -> attr.getIdentifier().equals(a.getId())) 217 .findAny(); 218 return action.isPresent() && action.get().isChecked(); 219 } 220 221 protected abstract ItemHistogram buildHistogram(Composite parent, IState state); 222 223 protected abstract IXDataRenderer getChartRenderer(IItemCollection itemsInTable, HistogramSelection selection); 224 225 protected abstract List<IAction> initializeChartConfiguration(IState state); 226 }