1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2019, Red Hat Inc. All rights reserved. 4 * 5 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 6 * 7 * The contents of this file are subject to the terms of either the Universal Permissive License 8 * v 1.0 as shown at http://oss.oracle.com/licenses/upl 9 * 10 * or the following license: 11 * 12 * Redistribution and use in source and binary forms, with or without modification, are permitted 13 * provided that the following conditions are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions 16 * and the following disclaimer. 17 * 18 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 19 * conditions and the following disclaimer in the documentation and/or other materials provided with 20 * the distribution. 21 * 22 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 23 * endorse or promote products derived from this software without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 26 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 27 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 30 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 32 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 */ 34 package org.openjdk.jmc.ui.misc; 35 36 import java.awt.Graphics2D; 37 38 import org.eclipse.swt.SWT; 39 import org.eclipse.swt.custom.SashForm; 40 import org.eclipse.swt.events.MouseAdapter; 41 import org.eclipse.swt.events.MouseEvent; 42 import org.eclipse.swt.events.MouseMoveListener; 43 import org.eclipse.swt.events.PaintEvent; 44 import org.eclipse.swt.events.PaintListener; 45 import org.eclipse.swt.graphics.Point; 46 import org.eclipse.swt.graphics.Rectangle; 47 import org.eclipse.swt.widgets.Canvas; 48 import org.eclipse.swt.widgets.Composite; 49 50 import org.openjdk.jmc.common.unit.IQuantity; 51 import org.openjdk.jmc.common.unit.IRange; 52 import org.openjdk.jmc.ui.charts.AWTChartToolkit; 53 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange; 54 import org.openjdk.jmc.ui.charts.XYChart; 55 import org.openjdk.jmc.ui.misc.PatternFly.Palette; 56 57 public class TimelineCanvas extends Canvas { 58 private static final int RANGE_INDICATOR_HEIGHT = 10; 59 private static final int RANGE_INDICATOR_Y_OFFSET = 25; 60 private int x1; 61 private int x2; 62 private int xOffset; 63 private AwtCanvas awtCanvas; 64 private ChartCanvas chartCanvas; 65 private Graphics2D g2d; 66 private IRange<IQuantity> chartRange; 67 private Rectangle dragRect; 68 private Rectangle indicatorRect; 69 private Rectangle timelineRect; 70 private SashForm sashForm; 71 private SubdividedQuantityRange xTickRange; 72 private XYChart chart; 73 74 public TimelineCanvas(Composite parent, ChartCanvas chartCanvas, SashForm sashForm) { 75 super(parent, SWT.NONE); 76 this.chartCanvas = chartCanvas; 77 this.sashForm = sashForm; 78 awtCanvas = new AwtCanvas(); 79 addPaintListener(new TimelineCanvasPainter()); 80 DragDetector dragDetector = new DragDetector(); 81 addMouseListener(dragDetector); 82 addMouseMoveListener(dragDetector); 83 } 84 85 private int calculateXOffset() { 86 return sashForm.getChildren()[0].getSize().x + sashForm.getSashWidth(); 87 } 88 89 public void renderRangeIndicator(int x1, int x2) { 90 this.x1 = x1; 91 this.x2 = x2; 92 this.redraw(); 93 } 94 95 public void setXTickRange(SubdividedQuantityRange xTickRange) { 96 this.xTickRange = xTickRange; 97 } 98 99 public void setChart(XYChart chart) { 100 this.chart = chart; 101 chartRange = chart.getVisibleRange(); 102 } 103 104 private class TimelineCanvasPainter implements PaintListener { 105 106 @Override 107 public void paintControl(PaintEvent e) { 108 xOffset = chartCanvas.translateDisplayToImageXCoordinates(calculateXOffset()); 109 110 Rectangle rect = getClientArea(); 111 g2d = awtCanvas.getGraphics(rect.width, rect.height); 112 113 // Draw the background 114 Point adjusted = chartCanvas.translateDisplayToImageCoordinates(rect.width, rect.height); 115 g2d.setColor(Palette.PF_BLACK_100.getAWTColor()); 116 g2d.fillRect(0, 0, adjusted.x, adjusted.y); 117 118 // Draw the horizontal axis 119 if (xTickRange != null) { 120 g2d.setColor(Palette.PF_BLACK.getAWTColor()); 121 AWTChartToolkit.drawAxis(g2d, xTickRange, 0, false, 1, false, xOffset); 122 } 123 124 // Draw the range indicator 125 indicatorRect = dragRect != null ? dragRect : new Rectangle( 126 x1 + xOffset, chartCanvas.translateDisplayToImageYCoordinates(RANGE_INDICATOR_Y_OFFSET), 127 x2 - x1, chartCanvas.translateDisplayToImageYCoordinates(RANGE_INDICATOR_HEIGHT)); 128 dragRect = null; 129 g2d.setPaint(Palette.PF_ORANGE_400.getAWTColor()); 130 g2d.fillRect(indicatorRect.x, indicatorRect.y, indicatorRect.width, indicatorRect.height); 131 132 Point totalSize = sashForm.getChildren()[1].getSize(); 133 adjusted = chartCanvas.translateDisplayToImageCoordinates(totalSize.x, totalSize.y); 134 timelineRect = new Rectangle( 135 xOffset, chartCanvas.translateDisplayToImageYCoordinates(RANGE_INDICATOR_Y_OFFSET), 136 adjusted.x, chartCanvas.translateDisplayToImageYCoordinates(RANGE_INDICATOR_HEIGHT)); 137 g2d.setPaint(Palette.PF_BLACK_600.getAWTColor()); 138 g2d.drawRect(timelineRect.x, timelineRect.y, timelineRect.width, timelineRect.height); 139 140 awtCanvas.paint(e, 0, 0); 141 } 142 } 143 144 private class DragDetector extends MouseAdapter implements MouseMoveListener { 145 146 boolean isDrag = false; 147 Point currentSelection; 148 Point lastSelection; 149 150 @Override 151 public void mouseDown(MouseEvent e) { 152 e.x = chartCanvas.translateDisplayToImageXCoordinates(e.x); 153 e.y = chartCanvas.translateDisplayToImageYCoordinates(e.y); 154 if (isDrag || e.button == 1 && timelineRect.contains(e.x, e.y)) { 155 isDrag = true; 156 currentSelection = new Point(e.x, e.y); 157 } 158 } 159 160 @Override 161 public void mouseUp(MouseEvent e) { 162 isDrag = false; 163 chart.setIsZoomPanDrag(false); 164 } 165 166 @Override 167 public void mouseMove(MouseEvent e) { 168 e.x = chartCanvas.translateDisplayToImageXCoordinates(e.x); 169 e.y = chartCanvas.translateDisplayToImageYCoordinates(e.y); 170 if (timelineRect.contains(e.x, e.y)) { 171 setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND)); 172 } else { 173 setCursor(getDisplay().getSystemCursor(SWT.CURSOR_ARROW)); 174 } 175 if (isDrag) { 176 lastSelection = currentSelection; 177 chart.setIsZoomPanDrag(true); 178 currentSelection = new Point(e.x, e.y); 179 int xdiff = currentSelection.x - lastSelection.x; 180 updateTimelineIndicatorFromDrag(xdiff); 181 } 182 } 183 184 private void updateTimelineIndicatorFromDrag(int xdiff) { 185 if (xdiff != 0 && 186 (indicatorRect.x + xdiff) >= timelineRect.x && 187 (indicatorRect.x + xdiff + indicatorRect.width) <= timelineRect.x + timelineRect.width) { 188 indicatorRect.x = indicatorRect.x + xdiff; 189 SubdividedQuantityRange xAxis = new SubdividedQuantityRange(chartRange.getStart(), chartRange.getEnd(), timelineRect.width, 1); 190 chart.setVisibleRange(xAxis.getQuantityAtPixel(indicatorRect.x - xOffset), 191 xAxis.getQuantityAtPixel(indicatorRect.x - xOffset + indicatorRect.width)); 192 dragRect = indicatorRect; 193 chartCanvas.redrawChart(); 194 } 195 } 196 } 197 }