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 }