1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2019, Red Hat Inc. All rights reserved.
   4  *
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * The contents of this file are subject to the terms of either the Universal Permissive License
   8  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   9  *
  10  * or the following license:
  11  *
  12  * Redistribution and use in source and binary forms, with or without modification, are permitted
  13  * provided that the following conditions are met:
  14  *
  15  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  16  * and the following disclaimer.
  17  *
  18  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  19  * conditions and the following disclaimer in the documentation and/or other materials provided with
  20  * the distribution.
  21  *
  22  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  23  * endorse or promote products derived from this software without specific prior written permission.
  24  *
  25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  27  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  31  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  32  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33  */
  34 package org.openjdk.jmc.flightrecorder.ui.pages;
  35 
  36 import org.eclipse.jface.viewers.StructuredSelection;
  37 import org.eclipse.osgi.util.NLS;
  38 import org.eclipse.swt.SWT;
  39 import org.eclipse.swt.custom.SashForm;
  40 import org.eclipse.swt.custom.ScrolledComposite;
  41 import org.eclipse.swt.graphics.Image;
  42 import org.eclipse.swt.layout.FillLayout;
  43 import org.eclipse.swt.layout.FormAttachment;
  44 import org.eclipse.swt.layout.FormData;
  45 import org.eclipse.swt.layout.FormLayout;
  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.swt.widgets.Event;
  50 import org.eclipse.swt.widgets.Listener;
  51 import org.eclipse.ui.forms.widgets.FormToolkit;
  52 
  53 import org.openjdk.jmc.common.IState;
  54 import org.openjdk.jmc.common.IWritableState;
  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.StreamModel;
  62 import org.openjdk.jmc.flightrecorder.ui.common.DataPageToolkit;
  63 import org.openjdk.jmc.flightrecorder.ui.common.FilterComponent;
  64 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector;
  65 import org.openjdk.jmc.flightrecorder.ui.common.FlavorSelector.FlavorSelectorState;
  66 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram;
  67 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  68 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit;
  69 import org.openjdk.jmc.ui.charts.ChartFilterControlBar;
  70 import org.openjdk.jmc.ui.charts.IXDataRenderer;
  71 import org.openjdk.jmc.ui.charts.RendererToolkit;
  72 import org.openjdk.jmc.ui.charts.XYChart;
  73 import org.openjdk.jmc.ui.handlers.ActionToolkit;
  74 import org.openjdk.jmc.ui.misc.ActionUiToolkit;
  75 import org.openjdk.jmc.ui.misc.ChartCanvas;
  76 import org.openjdk.jmc.ui.misc.ChartDisplayControlBar;
  77 import org.openjdk.jmc.ui.misc.ChartTextCanvas;
  78 import org.openjdk.jmc.ui.misc.PersistableSashForm;
  79 import org.openjdk.jmc.ui.misc.TimelineCanvas;
  80 
  81 abstract class ChartAndPopupTableUI extends ChartAndTableUI {
  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 static final int TIMELINE_HEIGHT = 40;
  88         private static final int X_OFFSET = 0;
  89         private static final int Y_OFFSET = 0;
  90         protected ChartFilterControlBar filterBar;
  91         protected ChartTextCanvas textCanvas;
  92         protected ItemHistogram hiddenTable;
  93         protected IPageContainer pageContainer;
  94         private ChartDisplayControlBar displayBar;
  95         private Composite hiddenTableContainer;
  96         private IItemCollection selectionItems;
  97         private IItemFilter pageFilter;
  98         private IRange<IQuantity> timeRange;
  99         private TimelineCanvas timelineCanvas;
 100 
 101         ChartAndPopupTableUI(IItemFilter pageFilter, StreamModel model, Composite parent, FormToolkit toolkit,
 102                         IPageContainer pageContainer, IState state, String sectionTitle, IItemFilter tableFilter, Image icon,
 103                         FlavorSelectorState flavorSelectorState) {
 104                 super(pageFilter, model, parent, toolkit, pageContainer, state, sectionTitle, tableFilter, icon, flavorSelectorState);
 105         }
 106 
 107         protected void init(IItemFilter pageFilter, StreamModel model, Composite parent, FormToolkit toolkit,
 108                         IPageContainer pageContainer, IState state, String sectionTitle, IItemFilter tableFilter, Image icon,
 109                         FlavorSelectorState flavorSelectorState) {
 110                 this.pageFilter = pageFilter;
 111                 this.model = model;
 112                 this.pageContainer = pageContainer;
 113                 form = DataPageToolkit.createForm(parent, toolkit, sectionTitle, icon);
 114 
 115                 hiddenTableContainer = new Composite(form, SWT.NONE);
 116                 hiddenTableContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 117                 hiddenTableContainer.setVisible(false);
 118 
 119                 hiddenTable = buildHistogram(hiddenTableContainer, state.getChild(TABLE));
 120                 hiddenTable.getManager().getViewer().addSelectionChangedListener(e -> buildChart());
 121 
 122                 tableFilterComponent = FilterComponent.createFilterComponent(hiddenTable.getManager().getViewer().getControl(),
 123                                 hiddenTable.getManager(), tableFilter, model.getItems().apply(pageFilter),
 124                                 pageContainer.getSelectionStore()::getSelections, this::onFilterChange);
 125 
 126                 /**
 127                  * Chart Container (1 column gridlayout) - Contains filter bar & graph container
 128                  * Graph Container (2 column gridlayout) - Contains chart and timeline container & display bar
 129                  * Chart and Timeline Container (1 column gridlayout) Contains chart and text container and timeline canvas
 130                  * Zoom-pan and Chart Container (formlayout) - Contains chart and text container contents and zoom-pan overlay
 131                  * Zoom-pan Container (filllayout) - Contains zoom-pan chart overlay
 132                  * Full screen Chart Container (1 column gridlayout) - Contains chart container
 133                  * Chart and Text Container (2 column gridlayout) - Contains scText and textCanvas) & scChart (and chart canvas)
 134                  */
 135                 chartContainer = toolkit.createComposite(form.getBody());
 136                 chartContainer.setLayout(new GridLayout());
 137                 chartContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 138                 // Filter Controls
 139                 Listener resetListener = new Listener() {
 140                         @Override
 141                         public void handleEvent(Event event) {
 142                                 onSetRange(false);
 143                         }
 144                 };
 145                 filterBar = new ChartFilterControlBar(chartContainer, resetListener, pageContainer.getRecordingRange());
 146                 filterBar.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
 147 
 148                 // Container to hold the chart (& timeline) and display toolbar
 149                 Composite graphContainer = toolkit.createComposite(chartContainer);
 150                 GridLayout gridLayout = new GridLayout(2, false);
 151                 gridLayout.marginWidth = 0;
 152                 gridLayout.marginHeight = 0;
 153                 graphContainer.setLayout(gridLayout);
 154                 graphContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 155 
 156                 // Container to hold the chart and timeline canvas
 157                 Composite chartAndTimelineContainer = toolkit.createComposite(graphContainer);
 158                 gridLayout = new GridLayout();
 159                 gridLayout.marginWidth = 0;
 160                 gridLayout.marginHeight = 0;
 161                 chartAndTimelineContainer.setLayout(gridLayout);
 162                 chartAndTimelineContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 163 
 164                 // Container to hold the chart and a zoom-pan overlay
 165                 Composite zoomPanAndChartContainer = toolkit.createComposite(chartAndTimelineContainer);
 166                 zoomPanAndChartContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 167                 zoomPanAndChartContainer.setLayout(new FormLayout());
 168 
 169                 // Container to hold fixed zoom-pan display
 170                 Composite zoomPanContainer = toolkit.createComposite(zoomPanAndChartContainer);
 171                 zoomPanContainer.setLayout(new FillLayout());
 172                 FormData fd = new FormData();
 173                 fd.height = 80;
 174                 fd.width = 150;
 175                 fd.bottom = new FormAttachment(100, -12);
 176                 fd.right = new FormAttachment(100, -12);
 177                 zoomPanContainer.setLayoutData(fd);
 178 
 179                 // Container to hold the chart
 180                 Composite fullScreenChartContainer = toolkit.createComposite(zoomPanAndChartContainer);
 181                 fullScreenChartContainer.setLayout(gridLayout);
 182                 fd = new FormData();
 183                 fd.right = new FormAttachment(100, -1);
 184                 fd.top = new FormAttachment(0, 1);
 185                 fd.left = new FormAttachment(0, 1);
 186                 fd.bottom = new FormAttachment(100, -1);
 187                 fullScreenChartContainer.setLayoutData(fd);
 188 
 189                 // Container to hold the text and chart canvases
 190                 Composite chartAndTextContainer = toolkit.createComposite(fullScreenChartContainer);
 191                 gridLayout = new GridLayout(2, false);
 192                 gridLayout.horizontalSpacing = 0;
 193                 gridLayout.marginWidth = 0;
 194                 gridLayout.marginHeight = 0;
 195                 chartAndTextContainer.setLayout(gridLayout);
 196                 chartAndTextContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 197 
 198                 sash = new SashForm(chartAndTextContainer, SWT.VERTICAL);
 199                 sash.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 200                 toolkit.adapt(sash);
 201 
 202                 ScrolledComposite scText = new ScrolledComposite(sash, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
 203                 GridData scTextGd = new GridData(SWT.FILL, SWT.FILL, false, true);
 204                 scTextGd.widthHint = 180;
 205                 scText.setLayoutData(scTextGd);
 206                 textCanvas = new ChartTextCanvas(scText);
 207                 textCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
 208 
 209                 ScrolledComposite scChart = new ScrolledComposite(sash, SWT.BORDER | SWT.V_SCROLL);
 210                 scChart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 211                 chartCanvas = new ChartCanvas(scChart);
 212                 chartCanvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 213 
 214                 chartCanvas.setTextCanvas(textCanvas);
 215                 textCanvas.setChartCanvas(chartCanvas);
 216 
 217                 scChart.setContent(chartCanvas);
 218                 scChart.setAlwaysShowScrollBars(true);
 219                 scChart.setExpandHorizontal(true);
 220                 scChart.setExpandVertical(true);
 221                 scText.setContent(textCanvas);
 222                 scText.setAlwaysShowScrollBars(false);
 223                 scText.setExpandHorizontal(true);
 224                 scText.setExpandVertical(true);
 225 
 226                 timelineCanvas = new TimelineCanvas(chartAndTimelineContainer, chartCanvas, sash);
 227                 GridData gridData = new GridData(SWT.FILL, SWT.DEFAULT, true, false);
 228                 gridData.heightHint = TIMELINE_HEIGHT;
 229                 timelineCanvas.setLayoutData(gridData);
 230 
 231                 // add the display bar to the right of the chart scrolled composite
 232                 displayBar = new ChartDisplayControlBar(graphContainer);
 233 
 234                 allChartSeriesActions = initializeChartConfiguration(state);
 235                 IState chartState = state.getChild(CHART);
 236                 ActionToolkit.loadCheckState(chartState, allChartSeriesActions.stream());
 237                 chartLegend = ActionUiToolkit.buildCheckboxViewer(chartContainer, allChartSeriesActions.stream());
 238                 gridData = new GridData(SWT.FILL, SWT.FILL, false, true);
 239                 gridData.widthHint = 180;
 240                 chartLegend.getControl().setLayoutData(gridData);
 241                 PersistableSashForm.loadState(sash, state.getChild(SASH));
 242                 DataPageToolkit.createChartTimestampTooltip(chartCanvas);
 243 
 244                 chart = new XYChart(pageContainer.getRecordingRange(), RendererToolkit.empty(), X_OFFSET, Y_OFFSET, timelineCanvas, filterBar, displayBar);
 245                 DataPageToolkit.setChart(chartCanvas, chart, pageContainer::showSelection);
 246                 DataPageToolkit.setChart(textCanvas, chart, pageContainer::showSelection);
 247                 SelectionStoreActionToolkit.addSelectionStoreRangeActions(pageContainer.getSelectionStore(), chart,
 248                                 JfrAttributes.LIFETIME, NLS.bind(Messages.ChartAndTableUI_TIMELINE_SELECTION, form.getText()),
 249                                 chartCanvas.getContextMenu());
 250                 buildChart();
 251 
 252                 // Wire-up the chart & text canvases to the filter and display bars
 253                 displayBar.setChart(chart);
 254                 displayBar.setChartCanvas(chartCanvas);
 255                 displayBar.setTextCanvas(textCanvas);
 256                 displayBar.createZoomPan(zoomPanContainer);
 257                 chartCanvas.setZoomOnClickListener(mouseDown -> displayBar.zoomOnClick(mouseDown));
 258                 chartCanvas.setZoomToSelectionListener(() -> displayBar.zoomToSelection());
 259                 timelineCanvas.setChart(chart);
 260 
 261                 if (chartState != null) {
 262                         final String legendSelection = chartState.getAttribute(SELECTED);
 263 
 264                         if (legendSelection != null) {
 265                                 allChartSeriesActions.stream().filter(ia -> legendSelection.equals(ia.getId())).findFirst()
 266                                                 .ifPresent(a -> chartLegend.setSelection(new StructuredSelection(a)));
 267                         }
 268                 }
 269 
 270                 flavorSelector = FlavorSelector.itemsWithTimerange(form, pageFilter, model.getItems(), pageContainer,
 271                                 this::onFlavorSelected, this::onSetRange, flavorSelectorState);
 272         }
 273 
 274         protected void onFilterChange(IItemFilter filter) {
 275                 IItemCollection items = getItems();
 276                 if (tableFilterComponent.isVisible()) {
 277                         table.show(items.apply(filter));
 278                         tableFilterComponent.setColor(table.getAllRows().getRowCount());
 279                 } else if (table != null) {
 280                         table.show(items);
 281                 }
 282         }
 283 
 284         @Override
 285         public void saveTo(IWritableState writableState) {
 286                 table = getUndisposedTable();
 287                 super.saveTo(writableState);
 288         }
 289 
 290         private void onSetRange(Boolean useRange) {
 291                 IRange<IQuantity> range = useRange ? timeRange : pageContainer.getRecordingRange();
 292                 chart.setVisibleRange(range.getStart(), range.getEnd());
 293                 chart.resetZoomFactor();
 294                 displayBar.resetZoomScale();
 295                 buildChart();
 296         }
 297 
 298         private void onFlavorSelected(IItemCollection items, IRange<IQuantity> timeRange) {
 299                 this.selectionItems = items;
 300                 this.timeRange = timeRange;
 301                 hiddenTable.show(getItems());
 302                 if (selectionItems != null) {
 303                         Object[] tableInput = (Object[]) hiddenTable.getManager().getViewer().getInput();
 304                         if (tableInput != null) {
 305                                 hiddenTable.getManager().getViewer().setSelection(new StructuredSelection(tableInput));
 306                         } else {
 307                                 hiddenTable.getManager().getViewer().setSelection(null);
 308                         }
 309                 }
 310         }
 311 
 312         protected void buildChart() {
 313                 IXDataRenderer rendererRoot = getChartRenderer(getItems(), getUndisposedTable().getSelection());
 314                 chartCanvas.replaceRenderer(rendererRoot);
 315                 textCanvas.replaceRenderer(rendererRoot);
 316         }
 317 
 318         private IItemCollection getItems() {
 319                 return selectionItems != null ? selectionItems.apply(pageFilter) : model.getItems().apply(pageFilter);
 320         }
 321 
 322         public void setTimeRange(IRange<IQuantity> range) {
 323                 this.timeRange = range;
 324         }
 325 
 326         protected ItemHistogram getUndisposedTable() {
 327                 return isDisposed(table) ? hiddenTable : table;
 328         }
 329 
 330         private boolean isDisposed(ItemHistogram histogram) {
 331                 return histogram == null ? true : histogram.getManager().getViewer().getControl().isDisposed();
 332         }
 333 }