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.sequence.tools;
020
021import icy.gui.component.button.IcyButton;
022import icy.gui.component.sequence.SequenceChooser;
023import icy.gui.component.sequence.SequencePreviewPanel;
024import icy.gui.dialog.MessageDialog;
025import icy.resource.ResourceUtil;
026import icy.resource.icon.IcyIcon;
027import icy.sequence.DimensionId;
028import icy.sequence.Sequence;
029import icy.sequence.SequenceModel;
030
031import java.awt.Dimension;
032import java.awt.Font;
033import java.awt.GridBagConstraints;
034import java.awt.GridBagLayout;
035import java.awt.Insets;
036import java.awt.event.ActionEvent;
037import java.awt.event.ActionListener;
038
039import javax.swing.DefaultListModel;
040import javax.swing.JCheckBox;
041import javax.swing.JLabel;
042import javax.swing.JList;
043import javax.swing.JPanel;
044import javax.swing.JScrollPane;
045import javax.swing.ListSelectionModel;
046import javax.swing.SwingConstants;
047import javax.swing.border.TitledBorder;
048import javax.swing.event.ChangeEvent;
049import javax.swing.event.ChangeListener;
050import javax.swing.event.ListSelectionEvent;
051import javax.swing.event.ListSelectionListener;
052
053/**
054 * Frame for dimension merge operation.
055 * 
056 * @author Stephane
057 */
058public class SequenceDimensionMergePanel extends JPanel
059{
060    static class SequenceChannelEntry
061    {
062        final Sequence sequence;
063        final int c;
064
065        /**
066         * @param sequence
067         * @param c
068         */
069        public SequenceChannelEntry(Sequence sequence, int c)
070        {
071            super();
072
073            this.sequence = sequence;
074            this.c = c;
075        }
076
077        public SequenceChannelEntry(Sequence sequence)
078        {
079            this(sequence, -1);
080        }
081
082        @Override
083        public String toString()
084        {
085            if (c == -1)
086                return sequence.toString();
087
088            return sequence.toString() + "    [channel " + c + "]";
089        }
090    }
091
092    /**
093     * 
094     */
095    private static final long serialVersionUID = -5908902915282090447L;
096
097    // GUI
098    protected IcyButton addButton;
099    protected IcyButton removeButton;
100    protected IcyButton upButton;
101    protected IcyButton downButton;
102    protected JList sequenceList;
103    protected SequenceChooser sequenceChooser;
104    protected SequencePreviewPanel sequencePreview;
105    protected JCheckBox interlaceCheckBox;
106    protected JCheckBox fillEmptyImageCheckBox;
107    protected JCheckBox fitCheckbox;
108    private JLabel bottomArrowLabel;
109    private JLabel dimLabel;
110
111    // internals
112    protected DefaultListModel listModel;
113    protected ListSelectionModel selectionModel;
114    protected final DimensionId dim;
115
116    /**
117     * Create the panel.
118     */
119    public SequenceDimensionMergePanel(DimensionId dim)
120    {
121        super();
122
123        this.dim = dim;
124
125        listModel = new DefaultListModel();
126
127        initialize();
128
129        selectionModel = sequenceList.getSelectionModel();
130        selectionModel.addListSelectionListener(new ListSelectionListener()
131        {
132            @Override
133            public void valueChanged(ListSelectionEvent e)
134            {
135                refreshButtonsState();
136            }
137        });
138
139        interlaceCheckBox.addActionListener(new ActionListener()
140        {
141            @Override
142            public void actionPerformed(ActionEvent e)
143            {
144                fireChangedEvent();
145                previewImageChanged();
146            }
147        });
148        fillEmptyImageCheckBox.addActionListener(new ActionListener()
149        {
150            @Override
151            public void actionPerformed(ActionEvent e)
152            {
153                fireChangedEvent();
154                previewImageChanged();
155            }
156        });
157        fitCheckbox.addActionListener(new ActionListener()
158        {
159            @Override
160            public void actionPerformed(ActionEvent e)
161            {
162                fireChangedEvent();
163                previewImageChanged();
164            }
165        });
166
167        addButton.addActionListener(new ActionListener()
168        {
169            @Override
170            public void actionPerformed(ActionEvent e)
171            {
172                final Sequence seq = sequenceChooser.getSelectedSequence();
173
174                if (seq != null)
175                {
176                    if (checkSequenceIsCompatible(seq, true, true))
177                    {
178                        if (SequenceDimensionMergePanel.this.dim == DimensionId.C)
179                        {
180                            // add per channel
181                            for (int c = 0; c < seq.getSizeC(); c++)
182                                listModel.addElement(new SequenceChannelEntry(seq, c));
183                        }
184                        else
185                            listModel.addElement(new SequenceChannelEntry(seq));
186
187                        refreshButtonsState();
188                        fireChangedEvent();
189                        previewDimensionChanged();
190                    }
191                }
192            }
193        });
194        removeButton.addActionListener(new ActionListener()
195        {
196            @Override
197            public void actionPerformed(ActionEvent e)
198            {
199                listModel.remove(selectionModel.getMinSelectionIndex());
200
201                refreshButtonsState();
202                fireChangedEvent();
203                previewDimensionChanged();
204            }
205        });
206        upButton.addActionListener(new ActionListener()
207        {
208            @Override
209            public void actionPerformed(ActionEvent e)
210            {
211                final int index = selectionModel.getMinSelectionIndex();
212
213                // exchange index and (index - 1)
214                final Object obj = listModel.getElementAt(index - 1);
215                listModel.set(index - 1, listModel.getElementAt(index));
216                listModel.set(index, obj);
217
218                selectionModel.setSelectionInterval(index - 1, index - 1);
219
220                refreshButtonsState();
221                fireChangedEvent();
222                previewImageChanged();
223            }
224        });
225        downButton.addActionListener(new ActionListener()
226        {
227            @Override
228            public void actionPerformed(ActionEvent e)
229            {
230                final int index = selectionModel.getMinSelectionIndex();
231
232                // exchange index and (index + 1)
233                final Object obj = listModel.getElementAt(index + 1);
234                listModel.set(index + 1, listModel.getElementAt(index));
235                listModel.set(index, obj);
236
237                selectionModel.setSelectionInterval(index + 1, index + 1);
238
239                refreshButtonsState();
240                fireChangedEvent();
241                previewImageChanged();
242            }
243        });
244
245        dimLabel.setText(dim.toString());
246        IcyIcon icon = new IcyIcon(ResourceUtil.ICON_ARROW_DOWN);
247        icon.setDimension(new Dimension(20, 60));
248        bottomArrowLabel.setIcon(icon);
249
250        // interlace not available for channel merge operation
251        interlaceCheckBox.setVisible(dim != DimensionId.C);
252        // fillEmptyImageCheckBox.setVisible(false);
253
254        refreshButtonsState();
255    }
256
257    private void initialize()
258    {
259        GridBagLayout gridBagLayout = new GridBagLayout();
260        gridBagLayout.columnWidths = new int[] {24, 80, 140, 100, 0, 0};
261        gridBagLayout.rowHeights = new int[] {0, 26, 0, 0, 0, 0, 0, 174, 0};
262        gridBagLayout.columnWeights = new double[] {0.0, 1.0, 1.0, 1.0, 0.0, Double.MIN_VALUE};
263        gridBagLayout.rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, Double.MIN_VALUE};
264        setLayout(gridBagLayout);
265
266        JLabel lblSelectSequenceTo = new JLabel("Add sequence to merge in the list :");
267        GridBagConstraints gbc_lblSelectSequenceTo = new GridBagConstraints();
268        gbc_lblSelectSequenceTo.fill = GridBagConstraints.BOTH;
269        gbc_lblSelectSequenceTo.gridwidth = 4;
270        gbc_lblSelectSequenceTo.insets = new Insets(0, 0, 5, 5);
271        gbc_lblSelectSequenceTo.gridx = 0;
272        gbc_lblSelectSequenceTo.gridy = 0;
273        add(lblSelectSequenceTo, gbc_lblSelectSequenceTo);
274
275        sequenceChooser = new SequenceChooser();
276        GridBagConstraints gbc_sequenceChooser = new GridBagConstraints();
277        gbc_sequenceChooser.gridwidth = 4;
278        gbc_sequenceChooser.insets = new Insets(0, 0, 5, 5);
279        gbc_sequenceChooser.fill = GridBagConstraints.BOTH;
280        gbc_sequenceChooser.gridx = 0;
281        gbc_sequenceChooser.gridy = 1;
282        add(sequenceChooser, gbc_sequenceChooser);
283
284        addButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_ROUND_PLUS));
285        addButton.setToolTipText("Add selected sequence to the list.");
286        addButton.setFlat(true);
287        GridBagConstraints gbc_addButton = new GridBagConstraints();
288        gbc_addButton.fill = GridBagConstraints.BOTH;
289        gbc_addButton.insets = new Insets(0, 0, 5, 0);
290        gbc_addButton.gridx = 4;
291        gbc_addButton.gridy = 1;
292        add(addButton, gbc_addButton);
293
294        dimLabel = new JLabel("Z");
295        dimLabel.setHorizontalAlignment(SwingConstants.CENTER);
296        dimLabel.setFont(new Font("Tahoma", Font.BOLD, 14));
297        GridBagConstraints gbc_dimLabel = new GridBagConstraints();
298        gbc_dimLabel.fill = GridBagConstraints.HORIZONTAL;
299        gbc_dimLabel.anchor = GridBagConstraints.BASELINE;
300        gbc_dimLabel.insets = new Insets(0, 0, 5, 5);
301        gbc_dimLabel.gridx = 0;
302        gbc_dimLabel.gridy = 2;
303        add(dimLabel, gbc_dimLabel);
304
305        JScrollPane scrollPane = new JScrollPane();
306        GridBagConstraints gbc_scrollPane = new GridBagConstraints();
307        gbc_scrollPane.gridwidth = 3;
308        gbc_scrollPane.fill = GridBagConstraints.BOTH;
309        gbc_scrollPane.gridheight = 4;
310        gbc_scrollPane.insets = new Insets(0, 0, 5, 5);
311        gbc_scrollPane.gridx = 1;
312        gbc_scrollPane.gridy = 2;
313        add(scrollPane, gbc_scrollPane);
314
315        sequenceList = new JList(listModel);
316        scrollPane.setViewportView(sequenceList);
317        sequenceList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
318
319        removeButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_ROUND_MINUS));
320        removeButton.setToolTipText("Remove selected sequence from the list.");
321        removeButton.setFlat(true);
322        GridBagConstraints gbc_removeButton = new GridBagConstraints();
323        gbc_removeButton.fill = GridBagConstraints.BOTH;
324        gbc_removeButton.insets = new Insets(0, 0, 5, 0);
325        gbc_removeButton.gridx = 4;
326        gbc_removeButton.gridy = 2;
327        add(removeButton, gbc_removeButton);
328
329        bottomArrowLabel = new JLabel("");
330        bottomArrowLabel.setHorizontalAlignment(SwingConstants.CENTER);
331        GridBagConstraints gbc_bottomArrowLabel = new GridBagConstraints();
332        gbc_bottomArrowLabel.gridheight = 3;
333        gbc_bottomArrowLabel.insets = new Insets(0, 0, 5, 5);
334        gbc_bottomArrowLabel.gridx = 0;
335        gbc_bottomArrowLabel.gridy = 3;
336        add(bottomArrowLabel, gbc_bottomArrowLabel);
337
338        upButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_ROUND_ARROW_UP));
339        upButton.setToolTipText("Move up selected sequence.");
340        upButton.setFlat(true);
341        GridBagConstraints gbc_upButton = new GridBagConstraints();
342        gbc_upButton.fill = GridBagConstraints.BOTH;
343        gbc_upButton.insets = new Insets(0, 0, 5, 0);
344        gbc_upButton.gridx = 4;
345        gbc_upButton.gridy = 3;
346        add(upButton, gbc_upButton);
347
348        downButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_ROUND_ARROW_DOWN));
349        downButton.setToolTipText("Move down selected sequence.");
350        downButton.setFlat(true);
351        GridBagConstraints gbc_downButton = new GridBagConstraints();
352        gbc_downButton.fill = GridBagConstraints.BOTH;
353        gbc_downButton.insets = new Insets(0, 0, 5, 0);
354        gbc_downButton.gridx = 4;
355        gbc_downButton.gridy = 4;
356        add(downButton, gbc_downButton);
357
358        fitCheckbox = new JCheckBox("Scale image");
359        fitCheckbox.setToolTipText("Scale all image to the largest one");
360        GridBagConstraints gbc_fitCheckbox = new GridBagConstraints();
361        gbc_fitCheckbox.anchor = GridBagConstraints.WEST;
362        gbc_fitCheckbox.gridwidth = 2;
363        gbc_fitCheckbox.insets = new Insets(0, 0, 5, 5);
364        gbc_fitCheckbox.gridx = 0;
365        gbc_fitCheckbox.gridy = 6;
366        add(fitCheckbox, gbc_fitCheckbox);
367
368        fillEmptyImageCheckBox = new JCheckBox("Fill empty image");
369        fillEmptyImageCheckBox.setToolTipText("Replace empty image by the previous non empty one");
370        GridBagConstraints gbc_noEmptyImageCheckBox = new GridBagConstraints();
371        gbc_noEmptyImageCheckBox.fill = GridBagConstraints.VERTICAL;
372        gbc_noEmptyImageCheckBox.insets = new Insets(0, 0, 5, 5);
373        gbc_noEmptyImageCheckBox.gridx = 2;
374        gbc_noEmptyImageCheckBox.gridy = 6;
375        add(fillEmptyImageCheckBox, gbc_noEmptyImageCheckBox);
376
377        interlaceCheckBox = new JCheckBox("Interlace image");
378        interlaceCheckBox.setToolTipText("Interlace sequence image");
379        GridBagConstraints gbc_interlaceCheckBox = new GridBagConstraints();
380        gbc_interlaceCheckBox.anchor = GridBagConstraints.EAST;
381        gbc_interlaceCheckBox.gridwidth = 2;
382        gbc_interlaceCheckBox.fill = GridBagConstraints.VERTICAL;
383        gbc_interlaceCheckBox.insets = new Insets(0, 0, 5, 0);
384        gbc_interlaceCheckBox.gridx = 3;
385        gbc_interlaceCheckBox.gridy = 6;
386        add(interlaceCheckBox, gbc_interlaceCheckBox);
387
388        sequencePreview = new SequencePreviewPanel();
389        sequencePreview
390                .setBorder(new TitledBorder(null, "Preview", TitledBorder.LEADING, TitledBorder.TOP, null, null));
391        GridBagConstraints gbc_sequencePreview = new GridBagConstraints();
392        gbc_sequencePreview.gridwidth = 5;
393        gbc_sequencePreview.fill = GridBagConstraints.BOTH;
394        gbc_sequencePreview.gridx = 0;
395        gbc_sequencePreview.gridy = 7;
396        add(sequencePreview, gbc_sequencePreview);
397    }
398
399    public DimensionId getDimensionId()
400    {
401        return dim;
402    }
403
404    void refreshButtonsState()
405    {
406        final int index = selectionModel.getMinSelectionIndex();
407        final boolean notEmpty = index != -1;
408        final int size = listModel.getSize();
409
410        removeButton.setEnabled(notEmpty);
411        upButton.setEnabled(notEmpty && (index != 0));
412        downButton.setEnabled(notEmpty && (index != (size - 1)));
413    }
414
415    public int[] getSelectedChannels()
416    {
417        final int[] result = new int[listModel.size()];
418
419        for (int i = 0; i < listModel.getSize(); i++)
420            result[i] = ((SequenceChannelEntry) listModel.get(i)).c;
421
422        return result;
423    }
424
425    public Sequence[] getSequences()
426    {
427        final Sequence result[] = new Sequence[listModel.size()];
428
429        for (int i = 0; i < listModel.getSize(); i++)
430            result[i] = ((SequenceChannelEntry) listModel.get(i)).sequence;
431
432        return result;
433    }
434
435    boolean checkSequenceIsCompatible(Sequence seq, boolean showMessage, boolean showWarning)
436    {
437        boolean warningXYDone = false;
438
439        for (Sequence sequence : getSequences())
440        {
441            // first check for data type
442            if (!seq.getDataType_().equals(sequence.getDataType_()))
443            {
444                if (showMessage)
445                    MessageDialog.showDialog("You cannot merge sequences with different data type.");
446
447                return false;
448            }
449
450            // We can remove all these verification as the merge algorithm take care of that !
451            
452            // then depending dimension merge check for dimension equality
453            // switch (getDimensionId())
454            // {
455            // case C:
456            // if (seq.getSizeZ() != sequence.getSizeZ())
457            // {
458            // if (showMessage)
459            // MessageDialog.showDialog("You cannot merge channels from sequences with different Z size.");
460            //
461            // return false;
462            // }
463            // if (seq.getSizeT() != sequence.getSizeT())
464            // {
465            // if (showMessage)
466            // MessageDialog.showDialog("You cannot merge channels from sequences with different T size.");
467            //
468            // return false;
469            // }
470            // break;
471            //
472            // case Z:
473            // if (seq.getSizeC() != sequence.getSizeC())
474            // {
475            // if (showMessage)
476            // MessageDialog
477            // .showDialog("You cannot merge slices from sequences with different number of channel.");
478            //
479            // return false;
480            // }
481            // if (seq.getSizeT() != sequence.getSizeT())
482            // {
483            // if (showMessage)
484            // MessageDialog.showDialog("You cannot merge slices from sequences with different T size.");
485            //
486            // return false;
487            // }
488            // break;
489            //
490            // case T:
491            // if (seq.getSizeC() != sequence.getSizeC())
492            // {
493            // if (showMessage)
494            // MessageDialog.showDialog(
495            // "You cannot merge frames from sequences with different number of channel.",
496            // MessageDialog.PLAIN_MESSAGE);
497            //
498            // return false;
499            // }
500            // if (seq.getSizeZ() != sequence.getSizeZ())
501            // {
502            // if (showMessage)
503            // MessageDialog.showDialog("You cannot merge frames from sequences with different Z size.");
504            //
505            // return false;
506            // }
507            // break;
508            // }
509
510            // also consider the XY size
511            if (!isFitImagesEnabled())
512            {
513                if ((seq.getSizeX() != sequence.getSizeX()) || (seq.getSizeY() != sequence.getSizeY()))
514                {
515                    if (showWarning && !warningXYDone)
516                    {
517                        MessageDialog
518                                .showDialog(
519                                        "Sequences have different XY size !\nYou can enable the \"Scale image\" option to resize images if needed.",
520                                        MessageDialog.WARNING_MESSAGE);
521                        warningXYDone = true;
522                    }
523                }
524            }
525        }
526
527        return true;
528    }
529
530    /**
531     * @return the image provider
532     */
533    public SequenceModel getModel()
534    {
535        return sequencePreview.getModel();
536    }
537
538    public void setModel(SequenceModel model)
539    {
540        sequencePreview.setModel(model);
541    }
542
543    public void previewDimensionChanged()
544    {
545        sequencePreview.dimensionChanged();
546    }
547
548    public void previewImageChanged()
549    {
550        sequencePreview.imageChanged();
551    }
552
553    public boolean isInterlaceEnabled()
554    {
555        return interlaceCheckBox.isVisible() && interlaceCheckBox.isSelected();
556    }
557
558    public boolean isFillEmptyImageEnabled()
559    {
560        return fillEmptyImageCheckBox.isVisible() && fillEmptyImageCheckBox.isSelected();
561    }
562
563    public boolean isFitImagesEnabled()
564    {
565        return fitCheckbox.isVisible() && fitCheckbox.isSelected();
566    }
567
568    public boolean isInterlaceVisible()
569    {
570        return interlaceCheckBox.isVisible();
571    }
572
573    public void setInterlaceVisible(boolean value)
574    {
575        interlaceCheckBox.setVisible(value);
576    }
577
578    public boolean isFillEmptyImageVisible()
579    {
580        return fillEmptyImageCheckBox.isVisible();
581    }
582
583    public void setFillEmptyImageVisible(boolean value)
584    {
585        fillEmptyImageCheckBox.setVisible(value);
586    }
587
588    protected void fireChangedEvent()
589    {
590        final ChangeEvent event = new ChangeEvent(SequenceDimensionMergePanel.this);
591
592        for (ChangeListener listener : getListeners(ChangeListener.class))
593            listener.stateChanged(event);
594    }
595
596    public void addChangeListener(ChangeListener listener)
597    {
598        listenerList.add(ChangeListener.class, listener);
599    }
600
601    public void removeChangeListener(ChangeListener listener)
602    {
603        listenerList.remove(ChangeListener.class, listener);
604    }
605}