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 it under the terms of the GNU General
007 * Public License as
008 * published by the Free Software Foundation, either version 3 of the License, or (at your option)
009 * any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
012 * implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
014 * details.
015 * 
016 * You should have received a copy of the GNU General Public License along with Icy. If not, see
017 * <http://www.gnu.org/licenses/>.
018 */
019package icy.gui.lut;
020
021import icy.gui.component.CheckTabbedPane;
022import icy.gui.component.button.IcyButton;
023import icy.gui.dialog.MessageDialog;
024import icy.gui.lut.abstract_.IcyLutViewer;
025import icy.gui.util.GuiUtil;
026import icy.gui.viewer.Viewer;
027import icy.image.colormap.IcyColorMap;
028import icy.image.colormap.IcyColorMap.IcyColorMapType;
029import icy.image.colormap.IcyColorMapEvent;
030import icy.image.colormap.IcyColorMapListener;
031import icy.image.colormap.LinearColorMap;
032import icy.image.lut.LUT;
033import icy.image.lut.LUT.LUTChannel;
034import icy.math.Scaler;
035import icy.preferences.ApplicationPreferences;
036import icy.preferences.XMLPreferences;
037import icy.resource.ResourceUtil;
038import icy.resource.icon.IcyIcon;
039import icy.sequence.Sequence;
040import icy.sequence.SequenceEvent;
041import icy.sequence.SequenceListener;
042import icy.system.thread.ThreadUtil;
043import icy.type.DataType;
044import icy.util.StringUtil;
045
046import java.awt.BorderLayout;
047import java.awt.event.ActionEvent;
048import java.awt.event.ActionListener;
049import java.util.ArrayList;
050import java.util.List;
051
052import javax.swing.Box;
053import javax.swing.ButtonGroup;
054import javax.swing.JCheckBox;
055import javax.swing.JRadioButton;
056import javax.swing.JTabbedPane;
057import javax.swing.SwingConstants;
058import javax.swing.event.ChangeEvent;
059import javax.swing.event.ChangeListener;
060
061public class LUTViewer extends IcyLutViewer implements IcyColorMapListener, SequenceListener
062{
063    private static final long serialVersionUID = 8385018166371243663L;
064
065    /**
066     * pref id
067     */
068    private static final String PREF_ID_HISTO = "gui.histo";
069
070    private static final String ID_AUTO_REFRESH = "autoRefresh";
071    private static final String ID_AUTO_BOUNDS = "autoBounds";
072    private static final String ID_LOG_VIEW = "logView";
073
074    /**
075     * gui
076     */
077    final CheckTabbedPane bottomPane;
078
079    final JCheckBox autoRefreshHistoCheckBox;
080    final JCheckBox autoBoundsCheckBox;
081    final ButtonGroup scaleGroup;
082    final JRadioButton logButton;
083    final JRadioButton linearButton;
084    final IcyButton exportXLSButton;
085
086    /**
087     * data
088     */
089    final List<LUTChannelViewer> lutChannelViewers;
090
091    /**
092     * preferences
093     */
094    final XMLPreferences pref;
095
096    final Runnable boundsUpdater;
097    final Runnable channelNameUpdater;
098    final Runnable channelEnableUpdater;
099    final Runnable channelTabColorUpdater;
100
101    public LUTViewer(Viewer viewer, LUT lut)
102    {
103        super(viewer, lut);
104
105        pref = ApplicationPreferences.getPreferences().node(PREF_ID_HISTO);
106
107        boundsUpdater = new Runnable()
108        {
109            @Override
110            public void run()
111            {
112                final Sequence sequence = getSequence();
113
114                if (sequence != null)
115                {
116                    double[][] typeBounds = sequence.getChannelsTypeBounds();
117                    double[][] bounds = sequence.getChannelsBounds();
118
119                    for (int i = 0; i < Math.min(getLut().getNumChannel(), typeBounds.length); i++)
120                    {
121                        double[] tb = typeBounds[i];
122                        double[] b = bounds[i];
123
124                        final Scaler scaler = getLut().getLutChannel(i).getScaler();
125
126                        scaler.setAbsLeftRightIn(tb[0], tb[1]);
127                        scaler.setLeftRightIn(b[0], b[1]);
128                    }
129                }
130            }
131        };
132        channelEnableUpdater = new Runnable()
133        {
134            @Override
135            public void run()
136            {
137                for (int c = 0; c < Math.min(getLut().getNumChannel(), bottomPane.getTabCount()); c++)
138                    bottomPane.setTabChecked(c, getLut().getLutChannel(c).isEnabled());
139            }
140        };
141        channelTabColorUpdater = new Runnable()
142        {
143            @Override
144            public void run()
145            {
146                for (int c = 0; c < Math.min(getLut().getNumChannel(), bottomPane.getTabCount()); c++)
147                {
148                    final IcyColorMap colormap = getLut().getLutChannel(c).getColorMap();
149                    bottomPane.setBackgroundAt(c, colormap.getDominantColor());
150                }
151            }
152        };
153        channelNameUpdater = new Runnable()
154        {
155            @Override
156            public void run()
157            {
158                final Sequence sequence = getSequence();
159
160                if (sequence != null)
161                {
162                    // need to be done on EDT
163                    ThreadUtil.invokeNow(new Runnable()
164                    {
165                        @Override
166                        public void run()
167                        {
168                            for (int c = 0; c < Math.min(sequence.getSizeC(), bottomPane.getTabCount()); c++)
169                            {
170                                final String channelName = sequence.getChannelName(c);
171
172                                bottomPane.setTitleAt(c, StringUtil.limit(channelName, 10));
173                                if (sequence.getDefaultChannelName(c).equals(channelName))
174                                    bottomPane.setToolTipTextAt(c, "Channel " + c);
175                                else
176                                    bottomPane.setToolTipTextAt(c, channelName + " (channel " + c + ")");
177                            }
178                        }
179                    });
180                }
181            }
182        };
183
184        lutChannelViewers = new ArrayList<LUTChannelViewer>();
185
186        // build GUI
187        bottomPane = new CheckTabbedPane(SwingConstants.BOTTOM, true);
188        bottomPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
189
190        // add tab for each channel
191        for (int c = 0; c < lut.getNumChannel(); c++)
192        {
193            final LUTChannel lutChannel = lut.getLutChannel(c);
194            final LUTChannelViewer lbv = new LUTChannelViewer(viewer, lutChannel);
195
196            lutChannel.getColorMap().addListener(this);
197
198            lutChannelViewers.add(lbv);
199            bottomPane.addTab("ch " + c, lbv);
200        }
201
202        bottomPane.addChangeListener(new ChangeListener()
203        {
204            @Override
205            public void stateChanged(ChangeEvent e)
206            {
207                final int size = lutChannelViewers.size();
208                boolean changedState[] = new boolean[size];
209                boolean enabledState[] = new boolean[size];
210
211                for (int i = 0; i < size; i++)
212                {
213                    try
214                    {
215                        // null pointer exception can sometime happen here, normal
216                        enabledState[i] = bottomPane.isTabChecked(i);
217                        changedState[i] = lutChannelViewers.get(i).getLutChannel().isEnabled() != enabledState[i];
218                    }
219                    catch (Exception exc)
220                    {
221                        enabledState[i] = true;
222                        changedState[i] = false;
223                    }
224                }
225
226                // we really want to only set state which changed here and not the one which has
227                // been set from a "setEnabled" event
228                for (int i = 0; i < size; i++)
229                {
230                    if (changedState[i])
231                    {
232                        lutChannelViewers.get(i).getLutChannel().setEnabled(enabledState[i]);
233                    }
234                }
235            }
236        });
237
238        autoRefreshHistoCheckBox = new JCheckBox("Refresh", pref.getBoolean(ID_AUTO_REFRESH, true));
239        autoRefreshHistoCheckBox.setToolTipText("Automatically refresh histogram when data is modified");
240        autoRefreshHistoCheckBox.addActionListener(new ActionListener()
241        {
242            @Override
243            public void actionPerformed(ActionEvent e)
244            {
245                final boolean value = autoRefreshHistoCheckBox.isSelected();
246                if (value)
247                    refreshAllHistogram();
248                pref.putBoolean(ID_AUTO_REFRESH, value);
249            }
250        });
251        if (autoRefreshHistoCheckBox.isSelected())
252            refreshAllHistogram();
253
254        autoBoundsCheckBox = new JCheckBox("Auto bounds", getPreferredAutoBounds());
255        autoBoundsCheckBox.setToolTipText("Automatically ajdust bounds when data is modified");
256        autoBoundsCheckBox.addActionListener(new ActionListener()
257        {
258            @Override
259            public void actionPerformed(ActionEvent e)
260            {
261                final boolean value = autoBoundsCheckBox.isSelected();
262
263                if (value)
264                {
265                    ThreadUtil.runSingle(boundsUpdater);
266                    refreshAllHistogram();
267                    autoRefreshHistoCheckBox.setSelected(true);
268                    autoRefreshHistoCheckBox.setEnabled(false);
269                }
270                else
271                {
272                    final boolean refreshValue = pref.getBoolean(ID_AUTO_REFRESH, true);
273                    if (refreshValue)
274                        refreshAllHistogram();
275                    autoRefreshHistoCheckBox.setSelected(refreshValue);
276                    autoRefreshHistoCheckBox.setEnabled(true);
277                }
278
279                pref.putBoolean(ID_AUTO_BOUNDS, value);
280            }
281        });
282
283        final Sequence seq = getSequence();
284
285        scaleGroup = new ButtonGroup();
286        logButton = new JRadioButton("log");
287        logButton.setToolTipText("Display histogram in a logarithm form");
288        logButton.addActionListener(new ActionListener()
289        {
290            @Override
291            public void actionPerformed(ActionEvent e)
292            {
293                scaleTypeChanged(true);
294            }
295        });
296        linearButton = new JRadioButton("linear");
297        linearButton.setToolTipText("Display histogram in a linear form");
298        linearButton.addActionListener(new ActionListener()
299        {
300            @Override
301            public void actionPerformed(ActionEvent e)
302            {
303                scaleTypeChanged(false);
304            }
305        });
306
307        scaleGroup.add(logButton);
308        scaleGroup.add(linearButton);
309
310        // default
311        if (pref.getBoolean(ID_LOG_VIEW, true))
312            logButton.setSelected(true);
313        else
314            linearButton.setSelected(true);
315
316        exportXLSButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_XLS_EXPORT, 18));
317        exportXLSButton.setFlat(true);
318        exportXLSButton.setToolTipText("Export histogram data into an excel file");
319        exportXLSButton.addActionListener(new ActionListener()
320        {
321            @Override
322            public void actionPerformed(ActionEvent e)
323            {
324                try
325                {
326                    // export current visible histogram
327                    lutChannelViewers.get(bottomPane.getSelectedIndex()).getScalerPanel().getScalerViewer()
328                            .getHistogram().getHistogram().doXLSExport();
329                }
330                catch (Exception e1)
331                {
332                    MessageDialog.showDialog("Error", e1.getMessage(), MessageDialog.ERROR_MESSAGE);
333                }
334            }
335        });
336
337        setLayout(new BorderLayout());
338
339        add(GuiUtil.createLineBoxPanel(autoRefreshHistoCheckBox, autoBoundsCheckBox, Box.createHorizontalGlue(),
340                Box.createHorizontalStrut(4), logButton, linearButton, Box.createHorizontalStrut(4), exportXLSButton),
341                BorderLayout.NORTH);
342        add(bottomPane, BorderLayout.CENTER);
343
344        validate();
345
346        // update channel name and color
347        channelTabColorUpdater.run();
348        channelNameUpdater.run();
349
350        if (seq != null)
351        {
352            if (!seq.hasUserLUT() && autoBoundsCheckBox.isSelected())
353            {
354                ThreadUtil.runSingle(boundsUpdater);
355                refreshAllHistogram();
356                autoRefreshHistoCheckBox.setSelected(true);
357                autoRefreshHistoCheckBox.setEnabled(false);
358            }
359
360            seq.addListener(this);
361        }
362    }
363
364    private boolean getPreferredAutoBounds()
365    {
366        boolean result = pref.getBoolean(ID_AUTO_BOUNDS, true);
367
368        if (!result)
369            return false;
370
371        final Sequence sequence = getSequence();
372
373        if (sequence != null)
374        {
375            // byte data type ?
376            if (sequence.getDataType_() == DataType.UBYTE)
377            {
378                final int numChannel = getLut().getNumChannel();
379
380                // custom colormaps --> cannot use auto bounds
381                for (int c = 0; c < numChannel; c++)
382                    if (!getLut().getLutChannel(c).getColorMap().isLinear())
383                        return false;
384
385                if ((numChannel == 3) || (numChannel == 4))
386                {
387                    boolean rgb;
388
389                    // check if we have classic RGB
390                    rgb = getLut().getLutChannel(0).getColorMap().equals(LinearColorMap.red_)
391                            && getLut().getLutChannel(1).getColorMap().equals(LinearColorMap.green_)
392                            && getLut().getLutChannel(2).getColorMap().equals(LinearColorMap.blue_);
393
394                    // ARGB
395                    if (numChannel == 4)
396                        rgb &= (getLut().getLutChannel(3).getColorMap().getType() == IcyColorMapType.ALPHA);
397
398                    // do not use auto bounds for classic (A)RGB images
399                    if (rgb)
400                        return false;
401                }
402            }
403        }
404
405        return true;
406    }
407
408    @Override
409    public Sequence getSequence()
410    {
411        return super.getSequence();
412    }
413
414    @Override
415    public LUT getLut()
416    {
417        return super.getLut();
418    }
419
420    public boolean getAutoBounds()
421    {
422        return autoBoundsCheckBox.isSelected();
423    }
424
425    public void setAutoBound(boolean value)
426    {
427        autoBoundsCheckBox.setSelected(value);
428    }
429
430    public boolean getAutoRefreshHistogram()
431    {
432        return autoRefreshHistoCheckBox.isSelected();
433    }
434
435    public void setAutoRefreshHistogram(boolean value)
436    {
437        autoRefreshHistoCheckBox.setSelected(value);
438    }
439
440    public boolean getLogScale()
441    {
442        return logButton.isSelected();
443    }
444
445    public void setLogScale(boolean value)
446    {
447        if (value)
448            logButton.setSelected(true);
449        else
450            linearButton.setSelected(true);
451    }
452
453    void refreshAllHistogram()
454    {
455        for (int i = 0; i < lutChannelViewers.size(); i++)
456            lutChannelViewers.get(i).getScalerPanel().refreshHistogram();
457    }
458
459    void scaleTypeChanged(boolean log)
460    {
461        pref.putBoolean(ID_LOG_VIEW, log);
462        // change histogram scale type
463        for (int i = 0; i < lutChannelViewers.size(); i++)
464            lutChannelViewers.get(i).getScalerPanel().getScalerViewer().scaleTypeChanged(log);
465    }
466
467    @Override
468    public void colorMapChanged(IcyColorMapEvent e)
469    {
470        switch (e.getType())
471        {
472            case ENABLED_CHANGED:
473                ThreadUtil.runSingle(channelEnableUpdater);
474                break;
475
476            case MAP_CHANGED:
477                ThreadUtil.runSingle(channelTabColorUpdater);
478                break;
479
480            case TYPE_CHANGED:
481                break;
482        }
483    }
484
485    public void dispose()
486    {
487        removeAll();
488
489        Sequence seq = getSequence();
490        if (seq != null)
491            seq.removeListener(this);
492    }
493
494    @Override
495    public void sequenceChanged(SequenceEvent sequenceEvent)
496    {
497        final SequenceEvent e = sequenceEvent;
498
499        switch (e.getSourceType())
500        {
501            case SEQUENCE_META:
502                ThreadUtil.runSingle(channelNameUpdater);
503                break;
504
505            case SEQUENCE_COMPONENTBOUNDS:
506                if (autoBoundsCheckBox.isSelected())
507                    ThreadUtil.runSingle(boundsUpdater);
508                break;
509        }
510    }
511
512    @Override
513    public void sequenceClosed(Sequence sequence)
514    {
515
516    }
517}