001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 *
010 * All Rights Reserved
011 *
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.
014 *
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 *
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 *
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028package edu.wisc.ssec.mcidasv.util.nativepathchooser;
029
030import javafx.stage.FileChooser;
031
032import java.io.File;
033
034import java.util.List;
035import java.util.concurrent.Callable;
036import java.util.concurrent.TimeUnit;
037import java.util.function.Function;
038import java.util.function.Supplier;
039
040public class NativeFileChooser {
041    private final Supplier<FileChooser> fileChooserFactory;
042
043    /**
044     * Constructs a new file chooser that will use the provided factory.
045     * 
046     * The factory is accessed from the JavaFX event thread, so it should either
047     * be immutable or at least its state shouldn't be changed randomly while
048     * one of the dialog-showing method calls is in progress.
049     * 
050     * The factory should create and set up the chooser, for example,
051     * by setting extension filters. If there is no need to perform custom
052     * initialization of the chooser, FileChooser::new could be passed as
053     * a factory.
054     * 
055     * Alternatively, the method parameter supplied to the showDialog()
056     * function can be used to provide custom initialization.
057     * 
058     * @param fileChooserFactory the function used to construct new choosers
059     */
060    public NativeFileChooser(Supplier<FileChooser> fileChooserFactory) {
061        this.fileChooserFactory = fileChooserFactory;
062    }
063
064    /**
065     * Shows the FileChooser dialog by calling the provided method.
066     * 
067     * Waits for one second for the dialog-showing task to start in the JavaFX
068     * event thread, then throws an IllegalStateException if it didn't start.
069     * 
070     * @see #showDialog(java.util.function.Function, long, java.util.concurrent.TimeUnit) 
071     * @param <T> the return type of the method, usually File or List&lt;File&gt;
072     * @param method a function calling one of the dialog-showing methods
073     * @return whatever the method returns
074     */
075    public <T> T showDialog(Function<FileChooser, T> method) {
076        return showDialog(method, 1, TimeUnit.SECONDS);
077    }
078
079    /**
080     * Shows the FileChooser dialog by calling the provided method. The dialog 
081     * is created by the factory supplied to the constructor, then it is shown
082     * by calling the provided method on it, then the result is returned.
083     * <p>
084     * Everything happens in the right threads thanks to
085     * {@link SynchronousJFXCaller}. The task performed in the JavaFX thread
086     * consists of two steps: construct a chooser using the provided factory
087     * and invoke the provided method on it. Any exception thrown during these
088     * steps will be rethrown in the calling thread, which shouldn't
089     * normally happen unless the factory throws an unchecked exception.
090     * </p>
091     * <p>
092     * If the calling thread is interrupted during either the wait for
093     * the task to start or for its result, then null is returned and
094     * the Thread interrupted status is set.
095     * </p>
096     * @param <T> return type (usually File or List&lt;File&gt;)
097     * @param method a function that calls the desired FileChooser method
098     * @param timeout time to wait for Platform.runLater() to <em>start</em>
099     * the dialog-showing task (once started, it is allowed to run as long
100     * as needed)
101     * @param unit the time unit of the timeout argument
102     * @return whatever the method returns
103     * @throws IllegalStateException if Platform.runLater() fails to start
104     * the dialog-showing task within the given timeout
105     */
106    public <T> T showDialog(Function<FileChooser, T> method,
107            long timeout, TimeUnit unit) {
108        Callable<T> task = () -> {
109            FileChooser chooser = fileChooserFactory.get();
110            return method.apply(chooser);
111        };
112        SynchronousJFXCaller<T> caller = new SynchronousJFXCaller<>(task);
113        try {
114            return caller.call(timeout, unit);
115        } catch (RuntimeException | Error ex) {
116            throw ex;
117        } catch (InterruptedException ex) {
118            Thread.currentThread().interrupt();
119            return null;
120        } catch (Exception ex) {
121            throw new AssertionError("Got unexpected checked exception from"
122                    + " SynchronousJFXCaller.call()", ex);
123        }
124    }
125
126    /**
127     * Shows a FileChooser using FileChooser.showOpenDialog().
128     * 
129     * @see #showDialog(java.util.function.Function, long, java.util.concurrent.TimeUnit) 
130     * @return the return value of FileChooser.showOpenDialog()
131     */
132    public File showOpenDialog() {
133        return showDialog(chooser -> chooser.showOpenDialog(null));
134    }
135
136    /**
137     * Shows a FileChooser using FileChooser.showSaveDialog().
138     * 
139     * @see #showDialog(java.util.function.Function, long, java.util.concurrent.TimeUnit) 
140     * @return the return value of FileChooser.showSaveDialog()
141     */
142    public File showSaveDialog() {
143        return showDialog(chooser -> chooser.showSaveDialog(null));
144    }
145
146    /**
147     * Shows a FileChooser using FileChooser.showOpenMultipleDialog().
148     * 
149     * @see #showDialog(java.util.function.Function, long, java.util.concurrent.TimeUnit) 
150     * @return the return value of FileChooser.showOpenMultipleDialog()
151     */
152    public List<File> showOpenMultipleDialog() {
153        return showDialog(chooser -> chooser.showOpenMultipleDialog(null));
154    }
155
156//    public static void main(String[] args) {
157//        javafx.embed.swing.JFXPanel dummy = new javafx.embed.swing.JFXPanel();
158//        Platform.setImplicitExit(false);
159//        try {
160//            SynchronousJFXFileChooser chooser = new SynchronousJFXFileChooser(() -> {
161//                FileChooser ch = new FileChooser();
162//                ch.setTitle("Open any file you wish");
163//                return ch;
164//            });
165//            File file = chooser.showOpenDialog();
166//            System.out.println(file);
167//            // this will throw an exception:
168//            chooser.showDialog(ch -> ch.showOpenDialog(null), 1, TimeUnit.NANOSECONDS);
169//        } finally {
170//            Platform.exit();
171//        }
172//    }
173}