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