< prev index next >

application/org.openjdk.jmc.ui/src/main/java/org/openjdk/jmc/ui/misc/ChartCanvas.java

Print this page

        

@@ -1,7 +1,8 @@
 /*
- * 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
  * v 1.0 as shown at http://oss.oracle.com/licenses/upl

@@ -30,31 +31,33 @@
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 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;
 import org.eclipse.swt.events.KeyListener;
 import org.eclipse.swt.events.MouseAdapter;
 import org.eclipse.swt.events.MouseEvent;
 import org.eclipse.swt.events.MouseMoveListener;
 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;
 import org.eclipse.swt.widgets.Canvas;
 import org.eclipse.swt.widgets.Composite;

@@ -70,21 +73,26 @@
 import org.openjdk.jmc.ui.charts.IXDataRenderer;
 import org.openjdk.jmc.ui.charts.XYChart;
 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<Rectangle2D> highlightRects;
         private Object hoveredItemData;
 
         private class Selector extends MouseAdapter implements MouseMoveListener, MouseTrackListener {
 
                 int selectionStartX = -1;
                 int selectionStartY = -1;
+                Point highlightSelectionStart;
+                Point highlightSelectionEnd;
+                Point lastSelection;
                 boolean selectionIsClick = false;
 
                 @Override
                 public void mouseDown(MouseEvent e) {
                         /*

@@ -101,15 +109,50 @@
                          * The code below instead relies on ignoring mouse down events when SWT.MOD4 is
                          * depressed. Since MOD4 is CTRL on OS X and 0 on all other current platforms, this
                          * 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();
+                                }
                         }
                 }
 
                 @Override
                 public void mouseMove(MouseEvent e) {

@@ -129,22 +172,29 @@
                         if (selectionIsClick && ((Math.abs(x - selectionStartX) > 3) || (Math.abs(y - selectionStartY) > 3))) {
                                 selectionIsClick = false;
                         }
                         if (!selectionIsClick) {
                                 select((int) (selectionStartX / xScale), (int) (x / xScale), (int) (selectionStartY / yScale),
-                                                (int) (y / yScale));
+                                                (int) (y / yScale), true);
                         }
                 }
 
                 @Override
                 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();
+                                        }
                                 }
                         }
                 }
 
                 @Override

@@ -162,21 +212,41 @@
                 @Override
                 public void mouseHover(MouseEvent e) {
                 }
         }
 
+        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();
                                 }
                                 awtNeedsRedraw = false;
                         }

@@ -285,10 +355,18 @@
                         case '-':
                                 zoom(-1);
                                 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;
                                 case SWT.ARROW_LEFT:
                                         pan(-10);

@@ -331,30 +409,45 @@
         private final double yScale = Display.getDefault().getDPI().y / Environment.getNormalDPI();
 
         private final AwtCanvas awtCanvas = new AwtCanvas();
         private boolean awtNeedsRedraw;
         private Runnable selectionListener;
+        private Runnable zoomToSelectionListener;
+        private Consumer<Boolean> zoomOnClickListener;
         private IPropertyChangeListener aaListener;
         private XYChart awtChart;
         private MCContextMenuManager chartMenu;
+        private ChartTextCanvas textCanvas;
 
         public ChartCanvas(Composite parent) {
                 super(parent, SWT.NO_BACKGROUND);
                 addPaintListener(new Painter());
                 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);
                 addDisposeListener(e -> UIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(aaListener));
                 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() {
                 if (chartMenu == null) {
                         chartMenu = MCContextMenuManager.create(this);

@@ -363,11 +456,14 @@
                 return chartMenu;
         }
 
         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);
+                        }
                 }
         }
 
         /**
          * Translates display coordinates into image coordinates for the chart.

@@ -376,11 +472,11 @@
          *            the provided x coordinate
          * @param y
          *            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);
         }
 

@@ -389,14 +485,25 @@
          *
          * @param x
          *            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;
         }
 
         public void setHoveredItemData(Object data) {

@@ -405,10 +512,15 @@
 
         public void resetHoveredItemData() {
                 this.hoveredItemData = null;
         }
 
+        public void syncHighlightedRectangles (List<Rectangle2D> newRects) {
+                highlightRects = newRects;
+                redraw();
+        }
+
         private void updateHighlightRects() {
                 List<Rectangle2D> newRects = new ArrayList<>();
                 infoAt(new IChartInfoVisitor.Adapter() {
                         @Override
                         public void visit(IBucket bucket) {

@@ -445,10 +557,13 @@
                         }
                 }, lastMouseX, lastMouseY);
                 // Attempt to reduce flicker by avoiding unnecessary updates.
                 if (!newRects.equals(highlightRects)) {
                         highlightRects = newRects;
+                        if (textCanvas != null) {
+                                textCanvas.syncHighlightedRectangles(highlightRects);
+                        }
                         redraw();
                 }
         }
 
         private void clearHighlightRects() {

@@ -483,13 +598,16 @@
                 if ((awtChart != null) && awtChart.zoom(x, zoomInSteps)) {
                         redrawChart();
                 }
         }
 
-        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();
                 }
         }
 
         private void toggleSelect(int x, int y) {
                 Point p = translateDisplayToImageCoordinates(x, y);

@@ -513,28 +631,38 @@
                                                 range[1] = (x1 instanceof IQuantity) ? (IQuantity) x1 : null;
                                         }
                                 }
                         }, 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();
                 }
         }
 
         public void setChart(XYChart awtChart) {
                 this.awtChart = awtChart;
                 notifyListener();
                 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);
                 notifyListener();
                 redrawChart();

@@ -542,16 +670,34 @@
 
         public void setSelectionListener(Runnable selectionListener) {
                 this.selectionListener = selectionListener;
         }
 
+        public void setZoomToSelectionListener(Runnable zoomListener) {
+                this.zoomToSelectionListener = zoomListener;
+        }
+
+        public void setZoomOnClickListener(Consumer<Boolean> 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) {
                         awtChart.infoAt(visitor, p.x, p.y);
                 }

@@ -562,6 +708,13 @@
          */
         public void redrawChart() {
                 awtNeedsRedraw = true;
                 redraw();
         }
+
+        private void redrawChartText() {
+                if (textCanvas != null) {
+                        textCanvas.redrawChartText();
+                }
+        }
+
 }
< prev index next >