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.common; 34 35 import java.awt.Color; 36 import java.io.FileInputStream; 37 import java.io.InputStream; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.Collection; 41 import java.util.Comparator; 42 import java.util.HashMap; 43 import java.util.HashSet; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Map; 47 import java.util.Objects; 48 import java.util.Optional; 49 import java.util.Set; 50 import java.util.function.Consumer; 51 import java.util.function.Predicate; 52 import java.util.function.Supplier; 53 import java.util.regex.Matcher; 54 import java.util.regex.Pattern; 55 import java.util.stream.Collectors; 56 import java.util.stream.Stream; 57 58 import org.eclipse.core.runtime.IAdaptable; 59 import org.eclipse.jface.action.Action; 60 import org.eclipse.jface.action.IAction; 61 import org.eclipse.jface.action.IMenuListener; 62 import org.eclipse.jface.action.IMenuManager; 63 import org.eclipse.jface.action.MenuManager; 64 import org.eclipse.jface.action.Separator; 65 import org.eclipse.jface.dialogs.InputDialog; 66 import org.eclipse.jface.resource.ImageDescriptor; 67 import org.eclipse.jface.resource.JFaceResources; 68 import org.eclipse.jface.viewers.ColumnViewer; 69 import org.eclipse.jface.viewers.IBaseLabelProvider; 70 import org.eclipse.jface.viewers.StyledCellLabelProvider; 71 import org.eclipse.jface.viewers.ViewerCell; 72 import org.eclipse.jface.window.ToolTip; 73 import org.eclipse.jface.window.Window; 74 import org.eclipse.jface.wizard.Wizard; 75 import org.eclipse.jface.wizard.WizardDialog; 76 import org.eclipse.jface.wizard.WizardPage; 77 import org.eclipse.swt.SWT; 78 import org.eclipse.swt.custom.CTabFolder; 79 import org.eclipse.swt.custom.CTabItem; 80 import org.eclipse.swt.events.SelectionAdapter; 81 import org.eclipse.swt.events.SelectionEvent; 82 import org.eclipse.swt.graphics.GC; 83 import org.eclipse.swt.graphics.Image; 84 import org.eclipse.swt.graphics.ImageData; 85 import org.eclipse.swt.layout.FillLayout; 86 import org.eclipse.swt.layout.GridData; 87 import org.eclipse.swt.layout.GridLayout; 88 import org.eclipse.swt.widgets.Button; 89 import org.eclipse.swt.widgets.Composite; 90 import org.eclipse.swt.widgets.Control; 91 import org.eclipse.swt.widgets.Display; 92 import org.eclipse.swt.widgets.Event; 93 import org.eclipse.swt.widgets.FileDialog; 94 import org.eclipse.swt.widgets.Label; 95 import org.eclipse.ui.forms.widgets.Form; 96 import org.eclipse.ui.forms.widgets.FormText; 97 import org.eclipse.ui.forms.widgets.FormToolkit; 98 import org.openjdk.jmc.common.item.IAggregator; 99 import org.openjdk.jmc.common.item.IAttribute; 100 import org.openjdk.jmc.common.item.ICanonicalAccessorFactory; 101 import org.openjdk.jmc.common.item.IItem; 102 import org.openjdk.jmc.common.item.IItemCollection; 103 import org.openjdk.jmc.common.item.IItemFilter; 104 import org.openjdk.jmc.common.item.IItemIterable; 105 import org.openjdk.jmc.common.item.IItemQuery; 106 import org.openjdk.jmc.common.item.IMemberAccessor; 107 import org.openjdk.jmc.common.item.IType; 108 import org.openjdk.jmc.common.item.ItemFilters; 109 import org.openjdk.jmc.common.item.ItemToolkit; 110 import org.openjdk.jmc.common.unit.IQuantity; 111 import org.openjdk.jmc.common.unit.IRange; 112 import org.openjdk.jmc.common.unit.KindOfQuantity; 113 import org.openjdk.jmc.common.unit.QuantityRange; 114 import org.openjdk.jmc.common.unit.RangeContentType; 115 import org.openjdk.jmc.common.unit.UnitLookup; 116 import org.openjdk.jmc.common.util.ColorToolkit; 117 import org.openjdk.jmc.common.util.CompositeKey; 118 import org.openjdk.jmc.flightrecorder.JfrAttributes; 119 import org.openjdk.jmc.flightrecorder.jdk.JdkAttributes; 120 import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; 121 import org.openjdk.jmc.flightrecorder.jdk.JdkTypeIDs; 122 import org.openjdk.jmc.flightrecorder.rules.Result; 123 import org.openjdk.jmc.flightrecorder.rules.Severity; 124 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI; 125 import org.openjdk.jmc.flightrecorder.ui.IPageContainer; 126 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit; 127 import org.openjdk.jmc.flightrecorder.ui.ItemIterableToolkit; 128 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.CompositeKeyHistogramBuilder; 129 import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder; 130 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages; 131 import org.openjdk.jmc.flightrecorder.ui.overview.ResultOverview; 132 import org.openjdk.jmc.flightrecorder.ui.selection.IFilterFlavor; 133 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore.SelectionStoreEntry; 134 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit; 135 import org.openjdk.jmc.ui.charts.AWTChartToolkit.IColorProvider; 136 import org.openjdk.jmc.ui.charts.IQuantitySeries; 137 import org.openjdk.jmc.ui.charts.ISpanSeries; 138 import org.openjdk.jmc.ui.charts.IXDataRenderer; 139 import org.openjdk.jmc.ui.charts.QuantitySeries; 140 import org.openjdk.jmc.ui.charts.RendererToolkit; 141 import org.openjdk.jmc.ui.charts.SpanRenderer; 142 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange; 143 import org.openjdk.jmc.ui.charts.XYChart; 144 import org.openjdk.jmc.ui.charts.XYDataRenderer; 145 import org.openjdk.jmc.ui.charts.XYQuantities; 146 import org.openjdk.jmc.ui.column.ColumnMenusFactory; 147 import org.openjdk.jmc.ui.column.TableSettings; 148 import org.openjdk.jmc.ui.handlers.ActionToolkit; 149 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 150 import org.openjdk.jmc.ui.misc.ChartCanvas; 151 import org.openjdk.jmc.ui.misc.CompositeToolkit; 152 import org.openjdk.jmc.ui.misc.DisplayToolkit; 153 import org.openjdk.jmc.ui.misc.FilterEditor; 154 import org.openjdk.jmc.ui.misc.FilterEditor.AttributeValueProvider; 155 import org.openjdk.jmc.ui.misc.OverlayImageDescriptor; 156 import org.openjdk.jmc.ui.misc.SWTColorToolkit; 157 158 public class DataPageToolkit { 159 160 public static final IColorProvider<IItem> ITEM_COLOR = item -> TypeLabelProvider 161 .getColorOrDefault(item.getType().getIdentifier()); 162 163 public static final IColorProvider<IItem> getAttributeValueColor(final IAttribute<?> attribute) { 164 return new IColorProvider<IItem>() { 165 166 @Override 167 public Color getColor(IItem item) { 168 IMemberAccessor<?, IItem> accessor = attribute.getAccessor(ItemToolkit.getItemType(item)); 169 Object attributeValue = accessor != null ? accessor.getMember(item) : null; 170 // FIXME: Should we include the type or not? 171 return attributeValue != null 172 ? TypeLabelProvider.getColorOrDefault(attribute.getIdentifier() + "=" + attributeValue + "(" //$NON-NLS-1$ //$NON-NLS-2$ 173 + item.getType().getIdentifier() + ")") //$NON-NLS-1$ 174 : ITEM_COLOR.getColor(item); 175 } 176 }; 177 } 178 179 private static Map<String, Color> fieldColorMap = new HashMap<>(); 180 181 static { 182 183 // FIXME: Create FieldAppearance class, similar to TypeAppearence? 184 fieldColorMap.put(JdkAttributes.MACHINE_TOTAL.getIdentifier(), new Color(255, 128, 0)); 185 fieldColorMap.put(JdkAttributes.JVM_SYSTEM.getIdentifier(), new Color(128, 128, 128)); 186 fieldColorMap.put(JdkAttributes.JVM_USER.getIdentifier(), new Color(0, 0, 255)); 187 fieldColorMap.put(JdkAttributes.JVM_TOTAL.getIdentifier(), new Color(64, 64, 191)); 188 189 // FIXME: Handle ColorProvider and combined events 190 } 191 192 public static final Color ALLOCATION_COLOR = new Color(64, 144, 230); 193 194 public static final String FORM_TOOLBAR_PAGE_RESULTS = "pageResults"; //$NON-NLS-1$ 195 public static final String FORM_TOOLBAR_PAGE_SETUP = "pageSetup"; //$NON-NLS-1$ 196 public static final String FORM_TOOLBAR_PAGE_NAV = "pageNav"; //$NON-NLS-1$ 197 198 public static final String RESULT_ACTION_ID = "resultAction"; //$NON-NLS-1$ 199 200 public static Color getFieldColor(String fieldId) { 201 return fieldColorMap.getOrDefault(fieldId, ColorToolkit.getDistinguishableColor(fieldId)); 202 } 203 204 public static Color getFieldColor(IAttribute<?> attribute) { 205 return getFieldColor(attribute.getIdentifier()); 206 } 207 208 public static IAction createAttributeCheckAction(IAttribute<?> attribute, Consumer<Boolean> onChange) { 209 return createCheckAction(attribute.getName(), attribute.getDescription(), attribute.getIdentifier(), 210 getFieldColor(attribute), onChange); 211 } 212 213 public static IAction createTypeCheckAction( 214 String actionId, String typeId, String name, String description, Consumer<Boolean> onChange) { 215 return createCheckAction(name, description, actionId, TypeLabelProvider.getColorOrDefault(typeId), onChange); 216 } 217 218 public static IAction createAggregatorCheckAction( 219 IAggregator<?, ?> aggregator, String id, Color color, Consumer<Boolean> onChange) { 220 return createCheckAction(aggregator.getName(), aggregator.getDescription(), id, color, onChange); 221 } 222 223 public static IAction createCheckAction( 224 String name, String description, String id, Color color, Consumer<Boolean> onChange) { 225 return createCheckAction(name, description, id, 226 SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(color)), onChange); 227 } 228 229 public static IAction createCheckAction( 230 String name, String description, String id, ImageDescriptor icon, Consumer<Boolean> onChange) { 231 return ActionToolkit.checkAction(onChange, name, description, icon, id); 232 } 233 234 public static Optional<IXDataRenderer> buildLinesRow( 235 String title, String description, IItemCollection items, boolean fill, IItemQuery query, 236 Predicate<IAttribute<IQuantity>> attributeFilter, IQuantity includeLow, IQuantity includeHigh) { 237 XYDataRenderer renderer = includeHigh != null 238 ? new XYDataRenderer(includeLow, includeHigh, true, title, description) 239 : new XYDataRenderer(includeLow, title, description); 240 IItemCollection filteredItemsSupplier = items.apply(query.getFilter()); 241 Stream<IAttribute<IQuantity>> attributes = getQuantityAttributes(query); 242 if (attributeFilter != null) { 243 attributes = attributes.filter(attributeFilter); 244 } 245 if (DataPageToolkit.addEndTimeLines(renderer, filteredItemsSupplier, fill, attributes)) { 246 return Optional.of(new ItemRow(title, description, renderer, filteredItemsSupplier)); 247 } 248 return Optional.empty(); 249 } 250 251 /** 252 * @param q 253 * A query containing only {@code IAttribute<IQuantity>} attributes. Queries 254 * containing non-quantity attributes are not supported and may cause 255 * ClassCastExceptions later when the attributes are used. 256 * @return a stream of the query attributes 257 */ 258 /* 259 * FIXME: JMC-5125 - This cast chain is scary and should be reworked. 260 * 261 * If the query contains any non-quantity attributes then there will be a ClassCastException 262 * later when the attributes are used to extract values. 263 */ 264 @SuppressWarnings({"unchecked", "rawtypes"}) 265 public static Stream<IAttribute<IQuantity>> getQuantityAttributes(IItemQuery q) { 266 return (Stream) q.getAttributes().stream(); 267 } 268 269 public static void setChart(ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener) { 270 setChart(canvas, chart, selectionListener, null); 271 } 272 273 public static void setChart( 274 ChartCanvas canvas, XYChart chart, Consumer<IItemCollection> selectionListener, 275 Consumer<IRange<IQuantity>> selectRangeConsumer) { 276 IMenuManager contextMenu = canvas.getContextMenu(); 277 contextMenu.removeAll(); 278 canvas.getContextMenu().add(new Action(Messages.CHART_ZOOM_TO_SELECTED_RANGE) { 279 @Override 280 public void run() { 281 IQuantity selectionStart = chart.getSelectionStart(); 282 IQuantity selectionEnd = chart.getSelectionEnd(); 283 if (selectionStart == null || selectionEnd == null) { 284 chart.clearVisibleRange(); 285 } else { 286 chart.setVisibleRange(selectionStart, selectionEnd); 287 } 288 canvas.redrawChart(); 289 } 290 }); 291 292 canvas.setSelectionListener(() -> { 293 selectionListener.accept(ItemRow.getRangeSelection(chart, JfrAttributes.LIFETIME)); 294 IQuantity start = chart.getSelectionStart(); 295 IQuantity end = chart.getSelectionEnd(); 296 if (selectRangeConsumer != null) { 297 selectRangeConsumer 298 .accept(start != null && end != null ? QuantityRange.createWithEnd(start, end) : null); 299 } 300 }); 301 canvas.setChart(chart); 302 } 303 304 public static void setChart( 305 ChartCanvas canvas, XYChart chart, IAttribute<IQuantity> selectionAttribute, 306 Consumer<IItemCollection> selectionListener) { 307 IMenuManager contextMenu = canvas.getContextMenu(); 308 contextMenu.removeAll(); 309 canvas.setSelectionListener(() -> selectionListener.accept(ItemRow.getSelection(chart, selectionAttribute))); 310 canvas.setChart(chart); 311 } 312 313 /** 314 * Only works for items that are either fully overlapping, or disjunct. Must be ensured by 315 * client code. 316 */ 317 private static class RangePayload implements IAdaptable { 318 IItem item; 319 IQuantity start; 320 IQuantity end; 321 double rangeInPixels; 322 323 RangePayload(IItem item, IQuantity start, IQuantity end, double rangeInPixels) { 324 this.item = item; 325 this.start = start; 326 this.end = end; 327 this.rangeInPixels = rangeInPixels; 328 } 329 330 void combineWith(IItem item, IQuantity start, IQuantity end, double rangeInPixels) { 331 if (this.start.compareTo(start) < 0) { 332 // Will choose the item that starts last 333 this.start = start; 334 this.end = end; 335 this.item = item; 336 extendRangeInPixels(this.end.compareTo(start) > 0, rangeInPixels); 337 } else { 338 extendRangeInPixels(end.compareTo(this.start) > 0, rangeInPixels); 339 } 340 } 341 342 void extendRangeInPixels(boolean overlapping, double rangeInPixels) { 343 this.rangeInPixels = overlapping ? Math.max(this.rangeInPixels, rangeInPixels) 344 : this.rangeInPixels + rangeInPixels; 345 } 346 347 @Override 348 public <T> T getAdapter(Class<T> adapter) { 349 return IItem.class.isAssignableFrom(adapter) ? adapter.cast(item) : null; 350 } 351 } 352 353 private static ISpanSeries<RangePayload> rangeSeries( 354 IItemCollection events, IAttribute<IQuantity> startAttribute, IAttribute<IQuantity> endAttribute) { 355 return new ISpanSeries<RangePayload>() { 356 @Override 357 public XYQuantities<RangePayload[]> getQuantities(SubdividedQuantityRange xBucketRange) { 358 SubdividedQuantityRange xRange = xBucketRange.copyWithPixelSubdividers(); 359 List<RangePayload> spanningPixels = new ArrayList<>(); 360 RangePayload[] pixelBuckets = new RangePayload[xRange.getNumSubdividers()]; 361 events.forEach(is -> { 362 IMemberAccessor<IQuantity, IItem> startAccessor = startAttribute.getAccessor(is.getType()); 363 IMemberAccessor<IQuantity, IItem> endAccessor = endAttribute.getAccessor(is.getType()); 364 is.forEach(item -> { 365 IQuantity start = startAccessor.getMember(item); 366 IQuantity end = endAccessor.getMember(item); 367 int xPos = xRange.getFloorSubdivider(start); 368 int endPos = xRange.getFloorSubdivider(end); 369 if (xPos < pixelBuckets.length && endPos >= 0) { 370 // FIXME: If we have very short events (nanosecond scale) we can sometimes get a negative range. 371 double rangeInPixels = xRange.getPixel(end) - xRange.getPixel(start); 372 if (xPos != endPos) { 373 spanningPixels.add(new RangePayload(item, start, end, rangeInPixels)); 374 } else if (pixelBuckets[xPos] == null) { 375 pixelBuckets[xPos] = new RangePayload(item, start, end, rangeInPixels); 376 } else { 377 pixelBuckets[xPos].combineWith(item, start, end, rangeInPixels); 378 } 379 } 380 }); 381 }); 382 RangePayload[] sorted = Stream 383 .concat(Stream.of(pixelBuckets).filter(Objects::nonNull), spanningPixels.stream()) 384 .sorted(Comparator.comparing(r -> r.start)).toArray(RangePayload[]::new); 385 // FIXME: Should make it possible to use the RangePayload[] directly instead 386 List<IQuantity> starts = Stream.of(sorted).map(r -> r.start).collect(Collectors.toList()); 387 List<IQuantity> ends = Stream.of(sorted).map(r -> r.end).collect(Collectors.toList()); 388 return XYQuantities.create(sorted, starts, ends, xRange); 389 } 390 391 @Override 392 public IQuantity getStartX(RangePayload payload) { 393 return payload.start; 394 } 395 }; 396 } 397 398 public final static Color GC_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.GC_PAUSE); 399 private final static Color VM_OPERATIONS_BASE_COLOR = TypeLabelProvider.getColor(JdkTypeIDs.VM_OPERATIONS); 400 private final static IColorProvider<RangePayload> GC_COLOR = payload -> adjustAlpha(GC_BASE_COLOR, 401 payload.rangeInPixels); 402 private final static IColorProvider<RangePayload> APPLICATION_PAUSE_COLOR = payload -> adjustAlpha( 403 payload.item.getType().getIdentifier().equals(JdkTypeIDs.GC_PAUSE) ? GC_BASE_COLOR 404 : VM_OPERATIONS_BASE_COLOR, 405 payload.rangeInPixels); 406 public final static ImageDescriptor GC_LEGEND_ICON = new OverlayImageDescriptor( 407 SWTColorToolkit.getColorThumbnailDescriptor(SWTColorToolkit.asRGB(GC_BASE_COLOR)), false, 408 FlightRecorderUI.getDefault().getMCImageDescriptor("trash_overlay.png")); //$NON-NLS-1$ 409 410 /** 411 * Return a color with alpha calculated from a fraction. 412 * 413 * @param color 414 * a base color 415 * @param fraction 416 * A value where 0 gives the lowest alpha value and 1 gives the highest. Fractions 417 * above 1 are accepted and treated as 1. Negative fractions should not be used. 418 * @return a color with RGB from the base color and an alpha value depending on the fraction 419 */ 420 private static Color adjustAlpha(Color color, double fraction) { 421 return ColorToolkit.withAlpha(color, Math.min(200, (int) ((Math.max(0, fraction) + 0.15) * 255))); 422 } 423 424 public static ItemRow buildGcPauseRow(IItemCollection items) { 425 IItemCollection pauseEvents = items.apply(JdkFilters.GC_PAUSE); 426 ISpanSeries<RangePayload> gcBackdrop = rangeSeries(pauseEvents, JfrAttributes.START_TIME, 427 JfrAttributes.END_TIME); 428 return new ItemRow(SpanRenderer.build(gcBackdrop, GC_COLOR), pauseEvents); 429 } 430 431 public static ItemRow buildApplicationPauseRow(IItemCollection items) { 432 IItemFilter vmOperationPauseFilter = ItemFilters.and(JdkFilters.VM_OPERATIONS, 433 ItemFilters.equals(JdkAttributes.SAFEPOINT, true)); 434 IItemCollection applicationPauses = items 435 .apply(ItemFilters.or(JdkFilters.GC_PAUSE, JdkFilters.SAFE_POINTS, vmOperationPauseFilter)); 436 ISpanSeries<RangePayload> pausesSeries = rangeSeries(applicationPauses, JfrAttributes.START_TIME, 437 JfrAttributes.END_TIME); 438 return new ItemRow(SpanRenderer.build(pausesSeries, APPLICATION_PAUSE_COLOR), applicationPauses); 439 } 440 441 public static IXDataRenderer buildTimestampHistogramRenderer( 442 IItemCollection items, IAggregator<IQuantity, ?> aggregator, IAttribute<IQuantity> timestampAttribute, 443 Color color) { 444 IQuantitySeries<IQuantity[]> aggregatorSeries = BucketBuilder.aggregatorSeries(items, aggregator, 445 timestampAttribute); 446 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(aggregator).getDefaultUnit().quantity(0), 447 aggregator.getName(), aggregator.getDescription()); 448 renderer.addBarChart(aggregator.getName(), aggregatorSeries, color); 449 return renderer; 450 } 451 452 public static IXDataRenderer buildTimestampHistogramRenderer( 453 IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) { 454 return buildTimestampHistogramRenderer(items, aggregator, JfrAttributes.CENTER_TIME, color); 455 } 456 457 public static ItemRow buildTimestampHistogram( 458 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator, 459 IAttribute<IQuantity> timestampAttribute, Color color) { 460 return new ItemRow(title, description, 461 buildTimestampHistogramRenderer(items, aggregator, timestampAttribute, color), items); 462 } 463 464 public static ItemRow buildTimestampHistogram( 465 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> aggregator, Color color) { 466 return new ItemRow(title, description, buildTimestampHistogramRenderer(items, aggregator, color), items); 467 } 468 469 public static ItemHistogram createDistinctItemsTable( 470 Composite parent, IItemCollection items, IItemQuery query, TableSettings settings) { 471 CompositeKeyHistogramBuilder histogramBuilder = new CompositeKeyHistogramBuilder(); 472 for (IAttribute<?> attribute : query.getAttributes()) { 473 histogramBuilder.addKeyColumn(attribute); 474 } 475 ItemHistogram table = histogramBuilder.buildWithoutBorder(parent, settings); 476 return table; 477 } 478 479 public static IBaseLabelProvider createTableHighlightProvider(Pattern highlightPattern, boolean isWarning) { 480 return new StyledCellLabelProvider() { 481 @Override 482 public void update(ViewerCell cell) { 483 org.eclipse.swt.graphics.Color color = isWarning 484 ? new org.eclipse.swt.graphics.Color(Display.getCurrent(), 240, 120, 140) 485 : new org.eclipse.swt.graphics.Color(Display.getCurrent(), 255, 144, 4); 486 String text = getText(cell.getElement(), cell.getColumnIndex()); 487 Matcher matcher = highlightPattern.matcher(text); 488 if (matcher.find()) { 489 cell.getViewerRow().setBackground(0, color); 490 cell.getViewerRow().setBackground(1, color); 491 } 492 cell.setText(text); 493 super.update(cell); 494 } 495 496 private String getText(Object element, int index) { 497 Object key = AggregationGrid.getKey(element); 498 Object[] keyElements = ((CompositeKey) key).getKeyElements(); 499 return keyElements[index].toString(); 500 } 501 }; 502 } 503 504 public static void addContextMenus( 505 IPageContainer pc, ItemHistogram h, String selectionName, IAction ... extraActions) { 506 MCContextMenuManager mm = MCContextMenuManager.create(h.getManager().getViewer().getControl()); 507 ColumnMenusFactory.addDefaultMenus(h.getManager(), mm); 508 SelectionStoreActionToolkit.addSelectionStoreActions(pc.getSelectionStore(), h, selectionName, mm); 509 for (IAction action : extraActions) { 510 mm.add(action); 511 } 512 } 513 514 public static IXDataRenderer buildSizeRow( 515 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color, 516 IColorProvider<IItem> cp) { 517 return RendererToolkit.layers(buildSpanRenderer(items, cp), 518 buildTimestampHistogram(title, description, items, a, color)); 519 } 520 521 public static ItemRow buildDurationHistogram( 522 String title, String description, IItemCollection items, IAggregator<IQuantity, ?> a, Color color) { 523 IQuantitySeries<IQuantity[]> allocationSeries = BucketBuilder.aggregatorSeries(items, a, 524 JfrAttributes.DURATION); 525 XYDataRenderer renderer = new XYDataRenderer(getKindOfQuantity(a).getDefaultUnit().quantity(0), title, 526 description); 527 renderer.addBarChart(a.getName(), allocationSeries, color); 528 return new ItemRow(title, description, renderer, items); 529 } 530 531 // FIXME: Make something that can use something other than time as x-axis? 532 public static IXDataRenderer buildSpanRenderer(IItemCollection pathItems, IColorProvider<IItem> cp) { 533 ISpanSeries<IItem> dataSeries = QuantitySeries.max(pathItems, JfrAttributes.START_TIME, JfrAttributes.END_TIME); 534 return SpanRenderer.withBoundaries(dataSeries, cp); 535 } 536 537 public static boolean addEndTimeLines( 538 XYDataRenderer renderer, IItemCollection items, boolean fill, Stream<IAttribute<IQuantity>> yAttributes) { 539 // FIXME: JMC-4520 - Handle multiple item iterables 540 Iterator<IItemIterable> ii = items.iterator(); 541 if (ii.hasNext()) { 542 IItemIterable itemStream = ii.next(); 543 IType<IItem> type = itemStream.getType(); 544 // FIXME: A better way to ensure sorting by endTime 545 return yAttributes.peek(a -> addEndTimeLine(renderer, itemStream.iterator(), type, a, fill)) 546 .mapToLong(a -> 1L).sum() > 0; 547 } 548 return false; 549 } 550 551 public static void addEndTimeLine( 552 XYDataRenderer renderer, Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> yAttribute, 553 boolean fill) { 554 IQuantitySeries<?> qs = buildQuantitySeries(items, type, JfrAttributes.END_TIME, yAttribute); 555 renderer.addLineChart(yAttribute.getName(), qs, getFieldColor(yAttribute), fill); 556 } 557 558 public static IQuantitySeries<?> buildQuantitySeries( 559 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 560 IAttribute<IQuantity> yAttribute) { 561 IMemberAccessor<IQuantity, IItem> yAccessor = yAttribute.getAccessor(type); 562 if (yAccessor == null) { 563 throw new RuntimeException(yAttribute.getIdentifier() + " is not an attribute for " + type.getIdentifier()); //$NON-NLS-1$ 564 } 565 return buildQuantitySeries(items, type, xAttribute, yAccessor); 566 } 567 568 public static IQuantitySeries<?> buildQuantitySeries( 569 Iterator<? extends IItem> items, IType<IItem> type, IAttribute<IQuantity> xAttribute, 570 IMemberAccessor<? extends IQuantity, IItem> yAccessor) { 571 IMemberAccessor<IQuantity, IItem> xAccessor = xAttribute.getAccessor(type); 572 return QuantitySeries.all(items, xAccessor, yAccessor); 573 } 574 575 public static void createChartTooltip(ChartCanvas chart) { 576 createChartTooltip(chart, ChartToolTipProvider::new); 577 } 578 579 public static void createChartTimestampTooltip(ChartCanvas chart) { 580 createChartTooltip(chart, JfrAttributes.START_TIME, JfrAttributes.END_TIME, JfrAttributes.DURATION, 581 JfrAttributes.EVENT_TYPE, JfrAttributes.EVENT_STACKTRACE); 582 } 583 584 public static void createChartTooltip(ChartCanvas chart, IAttribute<?> ... excludedAttributes) { 585 createChartTooltip(chart, new HashSet<>(Arrays.asList(excludedAttributes))); 586 } 587 588 public static void createChartTooltip(ChartCanvas chart, Set<IAttribute<?>> excludedAttributes) { 589 createChartTooltip(chart, () -> new ChartToolTipProvider() { 590 @Override 591 protected Stream<IAttribute<?>> getAttributeStream(IType<IItem> type) { 592 return type.getAttributes().stream().filter(a -> !excludedAttributes.contains(a)); 593 } 594 }); 595 } 596 597 public static void createChartTooltip(ChartCanvas chart, Supplier<ChartToolTipProvider> toolTipProviderSupplier) { 598 new ToolTip(chart) { 599 String html; 600 Map<String, Image> images; 601 602 @Override 603 protected boolean shouldCreateToolTip(Event event) { 604 ChartToolTipProvider provider = toolTipProviderSupplier.get(); 605 chart.infoAt(provider, event.x, event.y); 606 html = provider.getHTML(); 607 images = provider.getImages(); 608 return html != null; 609 } 610 611 @Override 612 protected Composite createToolTipContentArea(Event event, Composite parent) { 613 FormText formText = CompositeToolkit.createInfoFormText(parent); 614 for (Map.Entry<String, Image> imgEntry : images.entrySet()) { 615 formText.setImage(imgEntry.getKey(), imgEntry.getValue()); 616 } 617 formText.setText(html, true, false); 618 return formText; 619 } 620 621 }; 622 623 } 624 625 private static KindOfQuantity<?> getKindOfQuantity(IAggregator<IQuantity, ?> a) { 626 IType<? super IQuantity> ct = a.getValueType(); 627 // FIXME: Refactor to avoid this cast 628 return ((KindOfQuantity<?>) ct); 629 } 630 631 public static Form createForm(Composite parent, FormToolkit toolkit, String title, Image img) { 632 Form form = toolkit.createForm(parent); 633 form.setText(title.replaceAll("&", "&&")); //$NON-NLS-1$ //$NON-NLS-2$ 634 form.setImage(img); 635 toolkit.decorateFormHeading(form); 636 FillLayout fillLayout = new FillLayout(); 637 fillLayout.marginHeight = 15; 638 fillLayout.marginWidth = 8; 639 form.getBody().setLayout(fillLayout); 640 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_RESULTS)); 641 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_SETUP)); 642 form.getToolBarManager().add(new Separator(FORM_TOOLBAR_PAGE_NAV)); 643 return form; 644 } 645 646 public static class ShowResultAction extends Action { 647 648 private String[] topics; 649 private final IPageContainer pageContainer; 650 private volatile Severity maxSeverity; 651 private final List<Consumer<Result>> listeners = new ArrayList<>(); 652 653 ShowResultAction(String title, int style, ImageDescriptor icon, Supplier<String> tooltip, 654 IPageContainer pageContainer, String ... topics) { 655 super(title, style); 656 setImageDescriptor(icon); 657 setToolTipText(tooltip.get()); 658 this.topics = topics; 659 this.pageContainer = pageContainer; 660 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 661 for (String topic : topics) { 662 Consumer<Result> listener = result -> { 663 Severity severity = Severity.get(result.getScore()); 664 if (severity.compareTo(maxSeverity) > 0) { 665 maxSeverity = severity; 666 setImageDescriptor(getResultIcon(maxSeverity)); 667 } else if (severity.compareTo(maxSeverity) < 0) { // severity could be less than previous max 668 maxSeverity = pageContainer.getRuleManager().getMaxSeverity(topics); 669 } 670 setToolTipText(tooltip.get()); 671 }; 672 listeners.add(listener); 673 pageContainer.getRuleManager().addResultListener(topic, listener); 674 } 675 } 676 677 private void removeListeners() { 678 listeners.forEach(l -> pageContainer.getRuleManager().removeResultListener(l)); 679 } 680 681 @Override 682 public void run() { 683 pageContainer.showResults(topics); 684 } 685 } 686 687 public static void addRuleResultAction( 688 Form form, IPageContainer pageContainer, Supplier<String> tooltip, String[] topics) { 689 if (topics == null || topics.length == 0 || !FlightRecorderUI.getDefault().isAnalysisEnabled()) { 690 return; 691 } 692 ImageDescriptor icon = getResultIcon(pageContainer.getRuleManager().getMaxSeverity(topics)); 693 ShowResultAction resultAction = new ShowResultAction(Messages.RULES_SHOW_RESULTS_ACTION, IAction.AS_PUSH_BUTTON, 694 icon, tooltip, pageContainer, topics); 695 resultAction.setId(RESULT_ACTION_ID); 696 form.getToolBarManager().appendToGroup(DataPageToolkit.FORM_TOOLBAR_PAGE_RESULTS, resultAction); 697 form.getToolBarManager().update(true); 698 form.addDisposeListener(e -> resultAction.removeListeners()); 699 } 700 701 private static ImageDescriptor getResultIcon(Severity severity) { 702 switch (severity) { 703 case OK: 704 return ResultOverview.ICON_OK; 705 case INFO: 706 return ResultOverview.ICON_INFO; 707 case WARNING: 708 return ResultOverview.ICON_WARNING; 709 case NA: 710 return ResultOverview.ICON_NA; 711 } 712 return null; 713 } 714 715 public static FilterEditor buildFilterSelector( 716 Composite parent, IItemFilter filter, IItemCollection items, Supplier<Stream<SelectionStoreEntry>> selections, 717 Consumer<IItemFilter> onSelect, boolean hasBorder) { 718 Supplier<Collection<IAttribute<?>>> attributeSupplier = () -> getPersistableAttributes( 719 getAttributes(filter != null ? items.apply(filter) : items)).collect(Collectors.toList()); 720 721 AttributeValueProvider valueSupplier = new AttributeValueProvider() { 722 @Override 723 public <V> V defaultValue(ICanonicalAccessorFactory<V> attribute) { 724 return findValueForFilter(items, attribute); 725 } 726 }; 727 728 FilterEditor editor = new FilterEditor(parent, onSelect, filter, attributeSupplier, valueSupplier, 729 TypeLabelProvider::getColorOrDefault, hasBorder ? SWT.BORDER : SWT.NONE); 730 731 MenuManager addFromSelectionPredicate = new MenuManager(Messages.FILTER_ADD_FROM_SELECTION); 732 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addFromSelectionPredicate); 733 addFromSelectionPredicate.setRemoveAllWhenShown(true); 734 addFromSelectionPredicate.addMenuListener(new IMenuListener() { 735 736 @Override 737 public void menuAboutToShow(IMenuManager manager) { 738 selections.get().forEach(entry -> { 739 MenuManager selectionFlavors = new MenuManager(entry.getName()); 740 entry.getSelection().getFlavors(editor.getFilter(), items, null) 741 .filter(f -> f instanceof IFilterFlavor).forEach(flavor -> { 742 selectionFlavors.add(new Action(flavor.getName()) { 743 @Override 744 public void run() { 745 editor.addRoot(((IFilterFlavor) flavor).getFilter()); 746 } 747 }); 748 }); 749 if (!selectionFlavors.isEmpty()) { 750 manager.add(selectionFlavors); 751 } 752 }); 753 } 754 }); 755 // FIXME: This could potentially move into the FilterEditor class 756 MenuManager addAttributeValuePredicate = new MenuManager(Messages.FILTER_ADD_FROM_ATTRIBUTE); 757 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addAttributeValuePredicate); 758 addAttributeValuePredicate.setRemoveAllWhenShown(true); 759 addAttributeValuePredicate.addMenuListener(new IMenuListener() { 760 Collection<IAttribute<?>> attributes; 761 762 @Override 763 public void menuAboutToShow(IMenuManager manager) { 764 if (attributes == null) { 765 attributes = attributeSupplier.get(); 766 } 767 attributes.stream().distinct().sorted((a1, a2) -> a1.getName().compareTo(a2.getName())) 768 .forEach(attr -> { 769 addAttributeValuePredicate.add(new Action(attr.getName()) { 770 @Override 771 public void run() { 772 IItemFilter filter = createDefaultFilter(items, attr); 773 editor.addRoot(filter); 774 } 775 }); 776 }); 777 } 778 }); 779 return editor; 780 } 781 782 // FIXME: Move to some AttributeToolkit? 783 private static Stream<IAttribute<?>> getAttributes(IItemCollection items) { 784 return ItemCollectionToolkit.stream(items).filter(IItemIterable::hasItems) 785 .flatMap(is -> is.getType().getAttributes().stream()); 786 } 787 788 public static Stream<IAttribute<?>> getPersistableAttributes(Stream<IAttribute<?>> attributes) { 789 // FIXME: Would like to always be able to persist a string representation of the attribute, because this is usable by filters. 790 791 // FIXME: Should we always include event type? Does it make any sense, except on the custom pages? 792 793 // FIXME: Transform both START_TIME and END_TIME to LIFETIME? 794 // FIXME: Add derived attributes, like a conversion of any THREAD or CLASS attribute? Thread group? 795 /* 796 * Make sure to do the conversions in the right order, so for example a stack trace can be 797 * converted to a top method, which then is converted to a method string. 798 */ 799 return attributes.map(a -> a.equals(JfrAttributes.EVENT_THREAD) ? JdkAttributes.EVENT_THREAD_NAME : a) 800 .flatMap(a -> a.equals(JfrAttributes.EVENT_STACKTRACE) ? Stream.of(JdkAttributes.STACK_TRACE_STRING, 801 JdkAttributes.STACK_TRACE_TOP_METHOD_STRING, JdkAttributes.STACK_TRACE_TOP_CLASS_STRING, 802 JdkAttributes.STACK_TRACE_TOP_PACKAGE, JdkAttributes.STACK_TRACE_BOTTOM_METHOD_STRING) 803 : Stream.of(a)) 804 .map(a -> a.equals(JdkAttributes.COMPILER_METHOD) ? JdkAttributes.COMPILER_METHOD_STRING : a) 805 // FIXME: String or id? 806 .map(a -> a.equals(JdkAttributes.REC_SETTING_FOR) ? JdkAttributes.REC_SETTING_FOR_NAME : a) 807 .map(a -> a.equals(JdkAttributes.CLASS_DEFINING_CLASSLOADER) 808 ? JdkAttributes.CLASS_DEFINING_CLASSLOADER_STRING : a) 809 .map(a -> a.equals(JdkAttributes.CLASS_INITIATING_CLASSLOADER) 810 ? JdkAttributes.CLASS_INITIATING_CLASSLOADER_STRING : a) 811 .filter(a -> a.equals(JfrAttributes.EVENT_TYPE) || (a.getContentType() instanceof RangeContentType) 812 || (a.getContentType().getPersister() != null)) 813 .distinct(); 814 } 815 816 /** 817 * Returns a value for attribute, firstly by trying to find one in the items, secondly by 818 * creating a default value for some known content types. Returns null if the first two cases 819 * fail. 820 * 821 * @param items 822 * @param attribute 823 * @return a value of type V, or null 824 */ 825 @SuppressWarnings("unchecked") 826 private static <V> V findValueForFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 827 IItem firstItem = ItemCollectionToolkit.stream(items).filter(is -> is.getType().hasAttribute(attribute)) 828 .flatMap(ItemIterableToolkit::stream) 829 .filter(i -> ((IMemberAccessor<V, IItem>) attribute.getAccessor(i.getType())).getMember(i) != null) 830 .findFirst().orElse(null); 831 if (firstItem != null) { 832 IMemberAccessor<V, IItem> accessor = (IMemberAccessor<V, IItem>) attribute.getAccessor(firstItem.getType()); 833 return accessor.getMember(firstItem); 834 } 835 if (UnitLookup.PLAIN_TEXT.equals(attribute.getContentType())) { 836 return (V) ""; //$NON-NLS-1$ 837 } 838 if (attribute.getContentType() instanceof KindOfQuantity<?>) { 839 return (V) ((KindOfQuantity<?>) attribute.getContentType()).getDefaultUnit().quantity(0); 840 } 841 return null; 842 } 843 844 /** 845 * Returns an default filter for attribute, which might be an equals filter, hasAttribute 846 * filter, or type filter, depending on the attribute and the contents of the items. 847 * 848 * @param items 849 * @param attribute 850 * @return a filter 851 */ 852 // FIXME: Should move to FilterEditor, or some subclass/specialization? 853 private static <V> IItemFilter createDefaultFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 854 V value = findValueForFilter(items, attribute); 855 if (value == null) { 856 return ItemFilters.hasAttribute(attribute); 857 } else if (attribute.equals(JfrAttributes.EVENT_TYPE)) { 858 return ItemFilters.type(((IType<?>) value).getIdentifier()); 859 } 860 return ItemFilters.equals(attribute, value); 861 } 862 863 public static void addRenameAction(Form form, IPageContainer editor) { 864 form.getMenuManager().add(new Action(Messages.PAGE_RENAME_MENU_ACTION) { 865 @Override 866 public void run() { 867 InputDialog dialog = new InputDialog(form.getShell(), Messages.PAGE_RENAME_DIALOG_TITLE, 868 Messages.PAGE_RENAME_DIALOG_MESSAGE, form.getText(), null); 869 if (dialog.open() == Window.OK) { 870 form.setText(dialog.getValue()); 871 editor.currentPageRefresh(); 872 } 873 } 874 }); 875 } 876 877 public static void addIconChangeAction(Form form, IPageContainer editor, Consumer<Image> newIconConsumer) { 878 form.getMenuManager().add(new Action(Messages.PAGE_CHANGE_ICON_MENU_ACTION) { 879 @Override 880 public void run() { 881 WizardDialog dialog = new WizardDialog(form.getShell(), 882 new IconChangeWizard(form.getImage(), newIconConsumer)); 883 dialog.open(); 884 editor.currentPageRefresh(); 885 } 886 }); 887 } 888 889 private static class IconChangeWizard extends Wizard { 890 891 private final Image currentImage; 892 private final Consumer<Image> imageConsumer; 893 private Label imageLabel; 894 895 public IconChangeWizard(Image currentImage, Consumer<Image> imageConsumer) { 896 setWindowTitle(Messages.PAGE_CHANGE_ICON_WIZARD_TITLE); 897 this.currentImage = currentImage; 898 this.imageConsumer = imageConsumer; 899 } 900 901 @Override 902 public void addPages() { 903 addPage(new WizardPage(Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE) { 904 905 @Override 906 public String getTitle() { 907 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE; 908 } 909 910 @Override 911 public String getDescription() { 912 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_DESC; 913 } 914 915 @Override 916 public void createControl(Composite parent) { 917 Composite container = new Composite(parent, SWT.NONE); 918 GridLayout layout = new GridLayout(1, false); 919 container.setLayout(layout); 920 921 Button button = new Button(container, SWT.NONE); 922 button.setText(Messages.PAGE_CHANGE_ICON_CHOOSE_IMAGE_FILE); 923 924 button.addSelectionListener(new SelectionAdapter() { 925 @Override 926 public void widgetSelected(SelectionEvent e) { 927 chooseImageFileDialog(); 928 } 929 }); 930 931 if (currentImage != null) { 932 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_CURRENT_ICON); 933 new Label(container, SWT.BORDER).setImage(currentImage); 934 } 935 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_NEW_ICON_PREVIEW); 936 imageLabel = new Label(container, SWT.BORDER); 937 GridData gd = new GridData(16, 16); 938 imageLabel.setLayoutData(gd); 939 940 setControl(container); 941 } 942 943 private void chooseImageFileDialog() { 944 FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); 945 String[] filterNames = new String[] {"Image Files", "All Files (*)"}; //$NON-NLS-1$ //$NON-NLS-2$ 946 String[] filterExtensions = new String[] {"*.gif;*.png;*.xpm;*.jpg;*.jpeg;*.tiff", "*"}; //$NON-NLS-1$ //$NON-NLS-2$ 947 fileDialog.setFilterNames(filterNames); 948 fileDialog.setFilterExtensions(filterExtensions); 949 String filename = fileDialog.open(); 950 if (filename == null) { 951 // Dialog was cancelled. Bail out early to avoid handling that case later. Premature? 952 return; 953 } 954 try (InputStream fis = new FileInputStream(filename)) { 955 ImageData imageData = new ImageData(fis); 956 // Validate image data 957 if (imageData.width != 16 || imageData.height != 16) { 958 imageData = resizeImage(imageData, 16, 16); 959 } 960 DisplayToolkit.dispose(imageLabel.getImage()); 961 imageLabel.setImage(new Image(getShell().getDisplay(), imageData)); 962 imageLabel.getParent().layout(); 963 setPageComplete(isPageComplete()); 964 } catch (Exception e) { 965 // FIXME: Add proper logging 966 e.printStackTrace(); 967 } 968 } 969 970 private ImageData resizeImage(ImageData imageData, int width, int height) { 971 Image original = ImageDescriptor.createFromImageData(imageData).createImage(); 972 Image scaled = new Image(Display.getDefault(), width, height); 973 GC gc = new GC(scaled); 974 gc.setAntialias(SWT.ON); 975 gc.setInterpolation(SWT.HIGH); 976 gc.drawImage(original, 0, 0, imageData.width, imageData.height, 0, 0, width, height); 977 gc.dispose(); 978 original.dispose(); 979 ImageData scaledData = scaled.getImageData(); 980 scaled.dispose(); 981 return scaledData; 982 } 983 984 @Override 985 public boolean isPageComplete() { 986 return imageLabel.getImage() != null; 987 } 988 989 }); 990 } 991 992 @Override 993 public boolean performFinish() { 994 imageConsumer.accept(imageLabel.getImage()); 995 DisplayToolkit.dispose(currentImage); 996 return true; 997 } 998 999 @Override 1000 public boolean performCancel() { 1001 DisplayToolkit.dispose(imageLabel.getImage()); 1002 return true; 1003 } 1004 1005 } 1006 1007 public static ItemList createSimpleItemList( 1008 Composite parent, ItemListBuilder listBuilder, IPageContainer pageContainer, TableSettings tableSettings, 1009 String selectionName) { 1010 1011 ItemList list = listBuilder.build(parent, tableSettings); 1012 ColumnViewer viewer = list.getManager().getViewer(); 1013 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); 1014 ColumnMenusFactory.addDefaultMenus(list.getManager(), mm); 1015 viewer.addSelectionChangedListener( 1016 e -> pageContainer.showSelection(ItemCollectionToolkit.build(list.getSelection().get()))); 1017 1018 if (selectionName != null) { 1019 SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), list, selectionName, 1020 mm); 1021 } 1022 1023 return list; 1024 } 1025 1026 public static void addTabItem(CTabFolder tabFolder, Control section, String name) { 1027 CTabItem tabItem = new CTabItem(tabFolder, SWT.NONE); 1028 tabItem.setControl(section); 1029 tabItem.setText(name); 1030 } 1031 1032 public static TypeFilterBuilder buildEventTypeTree( 1033 Composite parent, FormToolkit toolkit, Runnable onChange, boolean checkbox) { 1034 // TODO: Make more accessible. 1035 // TODO: Add support for storing the expansion state in a memento. 1036 // TODO: Add input from selection store, output to selection store 1037 // TODO: Add toolbar for choosing tree or checkbox tree. 1038 Composite treeComposite = new Composite(parent, SWT.NONE); 1039 treeComposite.setLayout(new GridLayout()); 1040 toolkit.adapt(treeComposite); 1041 Label caption = toolkit.createLabel(treeComposite, Messages.EVENT_TYPE_TREE_TITLE); 1042 caption.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); 1043 caption.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); 1044 1045 TypeFilterBuilder typeFilterTree = new TypeFilterBuilder(treeComposite, onChange, checkbox); 1046 1047 typeFilterTree.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 1048 return typeFilterTree; 1049 } 1050 1051 public static boolean isTypeWithThreadAndDuration(IType<?> type) { 1052 return JfrAttributes.EVENT_THREAD.getAccessor(type) != null 1053 && JfrAttributes.START_TIME.getAccessor(type) != JfrAttributes.END_TIME.getAccessor(type); 1054 } 1055 1056 }