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}