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.action.IcyAbstractAction;
022import icy.gui.component.BorderedPanel;
023import icy.image.colormap.IcyColorMap;
024import icy.image.colormap.IcyColorMap.IcyColorMapType;
025import icy.image.colormap.IcyColorMapComponent;
026import icy.image.colormap.IcyColorMapComponent.ControlPoint;
027import icy.image.lut.LUT.LUTChannel;
028import icy.image.lut.LUT.LUTChannelEvent;
029import icy.image.lut.LUT.LUTChannelEvent.LUTChannelEventType;
030import icy.image.lut.LUT.LUTChannelListener;
031import icy.resource.icon.IcyIcon;
032import icy.util.ColorUtil;
033import icy.util.EventUtil;
034
035import java.awt.BasicStroke;
036import java.awt.Color;
037import java.awt.Cursor;
038import java.awt.Dimension;
039import java.awt.Graphics;
040import java.awt.Graphics2D;
041import java.awt.Point;
042import java.awt.RenderingHints;
043import java.awt.event.ActionEvent;
044import java.awt.event.ActionListener;
045import java.awt.event.KeyEvent;
046import java.awt.event.MouseEvent;
047import java.awt.event.MouseListener;
048import java.awt.event.MouseMotionListener;
049import java.awt.geom.GeneralPath;
050import java.awt.geom.Path2D;
051import java.awt.geom.Point2D;
052import java.util.ArrayList;
053import java.util.EventListener;
054import java.util.List;
055
056import javax.swing.ActionMap;
057import javax.swing.BorderFactory;
058import javax.swing.InputMap;
059import javax.swing.JComponent;
060import javax.swing.JMenuItem;
061import javax.swing.JPopupMenu;
062import javax.swing.KeyStroke;
063import javax.swing.event.EventListenerList;
064
065/**
066 * @author stephane
067 */
068public class ColormapViewer extends BorderedPanel implements MouseListener, MouseMotionListener, LUTChannelListener
069{
070    private enum ActionType
071    {
072        NULL, MODIFY_CONTROLPOINT
073    }
074
075    public interface ColormapPositionListener extends EventListener
076    {
077        public void positionChanged(int index, int value);
078    }
079
080    /**
081     * 
082     */
083    private static final long serialVersionUID = -8338817004756013113L;
084
085    private static final int POINT_SIZE = 10;
086    private static final int LINE_SIZE = 3;
087    private static final int BORDER_WIDTH = 4;
088    private static final int BORDER_HEIGHT = 4;
089
090    private static final int MIN_INDEX = 0;
091    private static final int MAX_INDEX = IcyColorMap.MAX_INDEX;
092    private static final int MIN_VALUE = 0;
093    private static final int MAX_VALUE = IcyColorMap.MAX_LEVEL;
094
095    /**
096     * associated {@link LUTChannel}
097     */
098    private final LUTChannel lutChannel;
099
100    /**
101     * alpha enabled
102     */
103    private boolean alphaEnabled;
104
105    /**
106     * gui
107     */
108    private final JPopupMenu menu;
109
110    /**
111     * listeners
112     */
113    private final EventListenerList colorMapPositionListeners;
114    /**
115     * cached
116     */
117    final IcyColorMap colormap;
118    /**
119     * internals
120     */
121    private float pixToIndexRatio;
122    private float indexToPixRatio;
123    private float pixToValueRatio;
124    private float valueToPixRatio;
125    private ActionType action;
126    private IcyColorMapComponent currentComponent;
127    ControlPoint currentControlPoint;
128
129    public ColormapViewer(LUTChannel lutChannel)
130    {
131        super();
132
133        // dimension (don't change or you will regret !)
134        setMinimumSize(new Dimension(100, 100));
135        setPreferredSize(new Dimension(240, 100));
136        setMaximumSize(new Dimension(Short.MAX_VALUE, Short.MAX_VALUE));
137        // faster draw
138        setOpaque(true);
139        // set border
140        setBorder(BorderFactory.createEmptyBorder(BORDER_HEIGHT, BORDER_WIDTH, BORDER_HEIGHT, BORDER_WIDTH));
141        // for the input map
142        setFocusable(true);
143
144        this.lutChannel = lutChannel;
145        colormap = lutChannel.getColorMap();
146
147        colorMapPositionListeners = new EventListenerList();
148
149        // gui
150        menu = new JPopupMenu();
151
152        alphaEnabled = true;
153
154        action = ActionType.NULL;
155        currentComponent = null;
156        currentControlPoint = null;
157
158        // calculate ratios
159        updateRatios();
160
161        // we can't get key events without focus and having focus here is a problem
162        // as we can have externalized windows...
163        // addKeyListener(this);
164
165        // add listeners
166        addMouseListener(this);
167        addMouseMotionListener(this);
168
169        buildActionMap();
170    }
171
172    @Override
173    public void addNotify()
174    {
175        super.addNotify();
176
177        // add listeners
178        lutChannel.addListener(this);
179    }
180
181    @Override
182    public void removeNotify()
183    {
184        super.removeNotify();
185
186        // remove listeners
187        lutChannel.removeListener(this);
188    }
189
190    void buildActionMap()
191    {
192        final InputMap imap = getInputMap(JComponent.WHEN_FOCUSED);
193        final ActionMap amap = getActionMap();
194
195        imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), "delete");
196
197        amap.put("delete", new IcyAbstractAction("delete", (IcyIcon) null, "Delete control point", KeyEvent.VK_DELETE,
198                0)
199        {
200            /**
201             * 
202             */
203            private static final long serialVersionUID = 2375566345981567387L;
204
205            @Override
206            protected boolean doAction(ActionEvent e)
207            {
208                // remove current control point
209                if (currentControlPoint != null)
210                {
211                    currentControlPoint.remove();
212                    return true;
213                }
214
215                return false;
216            }
217        });
218    }
219
220    /**
221     * @return the colormap
222     */
223    public IcyColorMap getColormap()
224    {
225        return colormap;
226    }
227
228    /**
229     * Translate index to pixel
230     * 
231     * @param index
232     */
233    public int indexToPix(int index)
234    {
235        final int clientX = getClientX();
236        final int pix = (int) (index * indexToPixRatio) + clientX;
237        return Math.max(Math.min(pix, getClientWidth() + clientX), clientX);
238    }
239
240    /**
241     * Translate pixel to index
242     * 
243     * @param pixel
244     */
245    public int pixToIndex(int pixel)
246    {
247        final int ind = (int) ((pixel - getClientX()) * pixToIndexRatio);
248        return Math.max(Math.min(ind, MAX_INDEX), MIN_INDEX);
249    }
250
251    /**
252     * Translate value to pixel
253     * 
254     * @param value
255     * @return pixel for specified value
256     */
257    public int valueToPix(int value)
258    {
259        final int clientY = getClientY();
260        final int hl = (getClientHeight() - 1) + clientY;
261        final int pix = hl - (int) (value * valueToPixRatio);
262        return Math.max(Math.min(pix, hl), clientY);
263    }
264
265    /**
266     * Translate pixel to value
267     * 
268     * @param pixel
269     * @return value for specified pixel
270     */
271    public int pixToValue(int pixel)
272    {
273        final int hl = (getClientHeight() - 1) + getClientY();
274        final int value = (int) ((hl - pixel) * pixToValueRatio);
275        return Math.max(Math.min(value, MAX_VALUE), MIN_VALUE);
276    }
277
278    @Override
279    protected void paintComponent(Graphics g)
280    {
281        super.paintComponent(g);
282
283        // we do it here as componentResized event occurs after paint (and it is not time consuming)
284        updateRatios();
285
286        final Graphics2D g2 = (Graphics2D) g.create();
287        final int w = getWidth();
288        final int h = getHeight();
289
290        // draw colored background mesh
291        for (int i = 0; i < w; i++)
292        {
293            // get current color from pixel position
294            final Color curColor = getColorFromPixel(i);
295            final Color grayMixed = ColorUtil.mixOver(Color.gray, curColor);
296            final Color whiteMixed = ColorUtil.mixOver(Color.white, curColor);
297
298            for (int j = 0; j < h; j += 16)
299            {
300                // set graphics color
301                if (((i ^ j) & 16) != 0)
302                    g2.setColor(grayMixed);
303                else
304                    g2.setColor(whiteMixed);
305
306                g2.drawLine(i, j, i, j + 15);
307            }
308        }
309
310        if (alphaEnabled)
311            drawColormapBand(g2, colormap.alpha);
312
313        switch (colormap.getType())
314        {
315            case RGB:
316                drawColormapBand(g2, colormap.blue);
317                drawColormapBand(g2, colormap.green);
318                drawColormapBand(g2, colormap.red);
319                break;
320
321            case GRAY:
322                drawColormapBand(g2, colormap.gray);
323                break;
324        }
325
326        g2.setColor(Color.black);
327        g2.drawRect(0, 0, w - 1, h - 1);
328
329        g2.dispose();
330    }
331
332    private void drawColormapBand(Graphics2D g, IcyColorMapComponent band)
333    {
334        drawColormap(g, band);
335        drawControlPoints(g, band);
336    }
337
338    private void drawColormap(Graphics2D g, IcyColorMapComponent cmc)
339    {
340        final Graphics2D g2 = (Graphics2D) g.create();
341
342        // enable anti alias for better rendering
343        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
344
345        GeneralPath polyline = null;
346
347        // the LUT is defined directly, without control points
348        if (cmc.isRawData())
349        {
350            final int x = getClientX();
351            final int w = getClientWidth();
352
353            polyline = new GeneralPath(Path2D.WIND_EVEN_ODD, w);
354
355            int intensity = valueToPix(cmc.map[pixToIndex(0)]);
356            polyline.moveTo(x, intensity);
357
358            for (int i = x; i < (w + x); i++)
359            {
360                intensity = valueToPix(cmc.map[pixToIndex(i)]);
361                polyline.lineTo(i, intensity);
362            }
363        }
364        else
365        // the LUT is defined through control points, use them.
366        {
367            polyline = new GeneralPath(Path2D.WIND_EVEN_ODD, cmc.getControlPointCount());
368
369            ArrayList<ControlPoint> controlPoints = cmc.getControlPoints();
370            int x = getPixelPosX(controlPoints.get(0));
371            int y = getPixelPosY(controlPoints.get(0));
372            polyline.moveTo(x, y);
373
374            for (int i = 1; i < cmc.getControlPointCount(); i++)
375            {
376                x = getPixelPosX(controlPoints.get(i));
377                y = getPixelPosY(controlPoints.get(i));
378                polyline.lineTo(x, y);
379            }
380        }
381
382        if (isFocused(cmc))
383            g2.setColor(Color.lightGray);
384        else
385            g2.setColor(Color.black);
386        g2.setStroke(new BasicStroke(LINE_SIZE + 1));
387        g2.draw(polyline);
388
389        g2.setColor(getColor(cmc));
390        g2.setStroke(new BasicStroke(LINE_SIZE));
391        g2.draw(polyline);
392
393        g2.dispose();
394    }
395
396    private void drawControlPoints(Graphics2D g, IcyColorMapComponent cmc)
397    {
398        final Graphics2D g2 = (Graphics2D) g.create();
399
400        // enable anti alias for better rendering
401        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
402
403        final int offset_oval = POINT_SIZE / 2;
404
405        // define color
406        final Color color = getColor(cmc);
407        final List<ControlPoint> controlPoints;
408
409        synchronized (cmc.getControlPoints())
410        {
411            controlPoints = new ArrayList<IcyColorMapComponent.ControlPoint>(cmc.getControlPoints());
412        }
413
414        for (ControlPoint controlPoint : controlPoints)
415        {
416            final int x = getPixelPosX(controlPoint);
417            final int y = getPixelPosY(controlPoint);
418
419            if (controlPoint.isFixed())
420            {
421                // draw square control point
422                g2.setColor(color);
423                g2.fillRect(x - (offset_oval - 1), y - (offset_oval - 1), POINT_SIZE - 2, POINT_SIZE - 2);
424                g2.setColor(Color.darkGray);
425                g2.drawRect(x - (offset_oval - 1), y - (offset_oval - 1), POINT_SIZE - 2, POINT_SIZE - 2);
426                if (isFocused(controlPoint))
427                    g2.setColor(Color.white);
428                else
429                    g2.setColor(Color.black);
430                g2.drawRect(x - (offset_oval - 0), y - (offset_oval - 0), POINT_SIZE - 0, POINT_SIZE - 0);
431            }
432            else
433            {
434                // draw round control point
435                g2.setColor(color);
436                g2.fillOval(x - (offset_oval - 1), y - (offset_oval - 1), POINT_SIZE - 2, POINT_SIZE - 2);
437                g2.setColor(Color.darkGray);
438                g2.drawOval(x - (offset_oval - 1), y - (offset_oval - 1), POINT_SIZE - 2, POINT_SIZE - 2);
439                if (isFocused(controlPoint))
440                    g2.setColor(Color.white);
441                else
442                    g2.setColor(Color.black);
443                g2.drawOval(x - (offset_oval - 0), y - (offset_oval - 0), POINT_SIZE - 0, POINT_SIZE - 0);
444            }
445        }
446
447        g2.dispose();
448    }
449
450    public boolean isAlphaEnabled()
451    {
452        return alphaEnabled;
453    }
454
455    public void setAlphaEnabled(boolean value)
456    {
457        if (alphaEnabled != value)
458        {
459            alphaEnabled = value;
460
461            if (!value)
462                colormap.alpha.removeAllControlPoint();
463
464            repaint();
465        }
466    }
467
468    /**
469     * set current controller or control point
470     */
471    public void setCurrentElements(IcyColorMapComponent cmc, ControlPoint cp)
472    {
473        if (currentControlPoint != cp)
474        {
475            currentControlPoint = cp;
476            repaint();
477        }
478
479        if (currentComponent != cmc)
480        {
481            currentComponent = cmc;
482            repaint();
483        }
484
485        final int cursor;
486
487        if ((cmc != null) || (cp != null))
488            cursor = Cursor.HAND_CURSOR;
489        else
490            cursor = Cursor.DEFAULT_CURSOR;
491
492        // set cursor only only if different
493        if (getCursor().getType() != cursor)
494            setCursor(new Cursor(cursor));
495    }
496
497    private boolean isFocused(IcyColorMapComponent cmc)
498    {
499        return (cmc != null) && (currentComponent == cmc);
500    }
501
502    private boolean isFocused(ControlPoint cp)
503    {
504        return (cp != null) && (currentControlPoint == cp);
505    }
506
507    /**
508     * return the final color for specified index
509     */
510    public Color getColor(int index)
511    {
512        return colormap.getColor(index);
513    }
514
515    /**
516     * get color of specified band
517     */
518    public Color getColor(IcyColorMapComponent cmc)
519    {
520        if (cmc == colormap.red)
521            return Color.red;
522        if (cmc == colormap.green)
523            return Color.green;
524        if (cmc == colormap.blue)
525            return Color.blue;
526        if (cmc == colormap.gray)
527            return Color.gray;
528        if (cmc == colormap.alpha)
529            return Color.white;
530
531        return Color.black;
532    }
533
534    /**
535     * return the final color for specified pixel position
536     */
537    public Color getColorFromPixel(int pixel)
538    {
539        return getColor(pixToIndex(pixel));
540    }
541
542    /**
543     * update ratios for data <--> pix conversion
544     */
545    private void updateRatios()
546    {
547        final int w = getClientWidth();
548        final int h = getClientHeight();
549
550        if (w <= 0)
551        {
552            indexToPixRatio = 0f;
553            pixToIndexRatio = 0f;
554        }
555        else
556        {
557            indexToPixRatio = (float) (w - 1) / (float) (IcyColorMap.SIZE - 1);
558            if (indexToPixRatio != 0f)
559                pixToIndexRatio = 1f / indexToPixRatio;
560            else
561                pixToIndexRatio = 0f;
562        }
563
564        if (h <= 0)
565        {
566            valueToPixRatio = 0f;
567            pixToValueRatio = 0f;
568        }
569        else
570        {
571            valueToPixRatio = (float) (h - 1) / (float) (IcyColorMap.MAX_LEVEL);
572            if (valueToPixRatio != 0f)
573                pixToValueRatio = 1f / valueToPixRatio;
574            else
575                pixToValueRatio = 0f;
576        }
577    }
578
579    // /**
580    // * Check if point is over any control point
581    // *
582    // * @param pos
583    // * point
584    // * @return boolean
585    // */
586    // private boolean isOverControlPoint(Point pos)
587    // {
588    // boolean result = false;
589    // final IcyColorMapType type = colormap.getType();
590    //
591    // // check only if alpha enabled
592    // if (alphaEnabled)
593    // result = result || isOverControlPoint(colormap.alpha, pos);
594    //
595    // // test according to display order (ARGB)
596    // if (type == IcyColorMapType.RGB)
597    // result = result || isOverControlPoint(colormap.red, pos) ||
598    // isOverControlPoint(colormap.green, pos)
599    // || isOverControlPoint(colormap.blue, pos);
600    // if (type == IcyColorMapType.GRAY)
601    // result = result || isOverControlPoint(colormap.gray, pos);
602    //
603    // return result;
604    // }
605
606    // /**
607    // * Check if point is over any control point from this band
608    // *
609    // * @param pos
610    // * point
611    // * @return boolean
612    // */
613    // private boolean isOverControlPoint(IcyColorMapComponent cmc, Point pos)
614    // {
615    // for (ControlPoint cp : cmc.getControlPoints())
616    // if (isOverlapped(cp, pos))
617    // return true;
618    //
619    // return false;
620    // }
621
622    /**
623     * Check if point is over any point in colormap
624     * 
625     * @param pos
626     *        point
627     * @return boolean
628     */
629    public boolean isOverlapped(IcyColorMapComponent cmc, Point pos)
630    {
631        final int index_min = Math.max(0, pixToIndex(pos.x - LINE_SIZE));
632        final int index_max = Math.min(IcyColorMap.MAX_INDEX, pixToIndex(pos.x + LINE_SIZE));
633
634        for (int ind = index_min; ind < index_max; ind++)
635            if (Point2D.distance(pos.x, pos.y, indexToPix(ind), valueToPix(cmc.map[ind])) <= (LINE_SIZE + 1))
636                return true;
637
638        return false;
639    }
640
641    /**
642     * Return true if pixel (x, y) is over the control point
643     * 
644     * @param p
645     *        point
646     * @return boolean
647     */
648    public boolean isOverlapped(ControlPoint cp, Point p)
649    {
650        return getDistance(cp, p) <= (POINT_SIZE / 2);
651    }
652
653    /**
654     * Return distance between control point and the specified point
655     * 
656     * @param p
657     *        point
658     * @return boolean
659     */
660    public double getDistance(ControlPoint cp, Point p)
661    {
662        return Point2D.distance(p.x, p.y, indexToPix(cp.getIndex()), valueToPix(cp.getValue()));
663    }
664
665    /**
666     * Set position from a pixel position
667     * 
668     * @param x
669     * @param y
670     */
671    public void setPixelPosition(ControlPoint cp, int x, int y)
672    {
673        cp.setPosition(pixToIndex(x), pixToValue(y));
674    }
675
676    /**
677     * Get X pixel position
678     * 
679     * @return X pixel position
680     */
681    public int getPixelPosX(ControlPoint cp)
682    {
683        return indexToPix(cp.getIndex());
684    }
685
686    /**
687     * Get Y pixel position
688     * 
689     * @return Y pixel position
690     */
691    public int getPixelPosY(ControlPoint cp)
692    {
693        return valueToPix(cp.getValue());
694    }
695
696    /**
697     * Find the overlapped colormap band by specified point
698     * 
699     * @param pos
700     *        point
701     * @return ColormapController
702     */
703    private IcyColorMapComponent getOverlappedColormapController(Point pos)
704    {
705        final IcyColorMapType type = colormap.getType();
706
707        // test according to display order (ARGB)
708        if (type == IcyColorMapType.RGB)
709        {
710            if (isOverlapped(colormap.red, pos))
711                return colormap.red;
712            if (isOverlapped(colormap.green, pos))
713                return colormap.green;
714            if (isOverlapped(colormap.blue, pos))
715                return colormap.blue;
716        }
717        if (type == IcyColorMapType.GRAY)
718            if (isOverlapped(colormap.gray, pos))
719                return colormap.gray;
720
721        // check only if alpha enabled
722        if (alphaEnabled)
723            if (isOverlapped(colormap.alpha, pos))
724                return colormap.alpha;
725
726        return null;
727    }
728
729    /**
730     * Find the closest overlapped control point by specified point
731     * 
732     * @param pos
733     *        point
734     * @return ControlPoint
735     */
736    private ControlPoint getClosestOverlappedControlPoint(Point pos)
737    {
738        ControlPoint point;
739        final IcyColorMapType type = colormap.getType();
740
741        // test according to display order (RGBA)
742        if (type == IcyColorMapType.RGB)
743        {
744            point = getClosestOverlappedControlPoint(colormap.red, pos);
745            if (point != null)
746                return point;
747            point = getClosestOverlappedControlPoint(colormap.green, pos);
748            if (point != null)
749                return point;
750            point = getClosestOverlappedControlPoint(colormap.blue, pos);
751            if (point != null)
752                return point;
753        }
754        if (type == IcyColorMapType.GRAY)
755        {
756            point = getClosestOverlappedControlPoint(colormap.gray, pos);
757            if (point != null)
758                return point;
759        }
760
761        // check only if alpha enabled
762        if (alphaEnabled)
763        {
764            point = getClosestOverlappedControlPoint(colormap.alpha, pos);
765            if (point != null)
766                return point;
767        }
768
769        return null;
770    }
771
772    /**
773     * Find the closest overlapped control point by specified point
774     * 
775     * @param pos
776     *        point
777     * @return ControlPoint
778     */
779    private ControlPoint getClosestOverlappedControlPoint(IcyColorMapComponent cmc, Point pos)
780    {
781        final List<ControlPoint> overlapped = new ArrayList<ControlPoint>();
782
783        // add all overlapped control points to the list
784        for (ControlPoint point : cmc.getControlPoints())
785            if (isOverlapped(point, pos))
786                overlapped.add(point);
787
788        final int size = overlapped.size();
789
790        // we have at least one overlapped control point ?
791        if (size > 0)
792        {
793            // find the closest from the specified position
794            ControlPoint closestPoint = overlapped.get(0);
795            double minDist = getDistance(closestPoint, pos);
796
797            for (int i = 1; i < size; i++)
798            {
799                final ControlPoint currentPoint = overlapped.get(i);
800                final double curDist = getDistance(currentPoint, pos);
801
802                if (curDist < minDist)
803                {
804                    closestPoint = currentPoint;
805                    minDist = curDist;
806                }
807            }
808
809            return closestPoint;
810        }
811
812        return null;
813    }
814
815    /**
816     * Set a control point to specified index and value
817     * 
818     * @param pos
819     *        position
820     */
821    ControlPoint setControlPoint(IcyColorMapComponent comp, Point pos)
822    {
823        return comp.setControlPoint(pixToIndex(pos.x), pixToValue(pos.y));
824    }
825
826    /**
827     * show popup menu
828     */
829    private void showPopupMenu(final Point pos)
830    {
831        // rebuild menu
832        menu.removeAll();
833
834        // keep a copy of current control point
835        final ControlPoint cp = currentControlPoint;
836
837        if (cp != null)
838        {
839            // fixed control point --> no popup menu
840            if (cp.isFixed())
841                return;
842
843            final JMenuItem removeItem = new JMenuItem("remove (Shift + Click)");
844
845            removeItem.addActionListener(new ActionListener()
846            {
847                @Override
848                public void actionPerformed(ActionEvent e)
849                {
850                    // remove the control point
851                    cp.remove();
852                }
853            });
854
855            menu.add(removeItem);
856        }
857        else
858        {
859            final IcyColorMapType type = colormap.getType();
860
861            if (type == IcyColorMapType.GRAY)
862            {
863                final JMenuItem addCPItem = new JMenuItem("add Gray point");
864
865                addCPItem.addActionListener(new ActionListener()
866                {
867                    @Override
868                    public void actionPerformed(ActionEvent e)
869                    {
870                        // add gray control point
871                        setControlPoint(colormap.gray, pos);
872                    }
873                });
874
875                menu.add(addCPItem);
876            }
877            if (type == IcyColorMapType.RGB)
878            {
879                final JMenuItem addCRPItem = new JMenuItem("add Red point");
880                final JMenuItem addCGPItem = new JMenuItem("add Green point");
881                final JMenuItem addCBPItem = new JMenuItem("add Blue point");
882
883                addCRPItem.addActionListener(new ActionListener()
884                {
885                    @Override
886                    public void actionPerformed(ActionEvent e)
887                    {
888                        // add red control point
889                        setControlPoint(colormap.red, pos);
890                    }
891                });
892                addCGPItem.addActionListener(new ActionListener()
893                {
894                    @Override
895                    public void actionPerformed(ActionEvent e)
896                    {
897                        // add green control point
898                        setControlPoint(colormap.green, pos);
899                    }
900                });
901                addCBPItem.addActionListener(new ActionListener()
902                {
903                    @Override
904                    public void actionPerformed(ActionEvent e)
905                    {
906                        // add blue control point
907                        setControlPoint(colormap.blue, pos);
908                    }
909                });
910
911                menu.add(addCRPItem);
912                menu.add(addCGPItem);
913                menu.add(addCBPItem);
914            }
915
916            if (alphaEnabled)
917            {
918                final JMenuItem addAlphaCPItem = new JMenuItem("add Alpha point");
919
920                addAlphaCPItem.addActionListener(new ActionListener()
921                {
922                    @Override
923                    public void actionPerformed(ActionEvent e)
924                    {
925                        // add alpha control point
926                        setControlPoint(colormap.alpha, pos);
927                    }
928                });
929
930                menu.add(addAlphaCPItem);
931            }
932        }
933
934        menu.pack();
935        menu.validate();
936
937        // display menu
938        menu.show(this, pos.x, pos.y);
939    }
940
941    /**
942     * update current controller and control point from mouse position
943     */
944    private void updateCurrentElements(Point pos)
945    {
946        final IcyColorMapComponent cmc;
947        // by default we search for an overlapped control point
948        final ControlPoint cp = getClosestOverlappedControlPoint(pos);
949
950        // if no overlapped control point we search for overlapped controller
951        if (cp == null)
952            cmc = getOverlappedColormapController(pos);
953        else
954            cmc = null;
955
956        // define current elements
957        setCurrentElements(cmc, cp);
958    }
959
960    /**
961     * update colormap position info
962     */
963    private void updateColormapPositionInfo(Point pos)
964    {
965        final ControlPoint cp;
966        final int index;
967        final int value;
968
969        if (action != ActionType.NULL)
970            cp = currentControlPoint;
971        else
972            cp = getClosestOverlappedControlPoint(pos);
973
974        if (cp != null)
975        {
976            index = cp.getIndex();
977            value = cp.getValue();
978        }
979        else
980        {
981            index = pixToIndex(pos.x);
982            value = pixToValue(pos.y);
983        }
984
985        // setToolTipText("<html>" + "index : " + index + "<br>" + "value : " + value);
986
987        colormapPositionChanged(index, value);
988    }
989
990    /**
991     * process on colormap change
992     */
993    public void onColormapChanged()
994    {
995        // repaint the colormap
996        repaint();
997    }
998
999    /**
1000     * Add a listener
1001     * 
1002     * @param listener
1003     */
1004    public void addColormapPositionListener(ColormapPositionListener listener)
1005    {
1006        colorMapPositionListeners.add(ColormapPositionListener.class, listener);
1007    }
1008
1009    /**
1010     * Remove a listener
1011     * 
1012     * @param listener
1013     */
1014    public void removeColormapPositionListener(ColormapPositionListener listener)
1015    {
1016        colorMapPositionListeners.remove(ColormapPositionListener.class, listener);
1017    }
1018
1019    /**
1020     * mouse position on colormap info changed
1021     */
1022    public void colormapPositionChanged(int index, int value)
1023    {
1024        for (ColormapPositionListener listener : colorMapPositionListeners.getListeners(ColormapPositionListener.class))
1025            listener.positionChanged(index, value);
1026    }
1027
1028    @Override
1029    public void lutChannelChanged(LUTChannelEvent e)
1030    {
1031        if (e.getType() == LUTChannelEventType.COLORMAP_CHANGED)
1032            onColormapChanged();
1033    }
1034
1035    @Override
1036    public void mouseClicked(MouseEvent e)
1037    {
1038        // nothing to do here
1039    }
1040
1041    @Override
1042    public void mouseEntered(MouseEvent e)
1043    {
1044        // // get the focus while mouse is on the component
1045        // setFocusable(true);
1046        // requestFocus();
1047        //
1048        // repaint();
1049
1050        // KeyboardFocusManager.getCurrentKeyboardFocusManager().downFocusCycle(this);
1051    }
1052
1053    @Override
1054    public void mouseExited(MouseEvent e)
1055    {
1056        // clear position info
1057        colormapPositionChanged(-1, -1);
1058        // unfocus if no action
1059        if (action == ActionType.NULL)
1060            setCurrentElements(null, null);
1061
1062        // KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent(this);
1063
1064        // // remove focus
1065        // setFocusable(false);
1066        //
1067        // // set focus back to last active viewer
1068        // final Viewer viewer = Icy.getMainInterface().getActiveViewer();
1069        // if (viewer != null)
1070        // viewer.requestFocus();
1071        //
1072        // repaint();
1073    }
1074
1075    @Override
1076    public void mousePressed(MouseEvent e)
1077    {
1078        final Point pos = e.getPoint();
1079
1080        if (EventUtil.isLeftMouseButton(e))
1081        {
1082            // we have a selected control point ?
1083            if (currentControlPoint != null)
1084            {
1085                // Shift pressed --> remove control point
1086                if (EventUtil.isShiftDown(e))
1087                    currentControlPoint.remove();
1088                // else we start modification
1089                else
1090                    action = ActionType.MODIFY_CONTROLPOINT;
1091            }
1092            // we have a selected controller ?
1093            else if (currentComponent != null)
1094            {
1095                action = ActionType.MODIFY_CONTROLPOINT;
1096                // add a new control point to the controller which become the active control point
1097                setCurrentElements(null, setControlPoint(currentComponent, pos));
1098            }
1099        }
1100        else if (EventUtil.isRightMouseButton(e))
1101        {
1102            showPopupMenu(pos);
1103        }
1104    }
1105
1106    @Override
1107    public void mouseReleased(MouseEvent e)
1108    {
1109        if (EventUtil.isLeftMouseButton(e))
1110        {
1111            action = ActionType.NULL;
1112            updateCurrentElements(e.getPoint());
1113        }
1114    }
1115
1116    @Override
1117    public void mouseDragged(MouseEvent e)
1118    {
1119        final Point pos = e.getPoint();
1120
1121        switch (action)
1122        {
1123            case MODIFY_CONTROLPOINT:
1124                setPixelPosition(currentControlPoint, pos.x, pos.y);
1125                break;
1126        }
1127
1128        updateColormapPositionInfo(pos);
1129    }
1130
1131    @Override
1132    public void mouseMoved(MouseEvent e)
1133    {
1134        final Point pos = e.getPoint();
1135
1136        updateCurrentElements(pos);
1137        updateColormapPositionInfo(pos);
1138    }
1139}