001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.painter; 020 021import icy.canvas.IcyCanvas; 022import icy.canvas.IcyCanvas2D; 023import icy.common.CollapsibleEvent; 024import icy.painter.OverlayEvent.OverlayEventType; 025import icy.painter.PainterEvent.PainterEventType; 026import icy.sequence.Sequence; 027import icy.system.thread.ThreadUtil; 028import icy.type.point.Point5D; 029import icy.util.EventUtil; 030import icy.util.ShapeUtil; 031import icy.util.XMLUtil; 032import icy.vtk.IcyVtkPanel; 033 034import java.awt.BasicStroke; 035import java.awt.Color; 036import java.awt.Graphics2D; 037import java.awt.event.InputEvent; 038import java.awt.event.KeyEvent; 039import java.awt.event.MouseEvent; 040import java.awt.geom.Ellipse2D; 041import java.awt.geom.Point2D; 042import java.lang.ref.WeakReference; 043import java.util.ArrayList; 044import java.util.Arrays; 045import java.util.EventListener; 046import java.util.List; 047 048import org.w3c.dom.Node; 049 050import plugins.kernel.canvas.VtkCanvas; 051import vtk.vtkActor; 052import vtk.vtkInformation; 053import vtk.vtkPolyDataMapper; 054import vtk.vtkProp; 055import vtk.vtkSphereSource; 056 057/** 058 * Anchor2D class, used for 2D point control. 059 * 060 * @author Stephane 061 */ 062public class Anchor2D extends Overlay implements VtkPainter, Runnable 063{ 064 /** 065 * Interface to listen Anchor2D position change 066 */ 067 public static interface Anchor2DPositionListener extends EventListener 068 { 069 public void positionChanged(Anchor2D source); 070 } 071 072 /** 073 * @deprecated Use {@link Anchor2DPositionListener} listener or {@link OverlayListener} 074 */ 075 @Deprecated 076 public static interface Anchor2DListener extends PainterListener 077 { 078 public void positionChanged(Anchor2D source); 079 } 080 081 public static class Anchor2DEvent implements CollapsibleEvent 082 { 083 private final Anchor2D source; 084 085 public Anchor2DEvent(Anchor2D source) 086 { 087 super(); 088 089 this.source = source; 090 } 091 092 /** 093 * @return the source 094 */ 095 public Anchor2D getSource() 096 { 097 return source; 098 } 099 100 @Override 101 public boolean collapse(CollapsibleEvent event) 102 { 103 if (equals(event)) 104 { 105 // nothing to do here 106 return true; 107 } 108 109 return false; 110 } 111 112 @Override 113 public int hashCode() 114 { 115 return source.hashCode(); 116 } 117 118 @Override 119 public boolean equals(Object obj) 120 { 121 if (obj instanceof Anchor2DEvent) 122 { 123 final Anchor2DEvent event = (Anchor2DEvent) obj; 124 125 return (event.getSource() == source); 126 } 127 128 return super.equals(obj); 129 } 130 } 131 132 protected static final String ID_COLOR = "color"; 133 protected static final String ID_SELECTEDCOLOR = "selected_color"; 134 protected static final String ID_SELECTED = "selected"; 135 protected static final String ID_POS_X = "pos_x"; 136 protected static final String ID_POS_Y = "pos_y"; 137 protected static final String ID_RAY = "ray"; 138 protected static final String ID_VISIBLE = "visible"; 139 140 public static final int DEFAULT_RAY = 6; 141 public static final Color DEFAULT_NORMAL_COLOR = Color.YELLOW; 142 public static final Color DEFAULT_SELECTED_COLOR = Color.WHITE; 143 144 public static final String PROPERTY_COLOR = ID_COLOR; 145 public static final String PROPERTY_SELECTEDCOLOR = ID_SELECTEDCOLOR; 146 public static final String PROPERTY_SELECTED = ID_SELECTED; 147 public static final String PROPERTY_RAY = ID_RAY; 148 149 /** 150 * position (canvas) 151 */ 152 protected final Point2D.Double position; 153 /** 154 * position Z (default = -1 = ALL) 155 */ 156 protected int z; 157 158 /** 159 * radius 160 */ 161 protected int ray; 162 163 /** 164 * color 165 */ 166 protected Color color; 167 /** 168 * selection color 169 */ 170 protected Color selectedColor; 171 /** 172 * selection flag 173 */ 174 protected boolean selected; 175 /** 176 * flag that indicate if anchor is visible 177 */ 178 protected boolean visible; 179 180 /** 181 * internals 182 */ 183 protected final Ellipse2D ellipse; 184 protected Point2D startDragMousePosition; 185 protected Point2D startDragPainterPosition; 186 187 // VTK 3D objects 188 vtkSphereSource vtkSource; 189 protected vtkPolyDataMapper polyMapper; 190 protected vtkActor actor; 191 protected vtkInformation vtkInfo; 192 193 // 3D internal 194 protected boolean needRebuild; 195 protected boolean needPropertiesUpdate; 196 protected double scaling[]; 197 protected WeakReference<VtkCanvas> canvas3d; 198 199 protected final List<Anchor2DListener> anchor2Dlisteners; 200 protected final List<Anchor2DPositionListener> anchor2DPositionlisteners; 201 202 public Anchor2D(double x, double y, int ray, Color color, Color selectedColor) 203 { 204 super("Anchor", OverlayPriority.SHAPE_NORMAL); 205 206 position = new Point2D.Double(x, y); 207 z = -1; 208 this.ray = ray; 209 this.color = color; 210 this.selectedColor = selectedColor; 211 selected = false; 212 visible = true; 213 214 ellipse = new Ellipse2D.Double(); 215 startDragMousePosition = null; 216 startDragPainterPosition = null; 217 218 vtkSource = null; 219 polyMapper = null; 220 actor = null; 221 vtkInfo = null; 222 223 scaling = new double[3]; 224 Arrays.fill(scaling, 1d); 225 226 needRebuild = true; 227 needPropertiesUpdate = false; 228 229 canvas3d = new WeakReference<VtkCanvas>(null); 230 231 anchor2Dlisteners = new ArrayList<Anchor2DListener>(); 232 anchor2DPositionlisteners = new ArrayList<Anchor2DPositionListener>(); 233 } 234 235 public Anchor2D(double x, double y, int ray, Color color) 236 { 237 this(x, y, ray, color, DEFAULT_SELECTED_COLOR); 238 } 239 240 /** 241 * @param x 242 * @param y 243 * @param ray 244 */ 245 public Anchor2D(double x, double y, int ray) 246 { 247 this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 248 } 249 250 /** 251 * @param x 252 * @param y 253 * @param color 254 */ 255 public Anchor2D(double x, double y, Color color, Color selectedColor) 256 { 257 this(x, y, DEFAULT_RAY, color, selectedColor); 258 } 259 260 /** 261 * @param x 262 * @param y 263 * @param color 264 */ 265 public Anchor2D(double x, double y, Color color) 266 { 267 this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); 268 } 269 270 /** 271 * @param x 272 * @param y 273 */ 274 public Anchor2D(double x, double y) 275 { 276 this(x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 277 } 278 279 /** 280 */ 281 public Anchor2D() 282 { 283 this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 284 } 285 286 /** 287 * @deprecated Use {@link #Anchor2D(double, double, int, Color, Color)} instead. 288 */ 289 @Deprecated 290 public Anchor2D(Point2D position, int ray, Color color, Color selectedColor) 291 { 292 this(position.getX(), position.getY(), ray, color, selectedColor); 293 } 294 295 /** 296 * @deprecated Use {@link #Anchor2D(double, double, int, Color)} instead. 297 */ 298 @Deprecated 299 public Anchor2D(Point2D position, int ray, Color color) 300 { 301 this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR); 302 } 303 304 /** 305 * @deprecated Use {@link #Anchor2D(double, double, int)} instead. 306 */ 307 @Deprecated 308 public Anchor2D(Point2D position, int ray) 309 { 310 this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 311 } 312 313 /** 314 * @deprecated Use {@link #Anchor2D(double, double, Color, Color)} instead. 315 */ 316 @Deprecated 317 public Anchor2D(Point2D position, Color color, Color selectedColor) 318 { 319 this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor); 320 } 321 322 /** 323 * @deprecated Use {@link #Anchor2D(double, double, Color)} instead. 324 */ 325 @Deprecated 326 public Anchor2D(Point2D position, Color color) 327 { 328 this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); 329 } 330 331 /** 332 * @deprecated Use {@link #Anchor2D(double, double)} instead. 333 */ 334 @Deprecated 335 public Anchor2D(Point2D position) 336 { 337 this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 338 } 339 340 /** 341 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead. 342 */ 343 @Deprecated 344 public Anchor2D(Sequence sequence, double x, double y, int ray, Color color, Color selectedColor) 345 { 346 this(x, y, ray, color, selectedColor); 347 sequence.addOverlay(this); 348 } 349 350 /** 351 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int, Color, Color)} instead. 352 */ 353 @Deprecated 354 public Anchor2D(Sequence sequence, Point2D position, int ray, Color color, Color selectedColor) 355 { 356 this(position.getX(), position.getY(), ray, color, selectedColor); 357 sequence.addOverlay(this); 358 } 359 360 /** 361 * @deprecated Use {@link Anchor2D#Anchor2D(Point2D, int, Color)} instead. 362 */ 363 @Deprecated 364 public Anchor2D(Sequence sequence, Point2D position, int ray, Color color) 365 { 366 this(position.getX(), position.getY(), ray, color, DEFAULT_SELECTED_COLOR); 367 sequence.addOverlay(this); 368 } 369 370 /** 371 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. 372 */ 373 @Deprecated 374 public Anchor2D(Sequence sequence, double x, double y, int ray) 375 { 376 this(x, y, ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 377 sequence.addOverlay(this); 378 } 379 380 /** 381 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color)} instead. 382 */ 383 @Deprecated 384 public Anchor2D(Sequence sequence, Point2D position, Color color) 385 { 386 this(position.getX(), position.getY(), DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); 387 sequence.addOverlay(this); 388 } 389 390 /** 391 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. 392 */ 393 @Deprecated 394 public Anchor2D(Sequence sequence, Point2D position, int ray) 395 { 396 this(position.getX(), position.getY(), ray, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 397 sequence.addOverlay(this); 398 } 399 400 /** 401 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, int)} instead. 402 */ 403 @Deprecated 404 public Anchor2D(Sequence sequence, double x, double y, Color color, Color selectedColor) 405 { 406 this(x, y, DEFAULT_RAY, color, selectedColor); 407 sequence.addOverlay(this); 408 } 409 410 /** 411 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead. 412 */ 413 @Deprecated 414 public Anchor2D(Sequence sequence, Point2D position, Color color, Color selectedColor) 415 { 416 this(position.getX(), position.getY(), DEFAULT_RAY, color, selectedColor); 417 sequence.addOverlay(this); 418 } 419 420 /** 421 * @deprecated Use {@link Anchor2D#Anchor2D(double, double, Color, Color)} instead. 422 */ 423 @Deprecated 424 public Anchor2D(Sequence sequence, double x, double y, Color color) 425 { 426 this(x, y, DEFAULT_RAY, color, DEFAULT_SELECTED_COLOR); 427 sequence.addOverlay(this); 428 } 429 430 /** 431 * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead. 432 */ 433 @Deprecated 434 public Anchor2D(Sequence sequence, double x, double y) 435 { 436 this(sequence, x, y, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 437 sequence.addOverlay(this); 438 } 439 440 /** 441 * @deprecated Use {@link Anchor2D#Anchor2D(double, double)} instead. 442 */ 443 @Deprecated 444 public Anchor2D(Sequence sequence, Point2D position) 445 { 446 this(position.getX(), position.getY(), DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 447 sequence.addOverlay(this); 448 } 449 450 /** 451 * @deprecated Use {@link Anchor2D#Anchor2D()} instead. 452 */ 453 @Deprecated 454 public Anchor2D(Sequence sequence) 455 { 456 this(0d, 0d, DEFAULT_RAY, DEFAULT_NORMAL_COLOR, DEFAULT_SELECTED_COLOR); 457 sequence.addOverlay(this); 458 } 459 460 @Override 461 protected void finalize() throws Throwable 462 { 463 super.finalize(); 464 465 // release allocated VTK resources 466 if (vtkSource != null) 467 vtkSource.Delete(); 468 if (actor != null) 469 { 470 actor.SetPropertyKeys(null); 471 actor.Delete(); 472 } 473 if (vtkInfo != null) 474 { 475 vtkInfo.Remove(VtkCanvas.visibilityKey); 476 vtkInfo.Delete(); 477 } 478 if (polyMapper != null) 479 polyMapper.Delete(); 480 } 481 482 /** 483 * @return the x 484 */ 485 public double getX() 486 { 487 return position.x; 488 } 489 490 /** 491 * @param x 492 * the x to set 493 */ 494 public void setX(double x) 495 { 496 setPosition(x, position.y); 497 } 498 499 /** 500 * @return the y 501 */ 502 public double getY() 503 { 504 return position.y; 505 } 506 507 /** 508 * @param y 509 * the y to set 510 */ 511 public void setY(double y) 512 { 513 setPosition(position.x, y); 514 } 515 516 /** 517 * Get anchor position (return the internal reference) 518 */ 519 public Point2D getPositionInternal() 520 { 521 return position; 522 } 523 524 public Point2D getPosition() 525 { 526 return new Point2D.Double(position.x, position.y); 527 } 528 529 public double getPositionX() 530 { 531 return position.x; 532 } 533 534 public double getPositionY() 535 { 536 return position.y; 537 } 538 539 public void moveTo(Point2D p) 540 { 541 setPosition(p.getX(), p.getY()); 542 } 543 544 public void moveTo(double x, double y) 545 { 546 setPosition(x, y); 547 } 548 549 public void setPosition(Point2D p) 550 { 551 setPosition(p.getX(), p.getY()); 552 } 553 554 public void setPosition(double x, double y) 555 { 556 if ((position.x != x) || (position.y != y)) 557 { 558 position.x = x; 559 position.y = y; 560 561 needRebuild = true; 562 positionChanged(); 563 painterChanged(); 564 } 565 } 566 567 public void translate(double dx, double dy) 568 { 569 setPosition(position.x + dx, position.y + dy); 570 } 571 572 /** 573 * @return Z position (-1 = ALL) 574 */ 575 public int getZ() 576 { 577 return z; 578 } 579 580 /** 581 * Set Z position (-1 = ALL) 582 */ 583 public void setZ(int value) 584 { 585 final int v; 586 587 // special value for infinite dimension --> change to -1 588 if (value == Integer.MIN_VALUE) 589 v = -1; 590 else 591 v = value; 592 593 if (this.z != v) 594 { 595 this.z = v; 596 597 needRebuild = true; 598 painterChanged(); 599 // FIXME: do this really need to send a position change event ? 600 positionChanged(); 601 } 602 } 603 604 /** 605 * @return the ray 606 */ 607 public int getRay() 608 { 609 return ray; 610 } 611 612 /** 613 * @param value 614 * the ray to set 615 */ 616 public void setRay(int value) 617 { 618 if (ray != value) 619 { 620 ray = value; 621 needRebuild = true; 622 painterChanged(); 623 } 624 } 625 626 /** 627 * @return the color 628 */ 629 public Color getColor() 630 { 631 return color; 632 } 633 634 /** 635 * @param value 636 * the color to set 637 */ 638 public void setColor(Color value) 639 { 640 if (color != value) 641 { 642 color = value; 643 needPropertiesUpdate = true; 644 propertyChanged(PROPERTY_COLOR); 645 painterChanged(); 646 } 647 } 648 649 /** 650 * @return the selectedColor 651 */ 652 public Color getSelectedColor() 653 { 654 return selectedColor; 655 } 656 657 /** 658 * @param value 659 * the selectedColor to set 660 */ 661 public void setSelectedColor(Color value) 662 { 663 if (selectedColor != value) 664 { 665 selectedColor = value; 666 needPropertiesUpdate = true; 667 propertyChanged(PROPERTY_SELECTEDCOLOR); 668 painterChanged(); 669 } 670 } 671 672 /** 673 * @return the selected 674 */ 675 public boolean isSelected() 676 { 677 return selected; 678 } 679 680 /** 681 * @param value 682 * the selected to set 683 */ 684 public void setSelected(boolean value) 685 { 686 if (selected != value) 687 { 688 selected = value; 689 690 // end drag 691 if (!value) 692 startDragMousePosition = null; 693 694 needPropertiesUpdate = true; 695 propertyChanged(PROPERTY_SELECTED); 696 painterChanged(); 697 } 698 } 699 700 // /** 701 // * @return the canRemove 702 // */ 703 // public boolean isCanRemove() 704 // { 705 // return canRemove; 706 // } 707 // 708 // /** 709 // * @param value 710 // * the canRemove to set 711 // */ 712 // public void setCanRemove(boolean value) 713 // { 714 // canRemove = value; 715 // } 716 717 /** 718 * @return the visible 719 */ 720 public boolean isVisible() 721 { 722 return visible; 723 } 724 725 /** 726 * @param value 727 * the visible to set 728 */ 729 public void setVisible(boolean value) 730 { 731 if (visible != value) 732 { 733 visible = value; 734 needPropertiesUpdate = true; 735 painterChanged(); 736 } 737 } 738 739 protected void initVtkObjects() 740 { 741 // init 3D painters stuff 742 vtkSource = new vtkSphereSource(); 743 vtkSource.SetRadius(getRay()); 744 vtkSource.SetThetaResolution(12); 745 vtkSource.SetPhiResolution(12); 746 747 polyMapper = new vtkPolyDataMapper(); 748 polyMapper.SetInputConnection((vtkSource).GetOutputPort()); 749 750 actor = new vtkActor(); 751 actor.SetMapper(polyMapper); 752 753 // use vtkInformations to store outline visibility state (hacky) 754 vtkInfo = new vtkInformation(); 755 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 756 // VtkCanvas use this to restore correctly outline visibility flag 757 actor.SetPropertyKeys(vtkInfo); 758 759 // initialize color 760 final Color col = getColor(); 761 actor.GetProperty().SetColor(col.getRed() / 255d, col.getGreen() / 255d, col.getBlue() / 255d); 762 } 763 764 /** 765 * update 3D painter for 3D canvas (called only when VTK is loaded). 766 */ 767 protected boolean rebuildVtkObjects() 768 { 769 final VtkCanvas canvas = canvas3d.get(); 770 // canvas was closed 771 if (canvas == null) 772 return false; 773 774 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 775 // canvas was closed 776 if (vtkPanel == null) 777 return false; 778 779 final Sequence seq = canvas.getSequence(); 780 // nothing to update 781 if (seq == null) 782 return false; 783 784 final Point2D pos = getPosition(); 785 double curZ = getZ(); 786 787 // all slices ? 788 if (curZ == -1d) 789 // set object at middle of the volume 790 curZ = seq.getSizeZ() / 2d; 791 792 // actor can be accessed in canvas3d for rendering so we need to synchronize access 793 vtkPanel.lock(); 794 try 795 { 796 // need to handle scaling on radius and position to keep a "round" sphere (else we obtain ellipsoid) 797 vtkSource.SetCenter(pos.getX() * scaling[0], pos.getY() * scaling[1], (curZ + 0.5d) * scaling[2]); 798 polyMapper.Update(); 799 800 // actor.SetScale(scaling); 801 } 802 finally 803 { 804 vtkPanel.unlock(); 805 } 806 807 // need to repaint 808 painterChanged(); 809 810 return true; 811 } 812 813 protected void updateVtkDisplayProperties() 814 { 815 if (actor == null) 816 return; 817 818 final VtkCanvas cnv = canvas3d.get(); 819 final Color col = isSelected() ? getSelectedColor() : getColor(); 820 final double r = col.getRed() / 255d; 821 final double g = col.getGreen() / 255d; 822 final double b = col.getBlue() / 255d; 823 // final float opacity = getOpacity(); 824 825 final IcyVtkPanel vtkPanel = (cnv != null) ? cnv.getVtkPanel() : null; 826 827 // we need to lock canvas as actor can be accessed during rendering 828 if (vtkPanel != null) 829 vtkPanel.lock(); 830 try 831 { 832 actor.GetProperty().SetColor(r, g, b); 833 if (isVisible()) 834 { 835 actor.SetVisibility(1); 836 vtkInfo.Set(VtkCanvas.visibilityKey, 1); 837 } 838 else 839 { 840 actor.SetVisibility(0); 841 vtkInfo.Set(VtkCanvas.visibilityKey, 0); 842 } 843 } 844 finally 845 { 846 if (vtkPanel != null) 847 vtkPanel.unlock(); 848 } 849 // need to repaint 850 painterChanged(); 851 } 852 853 protected void updateVtkRadius() 854 { 855 final VtkCanvas canvas = canvas3d.get(); 856 // canvas was closed 857 if (canvas == null) 858 return; 859 860 final IcyVtkPanel vtkPanel = canvas.getVtkPanel(); 861 // canvas was closed 862 if (vtkPanel == null) 863 return; 864 865 if (vtkSource == null) 866 return; 867 868 // update sphere radius base on canvas scale X 869 final double radius = getAdjRay(canvas) * scaling[0]; 870 871 if (vtkSource.GetRadius() != radius) 872 { 873 // actor can be accessed in canvas3d for rendering so we need to synchronize access 874 vtkPanel.lock(); 875 try 876 { 877 vtkSource.SetRadius(radius); 878 vtkSource.Update(); 879 } 880 finally 881 { 882 vtkPanel.unlock(); 883 } 884 885 // need to repaint 886 painterChanged(); 887 } 888 } 889 890 @Override 891 public vtkProp[] getProps() 892 { 893 // initialize VTK objects if not yet done 894 if (actor == null) 895 initVtkObjects(); 896 897 return new vtkActor[] {actor}; 898 } 899 900 /** 901 * Returns <code>true</code> if specified Point3D is over the anchor in the specified canvas 902 */ 903 public boolean isOver(IcyCanvas canvas, Point2D imagePoint) 904 { 905 updateEllipseForCanvas(canvas); 906 907 // specific VTK canvas processing 908 if (canvas instanceof VtkCanvas) 909 { 910 // faster to use picked object 911 return (actor != null) && (actor == ((VtkCanvas) canvas).getPickedObject()); 912 } 913 914 // at this point we need image position 915 if (imagePoint == null) 916 return false; 917 918 // fast contains test to start with 919 if (ellipse.getBounds2D().contains(imagePoint)) 920 return ellipse.contains(imagePoint); 921 922 return false; 923 } 924 925 public boolean isOver(IcyCanvas canvas, double x, double y) 926 { 927 return isOver(canvas, new Point2D.Double(x, y)); 928 } 929 930 protected double getAdjRay(IcyCanvas canvas) 931 { 932 // assume X dimension is ok here 933 return canvas.canvasToImageLogDeltaX(ray); 934 } 935 936 /** 937 * Update internal ellipse for specified Canvas 938 */ 939 protected void updateEllipseForCanvas(IcyCanvas canvas) 940 { 941 // specific VTK canvas processing 942 if (canvas instanceof VtkCanvas) 943 { 944 // 3D canvas 945 final VtkCanvas cnv = (VtkCanvas) canvas; 946 // update reference if needed 947 if (canvas3d.get() != cnv) 948 canvas3d = new WeakReference<VtkCanvas>(cnv); 949 950 // FIXME : need a better implementation 951 final double[] s = cnv.getVolumeScale(); 952 953 // scaling changed ? 954 if (!Arrays.equals(scaling, s)) 955 { 956 // update scaling 957 scaling = s; 958 // need rebuild 959 needRebuild = true; 960 } 961 962 if (needRebuild) 963 { 964 // initialize VTK objects if not yet done 965 if (actor == null) 966 initVtkObjects(); 967 968 // request rebuild 3D objects 969 ThreadUtil.runSingle(this); 970 needRebuild = false; 971 } 972 973 if (needPropertiesUpdate) 974 { 975 updateVtkDisplayProperties(); 976 needPropertiesUpdate = false; 977 } 978 979 // update sphere radius 980 updateVtkRadius(); 981 } 982 else 983 { 984 final double adjRay = getAdjRay(canvas); 985 986 ellipse.setFrame(position.x - adjRay, position.y - adjRay, adjRay * 2, adjRay * 2); 987 } 988 } 989 990 protected boolean updateDrag(InputEvent e, double x, double y) 991 { 992 // not dragging --> exit 993 if (startDragMousePosition == null) 994 return false; 995 996 double dx = x - startDragMousePosition.getX(); 997 double dy = y - startDragMousePosition.getY(); 998 999 // shift action --> limit to one direction 1000 if (EventUtil.isShiftDown(e)) 1001 { 1002 // X drag 1003 if (Math.abs(dx) > Math.abs(dy)) 1004 dy = 0; 1005 // Y drag 1006 else 1007 dx = 0; 1008 } 1009 1010 // set new position 1011 setPosition(startDragPainterPosition.getX() + dx, startDragPainterPosition.getY() + dy); 1012 1013 return true; 1014 } 1015 1016 protected boolean updateDrag(InputEvent e, Point2D pt) 1017 { 1018 return updateDrag(e, pt.getX(), pt.getY()); 1019 } 1020 1021 /** 1022 * called when anchor position has changed 1023 */ 1024 protected void positionChanged() 1025 { 1026 updater.changed(new Anchor2DEvent(this)); 1027 } 1028 1029 @SuppressWarnings("deprecation") 1030 @Override 1031 public void onChanged(CollapsibleEvent object) 1032 { 1033 if (object instanceof Anchor2DEvent) 1034 { 1035 firePositionChangedEvent(((Anchor2DEvent) object).getSource()); 1036 return; 1037 } 1038 1039 // provide event backward compatibility 1040 if (object instanceof OverlayEvent) 1041 { 1042 final OverlayEvent event = (OverlayEvent) object; 1043 1044 if (event.getType() == OverlayEventType.PAINTER_CHANGED) 1045 { 1046 final PainterEvent pe = new PainterEvent(this, PainterEventType.PAINTER_CHANGED); 1047 1048 for (Anchor2DListener listener : new ArrayList<Anchor2DListener>(anchor2Dlisteners)) 1049 listener.painterChanged(pe); 1050 } 1051 } 1052 1053 super.onChanged(object); 1054 } 1055 1056 protected void firePositionChangedEvent(Anchor2D source) 1057 { 1058 for (Anchor2DPositionListener listener : new ArrayList<Anchor2DPositionListener>(anchor2DPositionlisteners)) 1059 listener.positionChanged(source); 1060 1061 // backward compatibility 1062 for (Anchor2DListener listener : new ArrayList<Anchor2DListener>(anchor2Dlisteners)) 1063 listener.positionChanged(source); 1064 } 1065 1066 public void addPositionListener(Anchor2DPositionListener listener) 1067 { 1068 anchor2DPositionlisteners.add(listener); 1069 } 1070 1071 public void removePositionListener(Anchor2DPositionListener listener) 1072 { 1073 anchor2DPositionlisteners.remove(listener); 1074 } 1075 1076 /** 1077 * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or 1078 * {@link #addOverlayListener(OverlayListener)} instead. 1079 */ 1080 @Deprecated 1081 public void addAnchorListener(Anchor2DListener listener) 1082 { 1083 anchor2Dlisteners.add(listener); 1084 } 1085 1086 /** 1087 * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or 1088 * {@link #removeOverlayListener(OverlayListener)} instead. 1089 */ 1090 @Deprecated 1091 public void removeAnchorListener(Anchor2DListener listener) 1092 { 1093 anchor2Dlisteners.remove(listener); 1094 } 1095 1096 /** 1097 * @deprecated Use {@link #addPositionListener(Anchor2DPositionListener)} or 1098 * {@link #addOverlayListener(OverlayListener)} instead. 1099 */ 1100 @Deprecated 1101 public void addListener(Anchor2DListener listener) 1102 { 1103 addAnchorListener(listener); 1104 } 1105 1106 /** 1107 * @deprecated Use {@link #removePositionListener(Anchor2DPositionListener)} or 1108 * {@link #removeOverlayListener(OverlayListener)} instead. 1109 */ 1110 @Deprecated 1111 public void removeListener(Anchor2DListener listener) 1112 { 1113 removeAnchorListener(listener); 1114 } 1115 1116 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas, boolean simplified) 1117 { 1118 // this will update VTK objects if needed 1119 updateEllipseForCanvas(canvas); 1120 1121 if (canvas instanceof IcyCanvas2D) 1122 { 1123 // nothing to do here when not visible 1124 if (!isVisible()) 1125 return; 1126 1127 // get canvas Z position 1128 final int cnvZ = canvas.getPositionZ(); 1129 final int z = getZ(); 1130 1131 boolean sameZPos = ((z == -1) || (cnvZ == -1) || (z == cnvZ)); 1132 1133 if (sameZPos) 1134 { 1135 // trivial paint optimization 1136 if (ShapeUtil.isVisible(g, ellipse)) 1137 { 1138 final Graphics2D g2 = (Graphics2D) g.create(); 1139 1140 // draw content 1141 if (isSelected()) 1142 g2.setColor(getSelectedColor()); 1143 else 1144 g2.setColor(getColor()); 1145 1146 // simplified small drawing 1147 if (simplified) 1148 { 1149 final int ray = (int) canvas.canvasToImageDeltaX(2); 1150 g2.fillRect((int) getPositionX() - ray, (int) getPositionY() - ray, ray * 2, ray * 2); 1151 } 1152 // normal drawing 1153 else 1154 { 1155 // draw ellipse content 1156 g2.fill(ellipse); 1157 // draw black border 1158 g2.setStroke(new BasicStroke((float) (getAdjRay(canvas) / 8f))); 1159 g2.setColor(Color.black); 1160 g2.draw(ellipse); 1161 } 1162 1163 g2.dispose(); 1164 } 1165 } 1166 } 1167 else if (canvas instanceof VtkCanvas) 1168 { 1169 // nothing to do here 1170 } 1171 } 1172 1173 @Override 1174 public void paint(Graphics2D g, Sequence sequence, IcyCanvas canvas) 1175 { 1176 paint(g, sequence, canvas, false); 1177 } 1178 1179 /* 1180 * only for backward compatibility 1181 */ 1182 @Deprecated 1183 @Override 1184 public void keyPressed(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) 1185 { 1186 if (!isVisible()) 1187 return; 1188 1189 // VtkCanvas not handled here 1190 if (canvas instanceof VtkCanvas) 1191 return; 1192 // no image position --> exit 1193 if (imagePoint == null) 1194 return; 1195 1196 // just for the shift key state change 1197 updateDrag(e, imagePoint); 1198 } 1199 1200 /* 1201 * only for backward compatibility 1202 */ 1203 @Deprecated 1204 @Override 1205 public void keyReleased(KeyEvent e, Point2D imagePoint, IcyCanvas canvas) 1206 { 1207 if (!isVisible()) 1208 return; 1209 1210 // VtkCanvas not handled here 1211 if (canvas instanceof VtkCanvas) 1212 return; 1213 // no image position --> exit 1214 if (imagePoint == null) 1215 return; 1216 1217 // just for the shift key state change 1218 updateDrag(e, imagePoint); 1219 } 1220 1221 /* 1222 * only for backward compatibility 1223 */ 1224 @Deprecated 1225 @Override 1226 public void mousePressed(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) 1227 { 1228 if (!isVisible()) 1229 return; 1230 1231 if (e.isConsumed()) 1232 return; 1233 1234 // VtkCanvas not handled here 1235 if (canvas instanceof VtkCanvas) 1236 return; 1237 // no image position --> exit 1238 if (imagePoint == null) 1239 return; 1240 1241 if (EventUtil.isLeftMouseButton(e)) 1242 { 1243 // consume event to activate drag 1244 if (isSelected()) 1245 { 1246 startDragMousePosition = imagePoint; 1247 startDragPainterPosition = getPosition(); 1248 e.consume(); 1249 } 1250 } 1251 } 1252 1253 /* 1254 * only for backward compatibility 1255 */ 1256 @Deprecated 1257 @Override 1258 public void mouseReleased(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) 1259 { 1260 startDragMousePosition = null; 1261 } 1262 1263 /* 1264 * only for backward compatibility 1265 */ 1266 @Deprecated 1267 @Override 1268 public void mouseDrag(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) 1269 { 1270 if (!isVisible()) 1271 return; 1272 1273 if (e.isConsumed()) 1274 return; 1275 1276 // VtkCanvas not handled here 1277 if (canvas instanceof VtkCanvas) 1278 return; 1279 // no image position --> exit 1280 if (imagePoint == null) 1281 return; 1282 1283 if (EventUtil.isLeftMouseButton(e)) 1284 { 1285 // if selected then move according to mouse position 1286 if (isSelected()) 1287 { 1288 // force start drag if not already the case 1289 if (startDragMousePosition == null) 1290 { 1291 startDragMousePosition = getPosition(); 1292 startDragPainterPosition = getPosition(); 1293 } 1294 1295 updateDrag(e, imagePoint); 1296 1297 e.consume(); 1298 } 1299 } 1300 } 1301 1302 /* 1303 * only for backward compatibility 1304 */ 1305 @Deprecated 1306 @Override 1307 public void mouseMove(MouseEvent e, Point2D imagePoint, IcyCanvas canvas) 1308 { 1309 if (!isVisible()) 1310 return; 1311 1312 // VtkCanvas not handled here 1313 if (canvas instanceof VtkCanvas) 1314 return; 1315 // no image position --> exit 1316 if (imagePoint == null) 1317 return; 1318 1319 // already consumed, no selection possible 1320 if (e.isConsumed()) 1321 setSelected(false); 1322 else 1323 { 1324 final boolean overlapped = isOver(canvas, imagePoint.getX(), imagePoint.getY()); 1325 1326 setSelected(overlapped); 1327 1328 // so we can only have one selected at once 1329 if (overlapped) 1330 e.consume(); 1331 } 1332 } 1333 1334 @Override 1335 public void keyPressed(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1336 { 1337 if (!isVisible()) 1338 return; 1339 1340 // no image position --> exit 1341 if (imagePoint == null) 1342 return; 1343 1344 // just for the shift key state change 1345 updateDrag(e, imagePoint.x, imagePoint.y); 1346 } 1347 1348 @Override 1349 public void keyReleased(KeyEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1350 { 1351 if (!isVisible()) 1352 return; 1353 1354 // no image position --> exit 1355 if (imagePoint == null) 1356 return; 1357 1358 // just for the shift key state change 1359 updateDrag(e, imagePoint.x, imagePoint.y); 1360 } 1361 1362 @Override 1363 public void mousePressed(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1364 { 1365 if (!isVisible()) 1366 return; 1367 1368 if (e.isConsumed()) 1369 return; 1370 1371 // no image position --> exit 1372 if (imagePoint == null) 1373 return; 1374 1375 if (EventUtil.isLeftMouseButton(e)) 1376 { 1377 // consume event to activate drag 1378 if (isSelected()) 1379 { 1380 startDragMousePosition = imagePoint.toPoint2D(); 1381 startDragPainterPosition = getPosition(); 1382 e.consume(); 1383 } 1384 } 1385 } 1386 1387 @Override 1388 public void mouseReleased(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1389 { 1390 startDragMousePosition = null; 1391 } 1392 1393 @Override 1394 public void mouseDrag(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1395 { 1396 if (!isVisible()) 1397 return; 1398 1399 if (e.isConsumed()) 1400 return; 1401 1402 // no image position --> exit 1403 if (imagePoint == null) 1404 return; 1405 1406 if (EventUtil.isLeftMouseButton(e)) 1407 { 1408 // if selected then move according to mouse position 1409 if (isSelected()) 1410 { 1411 // force start drag if not already the case 1412 if (startDragMousePosition == null) 1413 { 1414 startDragMousePosition = getPosition(); 1415 startDragPainterPosition = getPosition(); 1416 } 1417 1418 updateDrag(e, imagePoint.x, imagePoint.y); 1419 1420 e.consume(); 1421 } 1422 } 1423 } 1424 1425 @Override 1426 public void mouseMove(MouseEvent e, Point5D.Double imagePoint, IcyCanvas canvas) 1427 { 1428 if (!isVisible()) 1429 return; 1430 1431 // already consumed, no selection possible 1432 if (e.isConsumed()) 1433 setSelected(false); 1434 else 1435 { 1436 final boolean overlapped = isOver(canvas, (imagePoint != null) ? imagePoint.toPoint2D() : null); 1437 1438 setSelected(overlapped); 1439 1440 // so we can only have one selected at once 1441 if (overlapped) 1442 e.consume(); 1443 } 1444 } 1445 1446 @Override 1447 public void run() 1448 { 1449 rebuildVtkObjects(); 1450 } 1451 1452 public boolean loadPositionFromXML(Node node) 1453 { 1454 if (node == null) 1455 return false; 1456 1457 beginUpdate(); 1458 try 1459 { 1460 setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d)); 1461 setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d)); 1462 } 1463 finally 1464 { 1465 endUpdate(); 1466 } 1467 1468 return true; 1469 } 1470 1471 public boolean savePositionToXML(Node node) 1472 { 1473 if (node == null) 1474 return false; 1475 1476 XMLUtil.setElementDoubleValue(node, ID_POS_X, getX()); 1477 XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY()); 1478 1479 return true; 1480 } 1481 1482 @Override 1483 public boolean loadFromXML(Node node) 1484 { 1485 if (node == null) 1486 return false; 1487 1488 beginUpdate(); 1489 try 1490 { 1491 setColor(new Color(XMLUtil.getElementIntValue(node, ID_COLOR, DEFAULT_NORMAL_COLOR.getRGB()))); 1492 setSelectedColor( 1493 new Color(XMLUtil.getElementIntValue(node, ID_SELECTEDCOLOR, DEFAULT_SELECTED_COLOR.getRGB()))); 1494 setX(XMLUtil.getElementDoubleValue(node, ID_POS_X, 0d)); 1495 setY(XMLUtil.getElementDoubleValue(node, ID_POS_Y, 0d)); 1496 setRay(XMLUtil.getElementIntValue(node, ID_RAY, DEFAULT_RAY)); 1497 setVisible(XMLUtil.getElementBooleanValue(node, ID_VISIBLE, true)); 1498 } 1499 finally 1500 { 1501 endUpdate(); 1502 } 1503 1504 return true; 1505 } 1506 1507 @Override 1508 public boolean saveToXML(Node node) 1509 { 1510 if (node == null) 1511 return false; 1512 1513 XMLUtil.setElementIntValue(node, ID_COLOR, getColor().getRGB()); 1514 XMLUtil.setElementIntValue(node, ID_SELECTEDCOLOR, getSelectedColor().getRGB()); 1515 XMLUtil.setElementDoubleValue(node, ID_POS_X, getX()); 1516 XMLUtil.setElementDoubleValue(node, ID_POS_Y, getY()); 1517 XMLUtil.setElementIntValue(node, ID_RAY, getRay()); 1518 XMLUtil.setElementBooleanValue(node, ID_VISIBLE, isVisible()); 1519 1520 return true; 1521 } 1522}