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 }