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 }