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.dialog;
020
021import icy.common.exception.UnsupportedFormatException;
022import icy.file.Loader;
023import icy.file.SequenceFileImporter;
024import icy.gui.component.ThumbnailComponent;
025import icy.gui.util.ComponentUtil;
026import icy.image.IcyBufferedImage;
027import icy.image.IcyBufferedImageUtil;
028import icy.main.Icy;
029import icy.resource.ResourceUtil;
030import icy.sequence.MetaDataUtil;
031import icy.sequence.SequenceIdImporter;
032import icy.util.OMEUtil;
033
034import java.awt.BorderLayout;
035import java.awt.Dimension;
036import java.awt.FlowLayout;
037import java.awt.GridLayout;
038import java.awt.event.ActionEvent;
039import java.awt.event.ActionListener;
040import java.awt.event.MouseAdapter;
041import java.awt.event.MouseEvent;
042import java.awt.image.BufferedImage;
043import java.io.IOException;
044import java.util.Timer;
045import java.util.TimerTask;
046
047import javax.swing.Box;
048import javax.swing.JButton;
049import javax.swing.JLabel;
050import javax.swing.JPanel;
051import javax.swing.JScrollPane;
052import javax.swing.ScrollPaneConstants;
053
054import loci.formats.IFormatReader;
055import loci.formats.ome.OMEXMLMetadataImpl;
056import ome.xml.meta.MetadataRetrieve;
057import ome.xml.meta.OMEXMLMetadata;
058
059/**
060 * Dialog used to select which serie to open for multi serie image.
061 * 
062 * @author Stephane
063 */
064public class SeriesSelectionDialog extends ActionDialog implements Runnable
065{
066    /**
067     * 
068     */
069    private static final long serialVersionUID = -2133845128887305016L;
070
071    protected static final int NUM_COL = 4;
072    protected static final int THUMB_X = 160;
073    protected static final int THUMB_Y = 140;
074
075    // GUI
076    protected JScrollPane scrollPane;
077    protected JPanel gridPanel;
078    protected ThumbnailComponent[] serieComponents;
079    protected JButton selectAllBtn;
080    protected JButton unselectAllBtn;
081
082    // internal
083    protected IFormatReader reader;
084    protected SequenceIdImporter importer;
085    protected String id;
086    protected OMEXMLMetadata metadata;
087    protected boolean singleSelection;
088    protected int[] selectedSeries;
089    protected final MouseAdapter serieDoubleClickAction;
090    protected final ActionListener serieSimpleClickAction;
091    protected final Thread loadingThread;
092
093    /**
094     * @deprecated Use {@link #SeriesSelectionDialog(SequenceFileImporter, String)} instead.
095     */
096    @Deprecated
097    public SeriesSelectionDialog(IFormatReader reader)
098    {
099        super("Series selection", null, Icy.getMainInterface().getMainFrame());
100
101        this.reader = reader;
102        // default is empty
103        selectedSeries = new int[] {};
104        singleSelection = false;
105
106        initialize();
107
108        serieSimpleClickAction = new ActionListener()
109        {
110            @Override
111            public void actionPerformed(ActionEvent e)
112            {
113                // not used here
114            }
115        };
116        // double click action = direct selection
117        serieDoubleClickAction = new MouseAdapter()
118        {
119            @Override
120            public void mouseClicked(MouseEvent e)
121            {
122                if (e.getClickCount() == 2)
123                {
124                    final ThumbnailComponent thumb = (ThumbnailComponent) e.getSource();
125
126                    for (int i = 0; i < serieComponents.length; i++)
127                    {
128                        if (serieComponents[i] == thumb)
129                        {
130                            selectedSeries = new int[] {i};
131                            dispose();
132                        }
133                    }
134                }
135            }
136        };
137
138        final int series;
139
140        if (reader != null)
141        {
142            metadata = OMEUtil.getOMEXMLMetadata((MetadataRetrieve) reader.getMetadataStore());
143            series = reader.getSeriesCount();
144        }
145        else
146        {
147            metadata = null;
148            series = 0;
149        }
150
151        serieComponents = new ThumbnailComponent[series];
152
153        // adjust number of row
154        int numRow = series / NUM_COL;
155        if (series > (NUM_COL * numRow))
156            numRow++;
157
158        ((GridLayout) gridPanel.getLayout()).setRows(numRow);
159
160        for (int i = 0; i < numRow; i++)
161        {
162            for (int j = 0; j < NUM_COL; j++)
163            {
164                final int index = (i * NUM_COL) + j;
165
166                if (index < series)
167                {
168                    final ThumbnailComponent thumb = new ThumbnailComponent(true);
169
170                    // add mouse listener (double click action)
171                    thumb.addMouseListener(serieDoubleClickAction);
172
173                    // remove mouse listener (double click action)
174                    if (serieComponents[index] != null)
175                        serieComponents[index].removeMouseListener(serieDoubleClickAction);
176
177                    serieComponents[index] = thumb;
178                    thumb.setEnabled(true);
179                    thumb.setTitle("loading...");
180                    thumb.setInfos("");
181                    thumb.setInfos2("");
182                    gridPanel.add(thumb);
183                }
184                else
185                    gridPanel.add(Box.createGlue());
186            }
187        }
188
189        // load thumbnails...
190        loadingThread = new Thread(this, "Series thumbnail loading");
191        loadingThread.start();
192
193        // action on "OK"
194        setOkAction(new ActionListener()
195        {
196            @Override
197            public void actionPerformed(ActionEvent e)
198            {
199                int numSelected = 0;
200                for (int i = 0; i < serieComponents.length; i++)
201                    if (serieComponents[i].isSelected())
202                        numSelected++;
203
204                selectedSeries = new int[numSelected];
205
206                int ind = 0;
207                for (int i = 0; i < serieComponents.length; i++)
208                    if (serieComponents[i].isSelected())
209                        selectedSeries[ind++] = i;
210            }
211        });
212
213        // action on "Select All"
214        selectAllBtn.addActionListener(new ActionListener()
215        {
216            @Override
217            public void actionPerformed(ActionEvent e)
218            {
219                for (ThumbnailComponent thumb : serieComponents)
220                    thumb.setSelected(true);
221            }
222        });
223
224        // action on "Unselect All"
225        unselectAllBtn.addActionListener(new ActionListener()
226        {
227            @Override
228            public void actionPerformed(ActionEvent e)
229            {
230                for (ThumbnailComponent thumb : serieComponents)
231                    thumb.setSelected(false);
232            }
233        });
234
235        setPreferredSize(new Dimension(740, 520));
236        pack();
237        ComponentUtil.center(this);
238        setVisible(true);
239    }
240
241    public SeriesSelectionDialog(SequenceIdImporter importer, String id, OMEXMLMetadata metadata,
242            boolean singleSelection)
243    {
244        super("Series selection", null, Icy.getMainInterface().getMainFrame());
245
246        this.importer = importer;
247        this.id = id;
248        this.metadata = metadata;
249        this.singleSelection = singleSelection;
250        // default is empty
251        selectedSeries = new int[] {};
252
253        initialize();
254
255        final int series = MetaDataUtil.getNumSeries(metadata);
256
257        // simple click action = simple selection
258        serieSimpleClickAction = new ActionListener()
259        {
260            @Override
261            public void actionPerformed(ActionEvent e)
262            {
263                final Object source = e.getSource();
264
265                // unselect all others
266                if (SeriesSelectionDialog.this.singleSelection)
267                {
268                    for (ThumbnailComponent thumb : serieComponents)
269                    {
270                        if (thumb != source)
271                            thumb.setSelected(false);
272                    }
273                }
274            }
275        };
276        // double click action = direct selection
277        serieDoubleClickAction = new MouseAdapter()
278        {
279            @Override
280            public void mouseClicked(MouseEvent e)
281            {
282                if (e.getClickCount() == 2)
283                {
284                    final ThumbnailComponent thumb = (ThumbnailComponent) e.getSource();
285
286                    for (int i = 0; i < serieComponents.length; i++)
287                    {
288                        if (serieComponents[i] == thumb)
289                        {
290                            selectedSeries = new int[] {i};
291                            dispose();
292                        }
293                    }
294                }
295            }
296        };
297
298        serieComponents = new ThumbnailComponent[series];
299
300        // adjust number of row
301        int numRow = series / NUM_COL;
302        if (series > (NUM_COL * numRow))
303            numRow++;
304
305        ((GridLayout) gridPanel.getLayout()).setRows(numRow);
306
307        for (int i = 0; i < numRow; i++)
308        {
309            for (int j = 0; j < NUM_COL; j++)
310            {
311                final int index = (i * NUM_COL) + j;
312
313                if (index < series)
314                {
315                    final ThumbnailComponent thumb = new ThumbnailComponent(true);
316
317                    // add mouse listener (double click action)
318                    thumb.addMouseListener(serieDoubleClickAction);
319                    // single click action
320                    thumb.addActionListener(serieSimpleClickAction);
321
322                    // remove mouse listener (double click action)
323                    if (serieComponents[index] != null)
324                        serieComponents[index].removeMouseListener(serieDoubleClickAction);
325
326                    serieComponents[index] = thumb;
327                    thumb.setEnabled(true);
328                    thumb.setTitle("loading...");
329                    thumb.setInfos("");
330                    thumb.setInfos2("");
331                    gridPanel.add(thumb);
332                }
333                else
334                    gridPanel.add(Box.createGlue());
335            }
336        }
337
338        // load thumbnails...
339        loadingThread = new Thread(this, "Series thumbnail loading");
340        loadingThread.start();
341
342        // action on "OK"
343        setOkAction(new ActionListener()
344        {
345            @Override
346            public void actionPerformed(ActionEvent e)
347            {
348                int numSelected = 0;
349                for (int i = 0; i < serieComponents.length; i++)
350                    if (serieComponents[i].isSelected())
351                        numSelected++;
352
353                selectedSeries = new int[numSelected];
354
355                int ind = 0;
356                for (int i = 0; i < serieComponents.length; i++)
357                    if (serieComponents[i].isSelected())
358                        selectedSeries[ind++] = i;
359            }
360        });
361
362        // action on "Select All"
363        selectAllBtn.addActionListener(new ActionListener()
364        {
365            @Override
366            public void actionPerformed(ActionEvent e)
367            {
368                for (ThumbnailComponent thumb : serieComponents)
369                    thumb.setSelected(true);
370            }
371        });
372        // action on "Unselect All"
373        unselectAllBtn.addActionListener(new ActionListener()
374        {
375            @Override
376            public void actionPerformed(ActionEvent e)
377            {
378                for (ThumbnailComponent thumb : serieComponents)
379                    thumb.setSelected(false);
380            }
381        });
382
383        // select/unselect all buttons visible only if not in "single selection" mode
384        selectAllBtn.setVisible(!singleSelection);
385        unselectAllBtn.setVisible(!singleSelection);
386
387        setPreferredSize(new Dimension(740, 520));
388        pack();
389        ComponentUtil.center(SeriesSelectionDialog.this);
390        setVisible(true);
391    }
392
393    public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadata metadata,
394            boolean singleSelection)
395    {
396        this((SequenceIdImporter) importer, id, metadata, singleSelection);
397    }
398
399    /**
400     * Create a new dialog to select the series to open from an image.
401     * 
402     * @throws IOException
403     * @throws UnsupportedFormatException
404     */
405    public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadata metadata)
406            throws UnsupportedFormatException, IOException
407    {
408        this(importer, id, metadata, false);
409    }
410
411    /**
412     * @deprecated Use {@link #SeriesSelectionDialog(SequenceFileImporter, String, OMEXMLMetadata)} instead.
413     */
414    @Deprecated
415    public SeriesSelectionDialog(SequenceFileImporter importer, String id, OMEXMLMetadataImpl metadata)
416            throws UnsupportedFormatException, IOException
417    {
418        this(importer, id, (OMEXMLMetadata) metadata);
419    }
420
421    /**
422     * Create a new dialog to select the series to open from an image.
423     * 
424     * @throws IOException
425     * @throws UnsupportedFormatException
426     */
427    public SeriesSelectionDialog(SequenceFileImporter importer, String id)
428            throws UnsupportedFormatException, IOException
429    {
430        this(importer, id, Loader.getOMEXMLMetaData(importer, id));
431    }
432
433    /**
434     * @return the selectedSeries
435     */
436    public int[] getSelectedSeries()
437    {
438        return selectedSeries;
439    }
440
441    void initialize()
442    {
443        JPanel panel = new JPanel();
444        getContentPane().add(panel, BorderLayout.NORTH);
445        panel.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
446
447        final JLabel lblSelect;
448        if (singleSelection)
449            lblSelect = new JLabel("Select the series and click 'Ok' or directly double click on it to open it.");
450        else
451            lblSelect = new JLabel(
452                    "Click on a serie to select / unselect it and click 'Ok' or double click to directly open it.");
453        ComponentUtil.setFontBold(lblSelect);
454        ComponentUtil.setFontSize(lblSelect, 12);
455        panel.add(lblSelect);
456
457        scrollPane = new JScrollPane(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
458                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
459        mainPanel.add(scrollPane, BorderLayout.CENTER);
460
461        gridPanel = new JPanel();
462        scrollPane.setViewportView(gridPanel);
463        gridPanel.setLayout(new GridLayout(2, NUM_COL, 0, 0));
464
465        buttonPanel.removeAll();
466
467        selectAllBtn = new JButton("Select all");
468        unselectAllBtn = new JButton("Unselect all");
469
470        buttonPanel.add(Box.createHorizontalStrut(4));
471        buttonPanel.add(selectAllBtn);
472        buttonPanel.add(Box.createHorizontalStrut(8));
473        buttonPanel.add(unselectAllBtn);
474        buttonPanel.add(Box.createHorizontalStrut(8));
475        buttonPanel.add(Box.createHorizontalGlue());
476        buttonPanel.add(Box.createHorizontalStrut(8));
477        buttonPanel.add(okBtn);
478        buttonPanel.add(Box.createHorizontalStrut(8));
479        buttonPanel.add(cancelBtn);
480        buttonPanel.add(Box.createHorizontalStrut(4));
481    }
482
483    @Override
484    protected void onClosed()
485    {
486        super.onClosed();
487
488        // kill loading task in 2 seconds
489        new Timer().schedule(new TimerTask()
490        {
491            @Override
492            public void run()
493            {
494                if ((loadingThread != null) && loadingThread.isAlive())
495                {
496                    try
497                    {
498                        loadingThread.interrupt();
499                    }
500                    catch (Throwable t)
501                    {
502                        // ignore
503                    }
504                }
505            }
506        }, 2000);
507    }
508
509    @Override
510    public void run()
511    {
512        try
513        {
514            // start by filling metadata only...
515            for (int i = 0; i < serieComponents.length; i++)
516            {
517                // interrupt
518                if (isClosed())
519                    return;
520
521                try
522                {
523                    final int sizeC = MetaDataUtil.getSizeC(metadata, i);
524
525                    serieComponents[i].setTitle(metadata.getImageName(i));
526                    serieComponents[i].setInfos(MetaDataUtil.getSizeX(metadata, i) + " x "
527                            + MetaDataUtil.getSizeY(metadata, i) + " - " + MetaDataUtil.getSizeZ(metadata, i) + "Z x "
528                            + MetaDataUtil.getSizeT(metadata, i) + "T");
529                    serieComponents[i].setInfos2(sizeC + ((sizeC > 1) ? " channels (" : " channel (")
530                            + MetaDataUtil.getDataType(metadata, i) + ")");
531                }
532                catch (Exception e)
533                {
534                    serieComponents[i].setTitle("Cannot read file");
535                    serieComponents[i].setInfos("");
536                    serieComponents[i].setInfos2("");
537                }
538
539                try
540                {
541                    // why does this sometime fails ???
542                    serieComponents[i].setImage(ResourceUtil.ICON_PICTURE);
543                }
544                catch (Exception e)
545                {
546                    // ignore
547                }
548            }
549
550            // then try to load thumbnail
551            for (int i = 0; i < serieComponents.length; i++)
552            {
553                // interrupt
554                if (isClosed())
555                    return;
556
557                try
558                {
559                    if (importer.open(id, 0))
560                    {
561                        try
562                        {
563                            final IcyBufferedImage img = importer.getThumbnail(i);
564                            serieComponents[i]
565                                    .setImage(IcyBufferedImageUtil.toBufferedImage(img, BufferedImage.TYPE_INT_ARGB));
566                        }
567                        finally
568                        {
569                            importer.close();
570                        }
571                    }
572                    else
573                        serieComponents[i].setImage(ResourceUtil.ICON_DELETE);
574                }
575                catch (OutOfMemoryError e)
576                {
577                    // error image, we just totally ignore error here...
578                    serieComponents[i].setImage(ResourceUtil.ICON_DELETE);
579                }
580                catch (Exception e)
581                {
582                    // error image, we just totally ignore error here...
583                    serieComponents[i].setImage(ResourceUtil.ICON_DELETE);
584                }
585            }
586        }
587        catch (ThreadDeath t)
588        {
589            // just stop process...
590        }
591    }
592}