--- old/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ChartCanvas.java 2019-10-22 09:27:20.566137089 -0400 +++ new/application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ChartCanvas.java 2019-10-22 09:27:20.403134760 -0400 @@ -1,6 +1,7 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. - * + * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, Red Hat Inc. All rights reserved. + * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The contents of this file are subject to the terms of either the Universal Permissive License @@ -10,17 +11,17 @@ * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. - * + * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided with * the distribution. - * + * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to * endorse or promote products derived from this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR @@ -32,17 +33,18 @@ */ package org.openjdk.jmc.ui.misc; -import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyEvent; @@ -53,6 +55,7 @@ import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; @@ -72,8 +75,10 @@ import org.openjdk.jmc.ui.common.util.Environment; import org.openjdk.jmc.ui.common.util.Environment.OSType; import org.openjdk.jmc.ui.handlers.MCContextMenuManager; +import org.openjdk.jmc.ui.misc.PatternFly.Palette; public class ChartCanvas extends Canvas { + private static int MIN_LANE_HEIGHT = 50; private int lastMouseX = -1; private int lastMouseY = -1; private List highlightRects; @@ -83,6 +88,9 @@ int selectionStartX = -1; int selectionStartY = -1; + Point highlightSelectionStart; + Point highlightSelectionEnd; + Point lastSelection; boolean selectionIsClick = false; @Override @@ -103,11 +111,46 @@ * suffices. Except for an additional platform check, this approach is also used in * org.eclipse.swt.custom.StyledText.handleMouseDown(Event). */ - if ((e.button == 1) && ((e.stateMask & SWT.MOD4) == 0)) { + if ((e.button == 1) && ((e.stateMask & SWT.MOD4) == 0) && ((e.stateMask & SWT.CTRL) == 0 ) && ((e.stateMask & SWT.SHIFT) == 0 )) { selectionStartX = e.x; selectionStartY = e.y; + highlightSelectionEnd = new Point(-1, -1); + lastSelection = new Point(-1, -1); selectionIsClick = true; toggleSelect(selectionStartX, selectionStartY); + } else if (((e.stateMask & SWT.CTRL) != 0) && (e.button == 1)) { + select(e.x, e.x, e.y, e.y, false); + if (selectionListener != null) { + selectionListener.run(); + } + } else if (((e.stateMask & SWT.SHIFT) != 0) && (e.button == 1)) { + if (highlightSelectionEnd.y == -1) { + highlightSelectionEnd = new Point(e.x, e.y); + lastSelection = highlightSelectionEnd; + if (highlightSelectionStart.y > highlightSelectionEnd.y) { + Point temp = highlightSelectionStart; + highlightSelectionStart = highlightSelectionEnd; + highlightSelectionEnd = temp; + } + } else { + if (e.y > highlightSelectionStart.y && e.y < highlightSelectionEnd.y) { + if (e.y < lastSelection.y) { + highlightSelectionEnd = new Point(e.x, e.y); + } else if (e.y > lastSelection.y) { + highlightSelectionStart = new Point(e.x, e.y); + } + } else if (e.y < highlightSelectionStart.y) { + highlightSelectionStart = new Point(e.x, e.y); + lastSelection = highlightSelectionStart; + } else if (e.y > highlightSelectionEnd.y) { + highlightSelectionEnd = new Point(e.x, e.y); + lastSelection = highlightSelectionEnd; + } + } + select(highlightSelectionStart.x, highlightSelectionEnd.x, highlightSelectionStart.y, highlightSelectionEnd.y, true); + if (selectionListener != null) { + selectionListener.run(); + } } } @@ -131,7 +174,7 @@ } if (!selectionIsClick) { select((int) (selectionStartX / xScale), (int) (x / xScale), (int) (selectionStartY / yScale), - (int) (y / yScale)); + (int) (y / yScale), true); } } @@ -139,10 +182,17 @@ public void mouseUp(MouseEvent e) { if (selectionStartX >= 0 && (e.button == 1)) { updateSelectionState(e); + highlightSelectionStart = new Point(selectionStartX, selectionStartY); selectionStartX = -1; selectionStartY = -1; + if (selectionIsClick) { + notifyZoomOnClickListener(e.button); + } if (selectionListener != null) { selectionListener.run(); + if (zoomToSelectionListener != null && !selectionIsClick) { + zoomToSelectionListener.run(); + } } } } @@ -164,17 +214,37 @@ } } + private int numItems = 0; + public void setNumItems(int numItems) { + this.numItems = numItems; + } + + private int getNumItems() { + return numItems; + } + class Painter implements PaintListener { @Override public void paintControl(PaintEvent e) { - Rectangle rect = getClientArea(); + Rectangle rect = new Rectangle(0, 0, getParent().getSize().x, getParent().getSize().y); + if (getNumItems() == 0) { + rect = getClientArea(); + } else if (getNumItems() == 1 || (MIN_LANE_HEIGHT * getNumItems() < rect.height)) { + // it fills the height + } else { + rect.height = MIN_LANE_HEIGHT * getNumItems(); + } + if (awtNeedsRedraw || !awtCanvas.hasImage(rect.width, rect.height)) { Graphics2D g2d = awtCanvas.getGraphics(rect.width, rect.height); - g2d.setColor(Color.WHITE); - g2d.fillRect(0, 0, rect.width, rect.height); Point adjusted = translateDisplayToImageCoordinates(rect.width, rect.height); + g2d.setColor(Palette.PF_BLACK_100.getAWTColor()); + g2d.fillRect(0, 0, adjusted.x, adjusted.y); render(g2d, adjusted.x, adjusted.y); + if (getParent() instanceof ScrolledComposite) { + ((ScrolledComposite) getParent()).setMinSize(rect.width, rect.height); + } if (highlightRects != null) { updateHighlightRects(); } @@ -287,6 +357,14 @@ break; default: switch (event.keyCode) { + case SWT.ESC: + awtChart.clearSelection(); + if (selectionListener != null) { + selectionListener.run(); + } + redrawChart(); + redrawChartText(); + break; case SWT.ARROW_RIGHT: pan(10); break; @@ -333,9 +411,12 @@ private final AwtCanvas awtCanvas = new AwtCanvas(); private boolean awtNeedsRedraw; private Runnable selectionListener; + private Runnable zoomToSelectionListener; + private Consumer zoomOnClickListener; private IPropertyChangeListener aaListener; private XYChart awtChart; private MCContextMenuManager chartMenu; + private ChartTextCanvas textCanvas; public ChartCanvas(Composite parent) { super(parent, SWT.NO_BACKGROUND); @@ -343,9 +424,7 @@ Selector selector = new Selector(); addMouseListener(selector); addMouseMoveListener(selector); - addMouseTrackListener(selector); FocusTracker.enableFocusTracking(this); - addListener(SWT.MouseVerticalWheel, new Zoomer()); addKeyListener(new KeyNavigator()); aaListener = new AntiAliasingListener(); UIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(aaListener); @@ -353,6 +432,20 @@ if (Environment.getOSType() == OSType.WINDOWS) { addMouseTrackListener(new WheelStealingZoomer()); } + if (getParent() instanceof ScrolledComposite) { // JFR Threads Page + ((ScrolledComposite) getParent()).getVerticalBar() + .addListener(SWT.Selection, e -> vBarScroll()); + } else { + addMouseTrackListener(selector); + addListener(SWT.MouseVerticalWheel, new Zoomer()); + } + } + + private void vBarScroll() { + if (textCanvas != null) { + Point location = ((ScrolledComposite) getParent()).getOrigin(); + textCanvas.syncScroll(location); + } } public IMenuManager getContextMenu() { @@ -365,7 +458,10 @@ private void render(Graphics2D context, int width, int height) { if (awtChart != null) { - awtChart.render(context, width, height); + awtChart.renderChart(context, width, height); + if (textCanvas == null) { + awtChart.renderText(context, width, height); + } } } @@ -378,7 +474,7 @@ * the provided y coordinate * @return a Point that represents the (x,y) coordinates in the chart's coordinate space */ - private Point translateDisplayToImageCoordinates(int x, int y) { + protected Point translateDisplayToImageCoordinates(int x, int y) { int xImage = (int) Math.round(x / xScale); int yImage = (int) Math.round(y / yScale); return new Point(xImage, yImage); @@ -391,10 +487,21 @@ * the provided display x coordinate * @return the x coordinate in the chart's coordinate space */ - private int translateDisplayToImageXCoordinates(int x) { + protected int translateDisplayToImageXCoordinates(int x) { return (int) Math.round(x / xScale); } + /** + * Translates a display x coordinate into an image x coordinate for the chart. + * + * @param x + * the provided display x coordinate + * @return the x coordinate in the chart's coordinate space + */ + protected int translateDisplayToImageYCoordinates(int y) { + return (int) Math.round(y / yScale); + } + public Object getHoveredItemData() { return this.hoveredItemData; } @@ -407,6 +514,11 @@ this.hoveredItemData = null; } + public void syncHighlightedRectangles (List newRects) { + highlightRects = newRects; + redraw(); + } + private void updateHighlightRects() { List newRects = new ArrayList<>(); infoAt(new IChartInfoVisitor.Adapter() { @@ -447,6 +559,9 @@ // Attempt to reduce flicker by avoiding unnecessary updates. if (!newRects.equals(highlightRects)) { highlightRects = newRects; + if (textCanvas != null) { + textCanvas.syncHighlightedRectangles(highlightRects); + } redraw(); } } @@ -485,9 +600,12 @@ } } - private void select(int x1, int x2, int y1, int y2) { - if ((awtChart != null) && awtChart.select(x1, x2, y1, y2)) { + private void select(int x1, int x2, int y1, int y2, boolean clear) { + Point p1 = translateDisplayToImageCoordinates(x1, y1); + Point p2 = translateDisplayToImageCoordinates(x2, y2); + if ((awtChart != null) && awtChart.select(p1.x, p2.x, p1.y, p2.y, clear)) { redrawChart(); + redrawChartText(); } } @@ -515,15 +633,17 @@ } }, x, y); if ((range[0] != null) || (range[1] != null)) { - if (!awtChart.select(range[0], range[1], p.y, p.y)) { + if (!awtChart.select(range[0], range[1], p.y, p.y, true)) { awtChart.clearSelection(); } } else { - if (!awtChart.select(p.x, p.x, p.y, p.y)) { + if (!awtChart.select(p.x, p.x, p.y, p.y, true)) { awtChart.clearSelection(); } } + notifyZoomOnClickListener(SWT.MouseDown); redrawChart(); + redrawChartText(); } } @@ -533,6 +653,14 @@ redrawChart(); } + public void setTextCanvas(ChartTextCanvas textCanvas) { + this.textCanvas = textCanvas; + } + + public void syncScroll(Point scrollPoint) { + ((ScrolledComposite) getParent()).setOrigin(scrollPoint); + } + public void replaceRenderer(IXDataRenderer rendererRoot) { assert awtChart != null; awtChart.setRendererRoot(rendererRoot); @@ -544,12 +672,30 @@ this.selectionListener = selectionListener; } + public void setZoomToSelectionListener(Runnable zoomListener) { + this.zoomToSelectionListener = zoomListener; + } + + public void setZoomOnClickListener(Consumer clickListener) { + this.zoomOnClickListener = clickListener; + } + + private void notifyZoomOnClickListener(Integer button) { + if (zoomOnClickListener != null) { + zoomOnClickListener.accept(button == SWT.MouseDown); + } + } + private void notifyListener() { if (selectionListener != null) { selectionListener.run(); } } + public void changeCursor(Cursor cursor) { + setCursor(cursor); + } + public void infoAt(IChartInfoVisitor visitor, int x, int y) { Point p = translateDisplayToImageCoordinates(x, y); if (awtChart != null) { @@ -564,4 +710,11 @@ awtNeedsRedraw = true; redraw(); } + + private void redrawChartText() { + if (textCanvas != null) { + textCanvas.redrawChartText(); + } + } + }