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