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(":") 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 public static FilterEditor buildFilterSelector( 753 Composite parent, IItemFilter filter, IItemCollection items, Supplier<Stream<SelectionStoreEntry>> selections, 754 Consumer<IItemFilter> onSelect, boolean hasBorder) { 755 Supplier<Collection<IAttribute<?>>> attributeSupplier = () -> getPersistableAttributes( 756 getAttributes(filter != null ? items.apply(filter) : items)).collect(Collectors.toList()); 757 758 AttributeValueProvider valueSupplier = new AttributeValueProvider() { 759 @Override 760 public <V> V defaultValue(ICanonicalAccessorFactory<V> attribute) { 761 return findValueForFilter(items, attribute); 762 } 763 }; 764 765 FilterEditor editor = new FilterEditor(parent, onSelect, filter, attributeSupplier, valueSupplier, 766 TypeLabelProvider::getColorOrDefault, hasBorder ? SWT.BORDER : SWT.NONE); 767 768 MenuManager addFromSelectionPredicate = new MenuManager(Messages.FILTER_ADD_FROM_SELECTION); 769 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addFromSelectionPredicate); 770 addFromSelectionPredicate.setRemoveAllWhenShown(true); 771 addFromSelectionPredicate.addMenuListener(new IMenuListener() { 772 773 @Override 774 public void menuAboutToShow(IMenuManager manager) { 775 selections.get().forEach(entry -> { 776 MenuManager selectionFlavors = new MenuManager(entry.getName()); 777 entry.getSelection().getFlavors(editor.getFilter(), items, null) 778 .filter(f -> f instanceof IFilterFlavor).forEach(flavor -> { 779 selectionFlavors.add(new Action(flavor.getName()) { 780 @Override 781 public void run() { 782 editor.addRoot(((IFilterFlavor) flavor).getFilter()); 783 } 784 }); 785 }); 786 if (!selectionFlavors.isEmpty()) { 787 manager.add(selectionFlavors); 788 } 789 }); 790 } 791 }); 792 // FIXME: This could potentially move into the FilterEditor class 793 MenuManager addAttributeValuePredicate = new MenuManager(Messages.FILTER_ADD_FROM_ATTRIBUTE); 794 editor.getContextMenu().prependToGroup(MCContextMenuManager.GROUP_NEW, addAttributeValuePredicate); 795 addAttributeValuePredicate.setRemoveAllWhenShown(true); 796 addAttributeValuePredicate.addMenuListener(new IMenuListener() { 797 Collection<IAttribute<?>> attributes; 798 799 @Override 800 public void menuAboutToShow(IMenuManager manager) { 801 if (attributes == null) { 802 attributes = attributeSupplier.get(); 803 } 804 attributes.stream().distinct().sorted((a1, a2) -> a1.getName().compareTo(a2.getName())) 805 .forEach(attr -> { 806 addAttributeValuePredicate.add(new Action(attr.getName()) { 807 @Override 808 public void run() { 809 IItemFilter filter = createDefaultFilter(items, attr); 810 editor.addRoot(filter); 811 } 812 }); 813 }); 814 } 815 }); 816 return editor; 817 } 818 819 // FIXME: Move to some AttributeToolkit? 820 private static Stream<IAttribute<?>> getAttributes(IItemCollection items) { 821 return ItemCollectionToolkit.stream(items).filter(IItemIterable::hasItems) 822 .flatMap(is -> is.getType().getAttributes().stream()); 823 } 824 825 public static Stream<IAttribute<?>> getPersistableAttributes(Stream<IAttribute<?>> attributes) { 826 // FIXME: Would like to always be able to persist a string representation of the attribute, because this is usable by filters. 827 828 // FIXME: Should we always include event type? Does it make any sense, except on the custom pages? 829 830 // FIXME: Transform both START_TIME and END_TIME to LIFETIME? 831 // FIXME: Add derived attributes, like a conversion of any THREAD or CLASS attribute? Thread group? 832 /* 833 * Make sure to do the conversions in the right order, so for example a stack trace can be 834 * converted to a top method, which then is converted to a method string. 835 */ 836 return attributes.map(a -> a.equals(JfrAttributes.EVENT_THREAD) ? JdkAttributes.EVENT_THREAD_NAME : a) 837 .flatMap(a -> a.equals(JfrAttributes.EVENT_STACKTRACE) ? Stream.of(JdkAttributes.STACK_TRACE_STRING, 838 JdkAttributes.STACK_TRACE_TOP_METHOD_STRING, JdkAttributes.STACK_TRACE_TOP_CLASS_STRING, 839 JdkAttributes.STACK_TRACE_TOP_PACKAGE, JdkAttributes.STACK_TRACE_BOTTOM_METHOD_STRING) 840 : Stream.of(a)) 841 .map(a -> a.equals(JdkAttributes.COMPILER_METHOD) ? JdkAttributes.COMPILER_METHOD_STRING : a) 842 // FIXME: String or id? 843 .map(a -> a.equals(JdkAttributes.REC_SETTING_FOR) ? JdkAttributes.REC_SETTING_FOR_NAME : a) 844 .map(a -> a.equals(JdkAttributes.CLASS_DEFINING_CLASSLOADER) 845 ? JdkAttributes.CLASS_DEFINING_CLASSLOADER_STRING : a) 846 .map(a -> a.equals(JdkAttributes.CLASS_INITIATING_CLASSLOADER) 847 ? JdkAttributes.CLASS_INITIATING_CLASSLOADER_STRING : a) 848 .filter(a -> a.equals(JfrAttributes.EVENT_TYPE) || (a.getContentType() instanceof RangeContentType) 849 || (a.getContentType().getPersister() != null)) 850 .distinct(); 851 } 852 853 /** 854 * Returns a value for attribute, firstly by trying to find one in the items, secondly by 855 * creating a default value for some known content types. Returns null if the first two cases 856 * fail. 857 * 858 * @param items 859 * @param attribute 860 * @return a value of type V, or null 861 */ 862 @SuppressWarnings("unchecked") 863 private static <V> V findValueForFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 864 IItem firstItem = ItemCollectionToolkit.stream(items).filter(is -> is.getType().hasAttribute(attribute)) 865 .flatMap(ItemIterableToolkit::stream) 866 .filter(i -> ((IMemberAccessor<V, IItem>) attribute.getAccessor(i.getType())).getMember(i) != null) 867 .findFirst().orElse(null); 868 if (firstItem != null) { 869 IMemberAccessor<V, IItem> accessor = (IMemberAccessor<V, IItem>) attribute.getAccessor(firstItem.getType()); 870 return accessor.getMember(firstItem); 871 } 872 if (UnitLookup.PLAIN_TEXT.equals(attribute.getContentType())) { 873 return (V) ""; //$NON-NLS-1$ 874 } 875 if (attribute.getContentType() instanceof KindOfQuantity<?>) { 876 return (V) ((KindOfQuantity<?>) attribute.getContentType()).getDefaultUnit().quantity(0); 877 } 878 return null; 879 } 880 881 /** 882 * Returns an default filter for attribute, which might be an equals filter, hasAttribute 883 * filter, or type filter, depending on the attribute and the contents of the items. 884 * 885 * @param items 886 * @param attribute 887 * @return a filter 888 */ 889 // FIXME: Should move to FilterEditor, or some subclass/specialization? 890 private static <V> IItemFilter createDefaultFilter(IItemCollection items, ICanonicalAccessorFactory<V> attribute) { 891 V value = findValueForFilter(items, attribute); 892 if (value == null) { 893 return ItemFilters.hasAttribute(attribute); 894 } else if (attribute.equals(JfrAttributes.EVENT_TYPE)) { 895 return ItemFilters.type(((IType<?>) value).getIdentifier()); 896 } 897 return ItemFilters.equals(attribute, value); 898 } 899 900 public static void addRenameAction(Form form, IPageContainer editor) { 901 form.getMenuManager().add(new Action(Messages.PAGE_RENAME_MENU_ACTION) { 902 @Override 903 public void run() { 904 InputDialog dialog = new InputDialog(form.getShell(), Messages.PAGE_RENAME_DIALOG_TITLE, 905 Messages.PAGE_RENAME_DIALOG_MESSAGE, form.getText(), null); 906 if (dialog.open() == Window.OK) { 907 form.setText(dialog.getValue()); 908 editor.currentPageRefresh(); 909 } 910 } 911 }); 912 } 913 914 public static void addIconChangeAction(Form form, IPageContainer editor, Consumer<Image> newIconConsumer) { 915 form.getMenuManager().add(new Action(Messages.PAGE_CHANGE_ICON_MENU_ACTION) { 916 @Override 917 public void run() { 918 WizardDialog dialog = new WizardDialog(form.getShell(), 919 new IconChangeWizard(form.getImage(), newIconConsumer)); 920 dialog.open(); 921 editor.currentPageRefresh(); 922 } 923 }); 924 } 925 926 private static class IconChangeWizard extends Wizard { 927 928 private final Image currentImage; 929 private final Consumer<Image> imageConsumer; 930 private Label imageLabel; 931 932 public IconChangeWizard(Image currentImage, Consumer<Image> imageConsumer) { 933 setWindowTitle(Messages.PAGE_CHANGE_ICON_WIZARD_TITLE); 934 this.currentImage = currentImage; 935 this.imageConsumer = imageConsumer; 936 } 937 938 @Override 939 public void addPages() { 940 addPage(new WizardPage(Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE) { 941 942 @Override 943 public String getTitle() { 944 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_TITLE; 945 } 946 947 @Override 948 public String getDescription() { 949 return Messages.PAGE_CHANGE_ICON_WIZARD_PAGE_DESC; 950 } 951 952 @Override 953 public void createControl(Composite parent) { 954 Composite container = new Composite(parent, SWT.NONE); 955 GridLayout layout = new GridLayout(1, false); 956 container.setLayout(layout); 957 958 Button button = new Button(container, SWT.NONE); 959 button.setText(Messages.PAGE_CHANGE_ICON_CHOOSE_IMAGE_FILE); 960 961 button.addSelectionListener(new SelectionAdapter() { 962 @Override 963 public void widgetSelected(SelectionEvent e) { 964 chooseImageFileDialog(); 965 } 966 }); 967 968 if (currentImage != null) { 969 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_CURRENT_ICON); 970 new Label(container, SWT.BORDER).setImage(currentImage); 971 } 972 new Label(container, SWT.NONE).setText(Messages.PAGE_CHANGE_ICON_NEW_ICON_PREVIEW); 973 imageLabel = new Label(container, SWT.BORDER); 974 GridData gd = new GridData(16, 16); 975 imageLabel.setLayoutData(gd); 976 977 setControl(container); 978 } 979 980 private void chooseImageFileDialog() { 981 FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN); 982 String[] filterNames = new String[] {"Image Files", "All Files (*)"}; //$NON-NLS-1$ //$NON-NLS-2$ 983 String[] filterExtensions = new String[] {"*.gif;*.png;*.xpm;*.jpg;*.jpeg;*.tiff", "*"}; //$NON-NLS-1$ //$NON-NLS-2$ 984 fileDialog.setFilterNames(filterNames); 985 fileDialog.setFilterExtensions(filterExtensions); 986 String filename = fileDialog.open(); 987 if (filename == null) { 988 // Dialog was cancelled. Bail out early to avoid handling that case later. Premature? 989 return; 990 } 991 try (InputStream fis = new FileInputStream(filename)) { 992 ImageData imageData = new ImageData(fis); 993 // Validate image data 994 if (imageData.width != 16 || imageData.height != 16) { 995 imageData = resizeImage(imageData, 16, 16); 996 } 997 DisplayToolkit.dispose(imageLabel.getImage()); 998 imageLabel.setImage(new Image(getShell().getDisplay(), imageData)); 999 imageLabel.getParent().layout(); 1000 setPageComplete(isPageComplete()); 1001 } catch (Exception e) { 1002 // FIXME: Add proper logging 1003 e.printStackTrace(); 1004 } 1005 } 1006 1007 private ImageData resizeImage(ImageData imageData, int width, int height) { 1008 Image original = ImageDescriptor.createFromImageData(imageData).createImage(); 1009 Image scaled = new Image(Display.getDefault(), width, height); 1010 GC gc = new GC(scaled); 1011 gc.setAntialias(SWT.ON); 1012 gc.setInterpolation(SWT.HIGH); 1013 gc.drawImage(original, 0, 0, imageData.width, imageData.height, 0, 0, width, height); 1014 gc.dispose(); 1015 original.dispose(); 1016 ImageData scaledData = scaled.getImageData(); 1017 scaled.dispose(); 1018 return scaledData; 1019 } 1020 1021 @Override 1022 public boolean isPageComplete() { 1023 return imageLabel.getImage() != null; 1024 } 1025 1026 }); 1027 } 1028 1029 @Override 1030 public boolean performFinish() { 1031 imageConsumer.accept(imageLabel.getImage()); 1032 DisplayToolkit.dispose(currentImage); 1033 return true; 1034 } 1035 1036 @Override 1037 public boolean performCancel() { 1038 DisplayToolkit.dispose(imageLabel.getImage()); 1039 return true; 1040 } 1041 1042 } 1043 1044 public static ItemList createSimpleItemList( 1045 Composite parent, ItemListBuilder listBuilder, IPageContainer pageContainer, TableSettings tableSettings, 1046 String selectionName) { 1047 1048 ItemList list = listBuilder.build(parent, tableSettings); 1049 ColumnViewer viewer = list.getManager().getViewer(); 1050 MCContextMenuManager mm = MCContextMenuManager.create(viewer.getControl()); 1051 ColumnMenusFactory.addDefaultMenus(list.getManager(), mm); 1052 viewer.addSelectionChangedListener( 1053 e -> pageContainer.showSelection(ItemCollectionToolkit.build(list.getSelection().get()))); 1054 1055 if (selectionName != null) { 1056 SelectionStoreActionToolkit.addSelectionStoreActions(pageContainer.getSelectionStore(), list, selectionName, 1057 mm); 1058 } 1059 1060 return list; 1061 } 1062 1063 public static void addTabItem(CTabFolder tabFolder, Control section, String name) { 1064 CTabItem tabItem = new CTabItem(tabFolder, SWT.NONE); 1065 tabItem.setControl(section); 1066 tabItem.setText(name); 1067 } 1068 1069 public static TypeFilterBuilder buildEventTypeTree( 1070 Composite parent, FormToolkit toolkit, Runnable onChange, boolean checkbox) { 1071 // TODO: Make more accessible. 1072 // TODO: Add support for storing the expansion state in a memento. 1073 // TODO: Add input from selection store, output to selection store 1074 // TODO: Add toolbar for choosing tree or checkbox tree. 1075 Composite treeComposite = new Composite(parent, SWT.NONE); 1076 treeComposite.setLayout(new GridLayout()); 1077 toolkit.adapt(treeComposite); 1078 Label caption = toolkit.createLabel(treeComposite, Messages.EVENT_TYPE_TREE_TITLE); 1079 caption.setFont(JFaceResources.getFontRegistry().getBold(JFaceResources.DEFAULT_FONT)); 1080 caption.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); 1081 1082 TypeFilterBuilder typeFilterTree = new TypeFilterBuilder(treeComposite, onChange, checkbox); 1083 1084 typeFilterTree.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); 1085 return typeFilterTree; 1086 } 1087 1088 public static boolean isTypeWithThreadAndDuration(IType<?> type) { 1089 return JfrAttributes.EVENT_THREAD.getAccessor(type) != null 1090 && JfrAttributes.START_TIME.getAccessor(type) != JfrAttributes.END_TIME.getAccessor(type); 1091 } 1092 1093 }