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.gui.lut; 020 021import icy.gui.component.math.HistogramPanel; 022import icy.gui.component.math.HistogramPanel.HistogramPanelListener; 023import icy.gui.dialog.MessageDialog; 024import icy.gui.viewer.Viewer; 025import icy.gui.viewer.ViewerEvent; 026import icy.gui.viewer.ViewerEvent.ViewerEventType; 027import icy.gui.viewer.ViewerListener; 028import icy.image.lut.LUT.LUTChannel; 029import icy.image.lut.LUT.LUTChannelEvent; 030import icy.image.lut.LUT.LUTChannelEvent.LUTChannelEventType; 031import icy.image.lut.LUT.LUTChannelListener; 032import icy.math.Histogram; 033import icy.math.MathUtil; 034import icy.math.Scaler; 035import icy.sequence.Sequence; 036import icy.sequence.SequenceEvent; 037import icy.sequence.SequenceEvent.SequenceEventSourceType; 038import icy.sequence.SequenceListener; 039import icy.system.thread.ThreadUtil; 040import icy.type.DataType; 041import icy.type.collection.array.Array1DUtil; 042import icy.util.ColorUtil; 043import icy.util.EventUtil; 044import icy.util.GraphicsUtil; 045import icy.util.StringUtil; 046 047import java.awt.BorderLayout; 048import java.awt.Color; 049import java.awt.Cursor; 050import java.awt.Dimension; 051import java.awt.Font; 052import java.awt.Graphics; 053import java.awt.Graphics2D; 054import java.awt.Point; 055import java.awt.Rectangle; 056import java.awt.event.ActionEvent; 057import java.awt.event.ActionListener; 058import java.awt.event.MouseEvent; 059import java.awt.event.MouseListener; 060import java.awt.event.MouseMotionListener; 061import java.awt.event.MouseWheelEvent; 062import java.awt.event.MouseWheelListener; 063import java.awt.geom.Point2D; 064import java.lang.reflect.Array; 065import java.util.EventListener; 066 067import javax.swing.JMenuItem; 068import javax.swing.JPanel; 069import javax.swing.JPopupMenu; 070import javax.swing.event.EventListenerList; 071 072/** 073 * @author stephane 074 */ 075public class ScalerViewer extends JPanel implements SequenceListener, LUTChannelListener, ViewerListener 076{ 077 protected static enum actionType 078 { 079 NULL, MODIFY_LOWBOUND, MODIFY_HIGHBOUND, MODIFY_MIDDLE 080 } 081 082 public static interface ScalerPositionListener extends EventListener 083 { 084 public void positionChanged(double index, int value, double normalizedValue); 085 } 086 087 public class ScalerHistogramPanel extends HistogramPanel 088 implements MouseListener, MouseMotionListener, MouseWheelListener 089 { 090 /** 091 * 092 */ 093 private static final long serialVersionUID = -7020904979961676368L; 094 095 /** 096 * internals 097 */ 098 private actionType action; 099 private final Point2D positionInfo; 100 private boolean mouseOnLeft; 101 102 public ScalerHistogramPanel(Scaler s) 103 { 104 super(s.getAbsLeftIn(), s.getAbsRightIn(), s.isIntegerData()); 105 106 action = actionType.NULL; 107 positionInfo = new Point2D.Double(); 108 mouseOnLeft = false; 109 110 // we want to display our own background 111 // setOpaque(false); 112 // dimension (don't change it or you will regret !) 113 setMinimumSize(new Dimension(100, 100)); 114 setPreferredSize(new Dimension(240, 100)); 115 116 // add listeners 117 addMouseListener(this); 118 addMouseMotionListener(this); 119 addMouseWheelListener(this); 120 } 121 122 /** 123 * update mouse cursor 124 */ 125 private void updateCursor(Point pos) 126 { 127 final int cursor; 128 129 if (action != actionType.NULL) 130 cursor = Cursor.W_RESIZE_CURSOR; 131 else if (isOverX(pos, getLowBoundPos()) || isOverX(pos, getHighBoundPos()) || isOverX(pos, getMiddlePos())) 132 cursor = Cursor.HAND_CURSOR; 133 else 134 cursor = Cursor.DEFAULT_CURSOR; 135 136 // only if different 137 if (getCursor().getType() != cursor) 138 setCursor(Cursor.getPredefinedCursor(cursor)); 139 } 140 141 private void setPositionInfo(double index, int value, double normalizedValue) 142 { 143 if ((positionInfo.getX() != index) || (positionInfo.getY() != value)) 144 { 145 positionInfo.setLocation(index, normalizedValue); 146 scalerPositionChanged(index, value, normalizedValue); 147 repaint(); 148 } 149 } 150 151 /** 152 * Check if Point p is over area (u, *) 153 * 154 * @param p 155 * point 156 * @param x 157 * area position 158 * @return boolean 159 */ 160 private boolean isOverX(Point p, int u) 161 { 162 return isOver(p.x, p.y, u, -1, ISOVER_DEFAULT_MARGIN); 163 } 164 165 /** 166 * Check if (x, y) is over area (u, v) 167 * 168 * @param x 169 * @param y 170 * pointer 171 * @param u 172 * @param v 173 * area position 174 * @param margin 175 * allowed margin 176 * @return boolean 177 */ 178 private boolean isOver(int x, int y, int u, int v, int margin) 179 { 180 final boolean x_ok; 181 final boolean y_ok; 182 183 x_ok = (u == -1) || ((x >= (u - margin)) && (x <= (u + margin))); 184 y_ok = (v == -1) || ((y >= (v - margin)) && (y <= (v + margin))); 185 186 return x_ok && y_ok; 187 } 188 189 public int getLowBoundPos() 190 { 191 return dataToPixel(getLowBound()); 192 } 193 194 public int getHighBoundPos() 195 { 196 return dataToPixel(getHighBound()); 197 } 198 199 public int getMiddlePos() 200 { 201 return (getHighBoundPos() + getLowBoundPos()) / 2; 202 } 203 204 private void setLowBoundPos(int pos) 205 { 206 setLowBound(pixelToData(pos)); 207 } 208 209 private void setHighBoundPos(int pos) 210 { 211 setHighBound(pixelToData(pos)); 212 } 213 214 @Override 215 protected void paintComponent(Graphics g) 216 { 217 updateHisto(); 218 219 super.paintComponent(g); 220 221 final Graphics2D g2 = (Graphics2D) g.create(); 222 try 223 { 224 // display mouse position infos 225 if (positionInfo.getX() != -1) 226 { 227 final int x = dataToPixel(positionInfo.getX()); 228 final int hRange = getClientHeight() - 1; 229 final int bottom = hRange + getClientY(); 230 final int y = bottom - (int) (positionInfo.getY() * hRange); 231 232 g2.setColor(ColorUtil.xor(getForeground())); 233 g2.drawLine(x, bottom, x, y); 234 } 235 236 paintBounds(g2); 237 238 if (!StringUtil.isEmpty(message)) 239 { 240 // string display 241 g2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 12)); 242 243 final Rectangle hintBounds = GraphicsUtil.getHintBounds(g2, message, 10, 4); 244 245 if (mouseOnLeft) 246 GraphicsUtil.drawHint(g2, message, getWidth() - (10 + hintBounds.width), 4, getForeground(), 247 getBackground()); 248 else 249 GraphicsUtil.drawHint(g2, message, 10, 4, getForeground(), getBackground()); 250 } 251 } 252 finally 253 { 254 g2.dispose(); 255 } 256 } 257 258 /** 259 * draw bounds 260 */ 261 private void paintBounds(Graphics2D g) 262 { 263 final int h = getClientHeight() - 1; 264 final int y = getClientY(); 265 final int lowBound = getLowBoundPos(); 266 final int highBound = getHighBoundPos(); 267 final int middle = getMiddlePos(); 268 269 g.setColor(ColorUtil.mix(Color.blue, Color.white, false)); 270 g.drawRect(lowBound - 2, y, 2, h); 271 g.setColor(Color.blue); 272 g.fillRect(lowBound - 1, y + 1, 1, h - 1); 273 g.setColor(ColorUtil.mix(Color.red, Color.white, false)); 274 g.drawRect(highBound - 1, y, 2, h); 275 g.setColor(Color.red); 276 g.fillRect(highBound, y + 1, 1, h - 1); 277 g.setColor(ColorUtil.mix(Color.green, Color.white, false)); 278 g.drawRect(middle - 1, y + 10, 1, h - 10); 279 g.setColor(Color.green); 280 g.fillRect(middle, y + 11, 0, h - 11); 281 } 282 283 @Override 284 public void mouseClicked(MouseEvent e) 285 { 286 if (e.isConsumed()) 287 return; 288 289 if (e.getClickCount() == 2) 290 { 291 showRangeSettingDialog(); 292 e.consume(); 293 } 294 } 295 296 @Override 297 public void mouseEntered(MouseEvent e) 298 { 299 updateCursor(e.getPoint()); 300 } 301 302 @Override 303 public void mouseExited(MouseEvent e) 304 { 305 if (getCursor().getType() != Cursor.getDefaultCursor().getType()) 306 setCursor(Cursor.getDefaultCursor()); 307 308 // hide message 309 setMessage(""); 310 setPositionInfo(-1, -1, -1); 311 } 312 313 @Override 314 public void mousePressed(MouseEvent e) 315 { 316 if (e.isConsumed()) 317 return; 318 319 final Point pos = e.getPoint(); 320 321 if (EventUtil.isLeftMouseButton(e)) 322 { 323 if (isOverX(pos, getLowBoundPos())) 324 action = actionType.MODIFY_LOWBOUND; 325 else if (isOverX(pos, getHighBoundPos())) 326 action = actionType.MODIFY_HIGHBOUND; 327 else if (isOverX(pos, getMiddlePos())) 328 action = actionType.MODIFY_MIDDLE; 329 330 // show message 331 if (action != actionType.NULL) 332 { 333 if (EventUtil.isShiftDown(e)) 334 setMessage("GLOBAL MOVE"); 335 else 336 setMessage("Maintain 'Shift' for global move"); 337 } 338 339 updateCursor(e.getPoint()); 340 e.consume(); 341 } 342 else if (EventUtil.isRightMouseButton(e)) 343 { 344 showSettingPopup(pos); 345 e.consume(); 346 } 347 } 348 349 @Override 350 public void mouseReleased(MouseEvent e) 351 { 352 if (EventUtil.isLeftMouseButton(e)) 353 { 354 action = actionType.NULL; 355 356 updateCursor(e.getPoint()); 357 358 setMessage(""); 359 } 360 } 361 362 @Override 363 public void mouseDragged(MouseEvent e) 364 { 365 if (e.isConsumed()) 366 return; 367 368 final Point pos = e.getPoint(); 369 final boolean shift = EventUtil.isShiftDown(e); 370 371 mouseOnLeft = pos.x < (getWidth() / 2); 372 373 switch (action) 374 { 375 case MODIFY_LOWBOUND: 376 setLowBoundPos(pos.x); 377 // also modify others bounds 378 if (shift) 379 { 380 final double newLowBound = getLowBound(); 381 for (LUTChannel lc : lutChannel.getLut().getLutChannels()) 382 lc.setMin(newLowBound); 383 } 384 e.consume(); 385 break; 386 387 case MODIFY_HIGHBOUND: 388 setHighBoundPos(pos.x); 389 // also modify others bounds 390 if (shift) 391 { 392 final double newHighBound = getHighBound(); 393 for (LUTChannel lc : lutChannel.getLut().getLutChannels()) 394 lc.setMax(newHighBound); 395 } 396 e.consume(); 397 break; 398 399 case MODIFY_MIDDLE: 400 final double width = (getHighBound() - getLowBound()) / 2d; 401 final double value = pixelToData(pos.x); 402 final double min = value - width; 403 final double max = value + width; 404 405 // global change 406 if (shift) 407 { 408 for (LUTChannel lc : lutChannel.getLut().getLutChannels()) 409 { 410 if ((min >= lc.getMinBound()) && (max <= lc.getMaxBound())) 411 { 412 lc.setMin(min); 413 lc.setMax(max); 414 } 415 } 416 } 417 else 418 { 419 if ((min >= lutChannel.getMinBound()) && (max <= lutChannel.getMaxBound())) 420 { 421 setLowBound(min); 422 setHighBound(max); 423 } 424 } 425 e.consume(); 426 break; 427 } 428 429 // message 430 if (action != actionType.NULL) 431 { 432 if (shift) 433 setMessage("GLOBAL MOVE"); 434 else 435 setMessage("Maintain 'Shift' for global move"); 436 } 437 438 if (getBinNumber() > 0) 439 { 440 final int bin = pixelToBin(pos.x); 441 double index = pixelToData(pos.x); 442 final int value = getBinSize(bin); 443 444 // use integer index with integer data type 445 if (isIntegerType()) 446 index = Math.floor(index); 447 448 if (action == actionType.NULL) 449 { 450 final String valueText = "value : " + MathUtil.roundSignificant(index, 5, true); 451 final String pixelText = "pixel number : " + value; 452 453 setMessage(valueText + "\n" + pixelText); 454 // setToolTipText("<html>" + valueText + "<br>" + pixelText); 455 } 456 457 setPositionInfo(index, value, getAdjustedBinSize(bin)); 458 } 459 } 460 461 @Override 462 public void mouseMoved(MouseEvent e) 463 { 464 final Point pos = e.getPoint(); 465 466 mouseOnLeft = pos.x < (getWidth() / 2); 467 468 updateCursor(e.getPoint()); 469 470 if (getBinNumber() > 0) 471 { 472 final int bin = pixelToBin(pos.x); 473 double index = pixelToData(pos.x); 474 final int value = getBinSize(bin); 475 476 // use integer index with integer data type 477 if (isIntegerType()) 478 index = Math.round(index); 479 480 final String valueText = "value : " + MathUtil.roundSignificant(index, 5, true); 481 final String pixelText = "pixel number : " + value; 482 483 setMessage(valueText + "\n" + pixelText); 484 // setToolTipText("<html>" + valueText + "<br>" + pixelText); 485 486 setPositionInfo(index, value, getAdjustedBinSize(bin)); 487 } 488 } 489 490 @Override 491 public void mouseWheelMoved(MouseWheelEvent e) 492 { 493 494 } 495 } 496 497 /** 498 * 499 */ 500 private static final long serialVersionUID = -1236985071716650592L; 501 502 private static final int ISOVER_DEFAULT_MARGIN = 3; 503 504 /** 505 * associated viewer & lutChannel 506 */ 507 Viewer viewer; 508 LUTChannel lutChannel; 509 /** 510 * histogram 511 */ 512 private ScalerHistogramPanel histogram; 513 private boolean histoNeedRefresh; 514 515 /** 516 * listeners 517 */ 518 private final EventListenerList scalerMapPositionListeners; 519 520 /** 521 * internals 522 */ 523 private final Runnable histoUpdater; 524 String message; 525 private int retry; 526 527 /** 528 * 529 */ 530 public ScalerViewer(Viewer viewer, LUTChannel lutChannel) 531 { 532 super(); 533 534 this.viewer = viewer; 535 this.lutChannel = lutChannel; 536 537 message = ""; 538 retry = 0; 539 scalerMapPositionListeners = new EventListenerList(); 540 histoUpdater = new Runnable() 541 { 542 @Override 543 public void run() 544 { 545 try 546 { 547 // refresh histogram 548 refreshHistoDataInternal(); 549 } 550 catch (Exception e) 551 { 552 // just ignore error, it's permitted here 553 } 554 } 555 }; 556 557 histogram = new ScalerHistogramPanel(lutChannel.getScaler()); 558 // listen for need refresh event 559 histogram.addListener(new HistogramPanelListener() 560 { 561 @Override 562 public void histogramNeedRefresh(HistogramPanel source) 563 { 564 internalRequestHistoDataRefresh(); 565 } 566 }); 567 histoNeedRefresh = false; 568 569 setLayout(new BorderLayout()); 570 add(histogram, BorderLayout.CENTER); 571 validate(); 572 573 // force first refresh 574 internalRequestHistoDataRefresh(); 575 576 // add listeners 577 final Sequence sequence = viewer.getSequence(); 578 579 if (sequence != null) 580 sequence.addListener(this); 581 viewer.addListener(this); 582 lutChannel.addListener(this); 583 } 584 585 public void requestHistoDataRefresh() 586 { 587 internalRequestHistoDataRefresh(); 588 } 589 590 private boolean isHistoVisible() 591 { 592 if (!isValid()) 593 return false; 594 595 return getVisibleRect().intersects(histogram.getBounds()); 596 } 597 598 void internalRequestHistoDataRefresh() 599 { 600 if (isHistoVisible()) 601 refreshHistoData(); 602 else 603 histoNeedRefresh = true; 604 } 605 606 void updateHisto() 607 { 608 if (histoNeedRefresh) 609 { 610 refreshHistoData(); 611 histoNeedRefresh = false; 612 } 613 } 614 615 private void refreshHistoData() 616 { 617 // send refresh operation 618 ThreadUtil.bgRunSingle(histoUpdater); 619 } 620 621 // this method is called by processor, we don't mind about exception here 622 void refreshHistoDataInternal() 623 { 624 final Histogram histo = histogram.getHistogram(); 625 final Sequence seq = viewer.getSequence(); 626 627 histogram.reset(); 628 try 629 { 630 if (seq != null) 631 { 632 final int maxZ; 633 final int maxT; 634 int t = viewer.getPositionT(); 635 int z = viewer.getPositionZ(); 636 637 if (t != -1) 638 maxT = t; 639 else 640 { 641 t = 0; 642 maxT = seq.getSizeT() - 1; 643 } 644 645 if (z != -1) 646 maxZ = z; 647 else 648 { 649 z = 0; 650 maxZ = seq.getSizeZ() - 1; 651 } 652 653 final int c = lutChannel.getChannel(); 654 655 for (; t <= maxT; t++) 656 { 657 for (; z <= maxZ; z++) 658 { 659 final Object data = seq.getDataXY(t, z, c); 660 661 // need to test for empty sequence 662 if (data != null) 663 { 664 final DataType dataType = seq.getDataType_(); 665 final int len = Array.getLength(data); 666 667 for (int i = 0; i < len; i++) 668 { 669 if ((i & 0xFFF) == 0) 670 { 671 // need to be recalculated so don't waste time here... 672 if (ThreadUtil.hasWaitingBgSingleTask(histoUpdater)) 673 return; 674 } 675 676 histo.addValue(Array1DUtil.getValue(data, i, dataType)); 677 } 678 } 679 } 680 } 681 } 682 683 retry = 0; 684 } 685 catch (Exception e) 686 { 687 // just redo it later 688 if (retry++ < 3) 689 refreshHistoData(); 690 } 691 finally 692 { 693 // notify that histogram computation is done 694 histogram.done(); 695 696 // histogram changed in the meantime --> recompute 697 if (histo != histogram.getHistogram()) 698 refreshHistoData(); 699 } 700 } 701 702 /** 703 * @return the histogram 704 */ 705 public HistogramPanel getHistogram() 706 { 707 return histogram; 708 } 709 710 /** 711 * @return the histoData 712 */ 713 public double[] getHistoData() 714 { 715 return histogram.getHistogramData(); 716 } 717 718 /** 719 * @return the scaler 720 */ 721 public Scaler getScaler() 722 { 723 return lutChannel.getScaler(); 724 } 725 726 public double getLowBound() 727 { 728 return lutChannel.getMin(); 729 } 730 731 public double getHighBound() 732 { 733 return lutChannel.getMax(); 734 } 735 736 void setLowBound(double value) 737 { 738 lutChannel.setMin(value); 739 } 740 741 void setHighBound(double value) 742 { 743 lutChannel.setMax(value); 744 } 745 746 /** 747 * tasks to do on scaler changes 748 */ 749 public void onScalerChanged() 750 { 751 final Scaler s = getScaler(); 752 753 histogram.setMinMaxIntValues(s.getAbsLeftIn(), s.getAbsRightIn(), s.isIntegerData()); 754 755 // repaint component now as bounds may have changed 756 repaint(); 757 } 758 759 /** 760 * process on sequence change 761 */ 762 void onSequenceDataChanged() 763 { 764 final LUTViewer lutViewer = viewer.getLutViewer(); 765 766 // update histogram 767 if ((lutViewer != null) && lutViewer.getAutoRefreshHistogram()) 768 requestHistoDataRefresh(); 769 } 770 771 /** 772 * process on position changed 773 */ 774 private void onPositionChanged() 775 { 776 final LUTViewer lutViewer = viewer.getLutViewer(); 777 778 // update histogram 779 if ((lutViewer != null) && lutViewer.getAutoRefreshHistogram()) 780 requestHistoDataRefresh(); 781 } 782 783 /** 784 * @return the message 785 */ 786 public String getMessage() 787 { 788 return message; 789 } 790 791 /** 792 * @param value 793 * the message to set 794 */ 795 public void setMessage(String value) 796 { 797 if (!StringUtil.equals(message, value)) 798 { 799 message = value; 800 repaint(); 801 } 802 } 803 804 805 /** 806 * Should be called when histogram scaling type changed 807 */ 808 public void scaleTypeChanged(boolean log) 809 { 810 if (log) 811 histogram.setLogScaling(true); 812 else 813 histogram.setLogScaling(false); 814 } 815 816 /** 817 * show popup menu 818 */ 819 protected void showSettingPopup(final Point pos) 820 { 821 // rebuild menu 822 final JPopupMenu menu = new JPopupMenu("Actions"); 823 824 final JMenuItem refreshItem = new JMenuItem("Refresh now"); 825 refreshItem.addActionListener(new ActionListener() 826 { 827 @Override 828 public void actionPerformed(ActionEvent e) 829 { 830 requestHistoDataRefresh(); 831 } 832 }); 833 final JMenuItem setBoundsItem = new JMenuItem("Set range"); 834 setBoundsItem.addActionListener(new ActionListener() 835 { 836 @Override 837 public void actionPerformed(ActionEvent e) 838 { 839 showRangeSettingDialog(); 840 } 841 }); 842 final JMenuItem exportItem = new JMenuItem("Export to excel"); 843 exportItem.addActionListener(new ActionListener() 844 { 845 @Override 846 public void actionPerformed(ActionEvent e) 847 { 848 try 849 { 850 getHistogram().getHistogram().doXLSExport(); 851 } 852 catch (Exception e1) 853 { 854 MessageDialog.showDialog("Error", e1.getMessage(), MessageDialog.ERROR_MESSAGE); 855 } 856 } 857 }); 858 859 menu.add(refreshItem); 860 menu.add(setBoundsItem); 861 menu.add(exportItem); 862 863 menu.pack(); 864 menu.validate(); 865 866 // display menu 867 menu.show(this, pos.x, pos.y); 868 } 869 870 void showRangeSettingDialog() 871 { 872 final ScalerBoundsSettingDialog boundsSettingDialog = new ScalerBoundsSettingDialog(lutChannel); 873 874 boundsSettingDialog.pack(); 875 boundsSettingDialog.setLocationRelativeTo(this); 876 boundsSettingDialog.setVisible(true); 877 } 878 879 /** 880 * Add a listener 881 * 882 * @param listener 883 */ 884 public void addScalerPositionListener(ScalerPositionListener listener) 885 { 886 scalerMapPositionListeners.add(ScalerPositionListener.class, listener); 887 } 888 889 /** 890 * Remove a listener 891 * 892 * @param listener 893 */ 894 public void removeScalerPositionListener(ScalerPositionListener listener) 895 { 896 scalerMapPositionListeners.remove(ScalerPositionListener.class, listener); 897 } 898 899 /** 900 * mouse position on scaler info changed 901 */ 902 public void scalerPositionChanged(double index, int value, double normalizedValue) 903 { 904 for (ScalerPositionListener listener : scalerMapPositionListeners.getListeners(ScalerPositionListener.class)) 905 listener.positionChanged(index, value, normalizedValue); 906 } 907 908 @Override 909 public void lutChannelChanged(LUTChannelEvent event) 910 { 911 if (event.getType() == LUTChannelEventType.SCALER_CHANGED) 912 onScalerChanged(); 913 } 914 915 @Override 916 public void viewerChanged(ViewerEvent event) 917 { 918 if (event.getType() == ViewerEventType.POSITION_CHANGED) 919 onPositionChanged(); 920 } 921 922 @Override 923 public void viewerClosed(Viewer viewer) 924 { 925 viewer.removeListener(this); 926 } 927 928 @Override 929 public void sequenceChanged(SequenceEvent sequenceEvent) 930 { 931 if (sequenceEvent.getSourceType() == SequenceEventSourceType.SEQUENCE_DATA) 932 onSequenceDataChanged(); 933 } 934 935 @Override 936 public void sequenceClosed(Sequence sequence) 937 { 938 sequence.removeListener(this); 939 } 940}