1 /* 2 * Copyright (c) 2018, 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 import java.awt.geom.Point2D; 38 import java.awt.geom.Rectangle2D; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.function.Consumer; 42 43 import org.eclipse.jface.action.IMenuManager; 44 import org.eclipse.jface.util.IPropertyChangeListener; 45 import org.eclipse.jface.util.PropertyChangeEvent; 46 import org.eclipse.swt.SWT; 47 import org.eclipse.swt.custom.ScrolledComposite; 48 import org.eclipse.swt.events.FocusEvent; 49 import org.eclipse.swt.events.FocusListener; 50 import org.eclipse.swt.events.KeyEvent; 51 import org.eclipse.swt.events.KeyListener; 52 import org.eclipse.swt.events.MouseAdapter; 53 import org.eclipse.swt.events.MouseEvent; 54 import org.eclipse.swt.events.MouseMoveListener; 55 import org.eclipse.swt.events.MouseTrackListener; 56 import org.eclipse.swt.events.PaintEvent; 57 import org.eclipse.swt.events.PaintListener; 58 import org.eclipse.swt.graphics.Cursor; 59 import org.eclipse.swt.graphics.GC; 60 import org.eclipse.swt.graphics.Point; 61 import org.eclipse.swt.graphics.Rectangle; 62 import org.eclipse.swt.widgets.Canvas; 63 import org.eclipse.swt.widgets.Composite; 64 import org.eclipse.swt.widgets.Control; 65 import org.eclipse.swt.widgets.Display; 66 import org.eclipse.swt.widgets.Event; 67 import org.eclipse.swt.widgets.Listener; 68 import org.openjdk.jmc.common.IDisplayable; 69 import org.openjdk.jmc.common.unit.IQuantity; 70 import org.openjdk.jmc.ui.UIPlugin; 71 import org.openjdk.jmc.ui.accessibility.FocusTracker; 72 import org.openjdk.jmc.ui.charts.IChartInfoVisitor; 73 import org.openjdk.jmc.ui.charts.IXDataRenderer; 74 import org.openjdk.jmc.ui.charts.XYChart; 75 import org.openjdk.jmc.ui.common.util.Environment; 76 import org.openjdk.jmc.ui.common.util.Environment.OSType; 77 import org.openjdk.jmc.ui.handlers.MCContextMenuManager; 78 import org.openjdk.jmc.ui.misc.PatternFly.Palette; 79 80 public class ChartCanvas extends Canvas { 81 private static int MIN_LANE_HEIGHT = 50; 82 private int lastMouseX = -1; 83 private int lastMouseY = -1; 84 private List<Rectangle2D> highlightRects; 85 private Object hoveredItemData; 86 87 private class Selector extends MouseAdapter implements MouseMoveListener, MouseTrackListener { 88 89 int selectionStartX = -1; 90 int selectionStartY = -1; 91 Point highlightSelectionStart; 92 Point highlightSelectionEnd; 93 Point lastSelection; 94 boolean selectionIsClick = false; 95 96 @Override 97 public void mouseDown(MouseEvent e) { 98 /* 99 * On Mac OS X, CTRL + left mouse button can be used to trigger a context menu. (This is 100 * for historical reasons when the primary input device on Macs were a mouse with a 101 * single physical button. All modern Macs have other means to bring up the context 102 * menu, typically a two finger tap.) 103 * 104 * Although I think it would be best to check that this MouseEvent does not cause a 105 * platform specific popup trigger, like java.awt.event.MouseEvent.isPopupTrigger() for 106 * AWT, SWT doesn't seem to have something as simple. It has the MenuDetectEvent, but 107 * the order in relation to this MouseEvent is unspecified. 108 * 109 * The code below instead relies on ignoring mouse down events when SWT.MOD4 is 110 * depressed. Since MOD4 is CTRL on OS X and 0 on all other current platforms, this 111 * suffices. Except for an additional platform check, this approach is also used in 112 * org.eclipse.swt.custom.StyledText.handleMouseDown(Event). 113 */ 114 if ((e.button == 1) && ((e.stateMask & SWT.MOD4) == 0) && ((e.stateMask & SWT.CTRL) == 0 ) && ((e.stateMask & SWT.SHIFT) == 0 )) { 115 selectionStartX = e.x; 116 selectionStartY = e.y; 117 highlightSelectionEnd = new Point(-1, -1); 118 lastSelection = new Point(-1, -1); 119 selectionIsClick = true; 120 toggleSelect(selectionStartX, selectionStartY); 121 } else if (((e.stateMask & SWT.CTRL) != 0) && (e.button == 1)) { 122 select(e.x, e.x, e.y, e.y, false); 123 if (selectionListener != null) { 124 selectionListener.run(); 125 } 126 } else if (((e.stateMask & SWT.SHIFT) != 0) && (e.button == 1)) { 127 if (highlightSelectionEnd.y == -1) { 128 highlightSelectionEnd = new Point(e.x, e.y); 129 lastSelection = highlightSelectionEnd; 130 if (highlightSelectionStart.y > highlightSelectionEnd.y) { 131 Point temp = highlightSelectionStart; 132 highlightSelectionStart = highlightSelectionEnd; 133 highlightSelectionEnd = temp; 134 } 135 } else { 136 if (e.y > highlightSelectionStart.y && e.y < highlightSelectionEnd.y) { 137 if (e.y < lastSelection.y) { 138 highlightSelectionEnd = new Point(e.x, e.y); 139 } else if (e.y > lastSelection.y) { 140 highlightSelectionStart = new Point(e.x, e.y); 141 } 142 } else if (e.y < highlightSelectionStart.y) { 143 highlightSelectionStart = new Point(e.x, e.y); 144 lastSelection = highlightSelectionStart; 145 } else if (e.y > highlightSelectionEnd.y) { 146 highlightSelectionEnd = new Point(e.x, e.y); 147 lastSelection = highlightSelectionEnd; 148 } 149 } 150 select(highlightSelectionStart.x, highlightSelectionEnd.x, highlightSelectionStart.y, highlightSelectionEnd.y, true); 151 if (selectionListener != null) { 152 selectionListener.run(); 153 } 154 } 155 } 156 157 @Override 158 public void mouseMove(MouseEvent e) { 159 if (selectionStartX >= 0) { 160 highlightRects = null; 161 updateSelectionState(e); 162 } else { 163 lastMouseX = e.x; 164 lastMouseY = e.y; 165 updateHighlightRects(); 166 } 167 } 168 169 private void updateSelectionState(MouseEvent e) { 170 int x = e.x; 171 int y = e.y; 172 if (selectionIsClick && ((Math.abs(x - selectionStartX) > 3) || (Math.abs(y - selectionStartY) > 3))) { 173 selectionIsClick = false; 174 } 175 if (!selectionIsClick) { 176 select((int) (selectionStartX / xScale), (int) (x / xScale), (int) (selectionStartY / yScale), 177 (int) (y / yScale), true); 178 } 179 } 180 181 @Override 182 public void mouseUp(MouseEvent e) { 183 if (selectionStartX >= 0 && (e.button == 1)) { 184 updateSelectionState(e); 185 highlightSelectionStart = new Point(selectionStartX, selectionStartY); 186 selectionStartX = -1; 187 selectionStartY = -1; 188 if (selectionIsClick) { 189 notifyZoomOnClickListener(e.button); 190 } 191 if (selectionListener != null) { 192 selectionListener.run(); 193 if (zoomToSelectionListener != null && !selectionIsClick) { 194 zoomToSelectionListener.run(); 195 } 196 } 197 } 198 } 199 200 @Override 201 public void mouseEnter(MouseEvent e) { 202 } 203 204 @Override 205 public void mouseExit(MouseEvent e) { 206 if (!getClientArea().contains(e.x, e.y)) { 207 resetHoveredItemData(); 208 } 209 clearHighlightRects(); 210 } 211 212 @Override 213 public void mouseHover(MouseEvent e) { 214 } 215 } 216 217 private int numItems = 0; 218 public void setNumItems(int numItems) { 219 this.numItems = numItems; 220 } 221 222 private int getNumItems() { 223 return numItems; 224 } 225 226 class Painter implements PaintListener { 227 228 @Override 229 public void paintControl(PaintEvent e) { 230 Rectangle rect = new Rectangle(0, 0, getParent().getSize().x, getParent().getSize().y); 231 if (getNumItems() == 0) { 232 rect = getClientArea(); 233 } else if (getNumItems() == 1 || (MIN_LANE_HEIGHT * getNumItems() < rect.height)) { 234 // it fills the height 235 } else { 236 rect.height = MIN_LANE_HEIGHT * getNumItems(); 237 } 238 239 if (awtNeedsRedraw || !awtCanvas.hasImage(rect.width, rect.height)) { 240 Graphics2D g2d = awtCanvas.getGraphics(rect.width, rect.height); 241 Point adjusted = translateDisplayToImageCoordinates(rect.width, rect.height); 242 g2d.setColor(Palette.PF_BLACK_100.getAWTColor()); 243 g2d.fillRect(0, 0, adjusted.x, adjusted.y); 244 render(g2d, adjusted.x, adjusted.y); 245 if (getParent() instanceof ScrolledComposite) { 246 ((ScrolledComposite) getParent()).setMinSize(rect.width, rect.height); 247 } 248 if (highlightRects != null) { 249 updateHighlightRects(); 250 } 251 awtNeedsRedraw = false; 252 } 253 awtCanvas.paint(e, 0, 0); 254 // Crude, flickering highlight of areas also delivered to tooltips. 255 // FIXME: Remove flicker by drawing in a buffered stage (AWT or SWT). 256 List<Rectangle2D> rs = highlightRects; 257 if (rs != null) { 258 GC gc = e.gc; 259 gc.setForeground(getForeground()); 260 for (Rectangle2D r : rs) { 261 int x = (int) (((int) r.getX()) * xScale); 262 int y = (int) (((int) r.getY()) * yScale); 263 if ((r.getWidth() == 0) && (r.getHeight() == 0)) { 264 int width = (int) Math.round(4 * xScale); 265 int height = (int) Math.round(4 * yScale); 266 gc.drawOval(x - (int) Math.round(2 * xScale), y - (int) Math.round(2 * yScale), width, height); 267 } else { 268 int width = (int) Math.round(r.getWidth() * xScale); 269 int height = (int) Math.round(r.getHeight() * yScale); 270 gc.drawRectangle(x, y, width, height); 271 } 272 } 273 } 274 } 275 } 276 277 class Zoomer implements Listener { 278 279 @Override 280 public void handleEvent(Event event) { 281 handleWheelEvent(event.stateMask, event.x, event.count); 282 } 283 284 } 285 286 /** 287 * Steals the wheel events from the currently focused control while hovering over this 288 * (ChartCanvas) control. Used on Windows to allow zooming without having to click in the chart 289 * first as click causes a selection. 290 */ 291 class WheelStealingZoomer implements Listener, MouseTrackListener, FocusListener { 292 293 private Control stealWheelFrom; 294 295 @Override 296 public void handleEvent(Event event) { 297 if (isDisposed()) { 298 stop(); 299 } else if (stealWheelFrom != null && !stealWheelFrom.isDisposed()) { 300 Point canvasSize = getSize(); 301 Point canvasPoint = toControl(stealWheelFrom.toDisplay(event.x, event.y)); 302 if (canvasPoint.x >= 0 && canvasPoint.y >= 0 && canvasPoint.x < canvasSize.x 303 && canvasPoint.y < canvasSize.y) { 304 handleWheelEvent(event.stateMask, canvasPoint.x, event.count); 305 event.doit = false; 306 } 307 } 308 } 309 310 private void stop() { 311 if (stealWheelFrom != null && !stealWheelFrom.isDisposed()) { 312 stealWheelFrom.removeListener(SWT.MouseVerticalWheel, this); 313 stealWheelFrom.removeFocusListener(this); 314 stealWheelFrom = null; 315 } 316 } 317 318 @Override 319 public void mouseEnter(MouseEvent e) { 320 stop(); 321 Control stealWheelFrom = getDisplay().getFocusControl(); 322 if (stealWheelFrom != null && stealWheelFrom != ChartCanvas.this) { 323 stealWheelFrom.addListener(SWT.MouseVerticalWheel, this); 324 stealWheelFrom.addFocusListener(this); 325 this.stealWheelFrom = stealWheelFrom; 326 } 327 } 328 329 @Override 330 public void mouseExit(MouseEvent e) { 331 } 332 333 @Override 334 public void mouseHover(MouseEvent e) { 335 }; 336 337 @Override 338 public void focusGained(FocusEvent e) { 339 } 340 341 @Override 342 public void focusLost(FocusEvent e) { 343 stop(); 344 } 345 } 346 347 class KeyNavigator implements KeyListener { 348 349 @Override 350 public void keyPressed(KeyEvent event) { 351 switch (event.character) { 352 case '+': 353 zoom(1); 354 break; 355 case '-': 356 zoom(-1); 357 break; 358 default: 359 switch (event.keyCode) { 360 case SWT.ESC: 361 awtChart.clearSelection(); 362 if (selectionListener != null) { 363 selectionListener.run(); 364 } 365 redrawChart(); 366 redrawChartText(); 367 break; 368 case SWT.ARROW_RIGHT: 369 pan(10); 370 break; 371 case SWT.ARROW_LEFT: 372 pan(-10); 373 break; 374 case SWT.ARROW_UP: 375 zoom(1); 376 break; 377 case SWT.ARROW_DOWN: 378 zoom(-1); 379 break; 380 default: 381 // Ignore 382 } 383 } 384 } 385 386 @Override 387 public void keyReleased(KeyEvent event) { 388 // Ignore 389 } 390 391 } 392 393 private class AntiAliasingListener implements IPropertyChangeListener { 394 395 @Override 396 public void propertyChange(PropertyChangeEvent event) { 397 redrawChart(); 398 } 399 400 } 401 402 /** 403 * This gets the "normal" DPI value for the system (72 on MacOS and 96 on Windows/Linux. It's 404 * used to determine how much larger the current DPI is so that we can draw the charts based on 405 * how large that area would be given the "normal" DPI value. Every draw on this smaller chart 406 * is then scaled up by the Graphics2D objects DefaultTransform. 407 */ 408 private final double xScale = Display.getDefault().getDPI().x / Environment.getNormalDPI(); 409 private final double yScale = Display.getDefault().getDPI().y / Environment.getNormalDPI(); 410 411 private final AwtCanvas awtCanvas = new AwtCanvas(); 412 private boolean awtNeedsRedraw; 413 private Runnable selectionListener; 414 private Runnable zoomToSelectionListener; 415 private Consumer<Boolean> zoomOnClickListener; 416 private IPropertyChangeListener aaListener; 417 private XYChart awtChart; 418 private MCContextMenuManager chartMenu; 419 private ChartTextCanvas textCanvas; 420 421 public ChartCanvas(Composite parent) { 422 super(parent, SWT.NO_BACKGROUND); 423 addPaintListener(new Painter()); 424 Selector selector = new Selector(); 425 addMouseListener(selector); 426 addMouseMoveListener(selector); 427 FocusTracker.enableFocusTracking(this); 428 addKeyListener(new KeyNavigator()); 429 aaListener = new AntiAliasingListener(); 430 UIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(aaListener); 431 addDisposeListener(e -> UIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(aaListener)); 432 if (Environment.getOSType() == OSType.WINDOWS) { 433 addMouseTrackListener(new WheelStealingZoomer()); 434 } 435 if (getParent() instanceof ScrolledComposite) { // JFR Threads Page 436 ((ScrolledComposite) getParent()).getVerticalBar() 437 .addListener(SWT.Selection, e -> vBarScroll()); 438 } else { 439 addMouseTrackListener(selector); 440 addListener(SWT.MouseVerticalWheel, new Zoomer()); 441 } 442 } 443 444 private void vBarScroll() { 445 if (textCanvas != null) { 446 Point location = ((ScrolledComposite) getParent()).getOrigin(); 447 textCanvas.syncScroll(location); 448 } 449 } 450 451 public IMenuManager getContextMenu() { 452 if (chartMenu == null) { 453 chartMenu = MCContextMenuManager.create(this); 454 chartMenu.addMenuListener(manager -> clearHighlightRects()); 455 } 456 return chartMenu; 457 } 458 459 private void render(Graphics2D context, int width, int height) { 460 if (awtChart != null) { 461 awtChart.renderChart(context, width, height); 462 if (textCanvas == null) { 463 awtChart.renderText(context, width, height); 464 } 465 } 466 } 467 468 /** 469 * Translates display coordinates into image coordinates for the chart. 470 * 471 * @param x 472 * the provided x coordinate 473 * @param y 474 * the provided y coordinate 475 * @return a Point that represents the (x,y) coordinates in the chart's coordinate space 476 */ 477 protected Point translateDisplayToImageCoordinates(int x, int y) { 478 int xImage = (int) Math.round(x / xScale); 479 int yImage = (int) Math.round(y / yScale); 480 return new Point(xImage, yImage); 481 } 482 483 /** 484 * Translates a display x coordinate into an image x coordinate for the chart. 485 * 486 * @param x 487 * the provided display x coordinate 488 * @return the x coordinate in the chart's coordinate space 489 */ 490 protected int translateDisplayToImageXCoordinates(int x) { 491 return (int) Math.round(x / xScale); 492 } 493 494 /** 495 * Translates a display x coordinate into an image x coordinate for the chart. 496 * 497 * @param x 498 * the provided display x coordinate 499 * @return the x coordinate in the chart's coordinate space 500 */ 501 protected int translateDisplayToImageYCoordinates(int y) { 502 return (int) Math.round(y / yScale); 503 } 504 505 public Object getHoveredItemData() { 506 return this.hoveredItemData; 507 } 508 509 public void setHoveredItemData(Object data) { 510 this.hoveredItemData = data; 511 } 512 513 public void resetHoveredItemData() { 514 this.hoveredItemData = null; 515 } 516 517 public void syncHighlightedRectangles (List<Rectangle2D> newRects) { 518 highlightRects = newRects; 519 redraw(); 520 } 521 522 private void updateHighlightRects() { 523 List<Rectangle2D> newRects = new ArrayList<>(); 524 infoAt(new IChartInfoVisitor.Adapter() { 525 @Override 526 public void visit(IBucket bucket) { 527 newRects.add(bucket.getTarget()); 528 } 529 530 @Override 531 public void visit(IPoint point) { 532 Point2D target = point.getTarget(); 533 newRects.add(new Rectangle2D.Double(target.getX(), target.getY(), 0, 0)); 534 } 535 536 @Override 537 public void visit(ISpan span) { 538 newRects.add(span.getTarget()); 539 } 540 541 @Override 542 public void visit(ITick tick) { 543 Point2D target = tick.getTarget(); 544 newRects.add(new Rectangle2D.Double(target.getX(), target.getY(), 0, 0)); 545 } 546 547 @Override 548 public void visit(ILane lane) { 549 // FIXME: Do we want this highlighted? 550 } 551 552 @Override 553 public void hover(Object data) { 554 if (data != null) { 555 setHoveredItemData(data); 556 } 557 } 558 }, lastMouseX, lastMouseY); 559 // Attempt to reduce flicker by avoiding unnecessary updates. 560 if (!newRects.equals(highlightRects)) { 561 highlightRects = newRects; 562 if (textCanvas != null) { 563 textCanvas.syncHighlightedRectangles(highlightRects); 564 } 565 redraw(); 566 } 567 } 568 569 private void clearHighlightRects() { 570 if (highlightRects != null) { 571 highlightRects = null; 572 redraw(); 573 } 574 } 575 576 private void handleWheelEvent(int stateMask, int x, int count) { 577 // SWT.MOD1 is CMD on OS X and CTRL elsewhere. 578 if ((stateMask & SWT.MOD1) != 0) { 579 pan(count * 3); 580 } else { 581 zoom(translateDisplayToImageXCoordinates(x), count); 582 } 583 } 584 585 private void pan(int rightPercent) { 586 if ((awtChart != null) && awtChart.pan(rightPercent)) { 587 redrawChart(); 588 } 589 } 590 591 private void zoom(int zoomInSteps) { 592 if ((awtChart != null) && awtChart.zoom(zoomInSteps)) { 593 redrawChart(); 594 } 595 } 596 597 private void zoom(int x, int zoomInSteps) { 598 if ((awtChart != null) && awtChart.zoom(x, zoomInSteps)) { 599 redrawChart(); 600 } 601 } 602 603 private void select(int x1, int x2, int y1, int y2, boolean clear) { 604 Point p1 = translateDisplayToImageCoordinates(x1, y1); 605 Point p2 = translateDisplayToImageCoordinates(x2, y2); 606 if ((awtChart != null) && awtChart.select(p1.x, p2.x, p1.y, p2.y, clear)) { 607 redrawChart(); 608 redrawChartText(); 609 } 610 } 611 612 private void toggleSelect(int x, int y) { 613 Point p = translateDisplayToImageCoordinates(x, y); 614 if (awtChart != null) { 615 final IQuantity[] range = new IQuantity[2]; 616 infoAt(new IChartInfoVisitor.Adapter() { 617 @Override 618 public void visit(IBucket bucket) { 619 if (range[0] == null) { 620 range[0] = (IQuantity) bucket.getStartX(); 621 range[1] = (IQuantity) bucket.getEndX(); 622 } 623 } 624 625 @Override 626 public void visit(ISpan span) { 627 if (range[0] == null) { 628 IDisplayable x0 = span.getStartX(); 629 IDisplayable x1 = span.getEndX(); 630 range[0] = (x0 instanceof IQuantity) ? (IQuantity) x0 : null; 631 range[1] = (x1 instanceof IQuantity) ? (IQuantity) x1 : null; 632 } 633 } 634 }, x, y); 635 if ((range[0] != null) || (range[1] != null)) { 636 if (!awtChart.select(range[0], range[1], p.y, p.y, true)) { 637 awtChart.clearSelection(); 638 } 639 } else { 640 if (!awtChart.select(p.x, p.x, p.y, p.y, true)) { 641 awtChart.clearSelection(); 642 } 643 } 644 notifyZoomOnClickListener(SWT.MouseDown); 645 redrawChart(); 646 redrawChartText(); 647 } 648 } 649 650 public void setChart(XYChart awtChart) { 651 this.awtChart = awtChart; 652 notifyListener(); 653 redrawChart(); 654 } 655 656 public void setTextCanvas(ChartTextCanvas textCanvas) { 657 this.textCanvas = textCanvas; 658 } 659 660 public void syncScroll(Point scrollPoint) { 661 ((ScrolledComposite) getParent()).setOrigin(scrollPoint); 662 } 663 664 public void replaceRenderer(IXDataRenderer rendererRoot) { 665 assert awtChart != null; 666 awtChart.setRendererRoot(rendererRoot); 667 notifyListener(); 668 redrawChart(); 669 } 670 671 public void setSelectionListener(Runnable selectionListener) { 672 this.selectionListener = selectionListener; 673 } 674 675 public void setZoomToSelectionListener(Runnable zoomListener) { 676 this.zoomToSelectionListener = zoomListener; 677 } 678 679 public void setZoomOnClickListener(Consumer<Boolean> clickListener) { 680 this.zoomOnClickListener = clickListener; 681 } 682 683 private void notifyZoomOnClickListener(Integer button) { 684 if (zoomOnClickListener != null) { 685 zoomOnClickListener.accept(button == SWT.MouseDown); 686 } 687 } 688 689 private void notifyListener() { 690 if (selectionListener != null) { 691 selectionListener.run(); 692 } 693 } 694 695 public void changeCursor(Cursor cursor) { 696 setCursor(cursor); 697 } 698 699 public void infoAt(IChartInfoVisitor visitor, int x, int y) { 700 Point p = translateDisplayToImageCoordinates(x, y); 701 if (awtChart != null) { 702 awtChart.infoAt(visitor, p.x, p.y); 703 } 704 } 705 706 /** 707 * Mark both the (AWT) chart and the SWT control as needing a redraw. 708 */ 709 public void redrawChart() { 710 awtNeedsRedraw = true; 711 redraw(); 712 } 713 714 private void redrawChartText() { 715 if (textCanvas != null) { 716 textCanvas.redrawChartText(); 717 } 718 } 719 720 }