001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028package edu.wisc.ssec.mcidasv.util;
029
030import static edu.wisc.ssec.mcidasv.util.McVGuiUtils.setButtonImage;
031
032import java.awt.Dimension;
033import java.awt.EventQueue;
034import java.awt.GridBagConstraints;
035import java.awt.GridBagLayout;
036import java.awt.event.ActionEvent;
037import java.awt.event.WindowAdapter;
038import java.awt.event.WindowEvent;
039
040import java.io.IOException;
041
042import javax.swing.GroupLayout;
043import javax.swing.ImageIcon;
044import javax.swing.JButton;
045import javax.swing.JDialog;
046import javax.swing.JFrame;
047import javax.swing.JLabel;
048import javax.swing.JPanel;
049import javax.swing.JScrollPane;
050import javax.swing.JTextPane;
051import javax.swing.LayoutStyle;
052import javax.swing.WindowConstants;
053import javax.swing.event.HyperlinkEvent;
054
055import org.slf4j.Logger;
056import org.slf4j.LoggerFactory;
057
058/**
059 * {@code WelcomeWindow} is really just intended to <i>try</i> to detect known
060 * hardware problems and inform the user about any problems.
061 *
062 * <p>The current implementation does not perform <i>any</i> detection, but
063 * expect this to change.
064 */
065// NOTE TO MCV CODERS:
066// **DOCUMENT WHAT CHECKS AND/OR DETECTION ARE BEING PERFORMED**
067//public class WelcomeWindow extends JFrame {
068public class WelcomeWindow extends JDialog {
069    
070    private static final Logger logger =
071        LoggerFactory.getLogger(WelcomeWindow.class);
072    
073    /** Path to {@literal "header"} image. */
074    private static final String LOGO_PATH = 
075        "/edu/wisc/ssec/mcidasv/images/mcidasv_logo.gif";
076
077    /** Path to the HTML to display within {@link #textPane}. */
078    private static final String WELCOME_HTML =
079        "/edu/wisc/ssec/mcidasv/resources/welcome.html";
080
081    /**
082     * Message to display if there was a problem loading
083     * {@link #WELCOME_HTML}.
084     */
085    private static final String ERROR_MESSAGE =
086        "McIDAS-V had a problem displaying its welcome message. Please"
087        + " contact the McIDAS Help Desk for assistance.";
088
089    /** Dimensions of the welcome window frame. */
090    private static final Dimension WINDOW_SIZE = new Dimension(495, 431);
091
092    /** Default auto-quit delay (in milliseconds). */
093    public static final long DEFAULT_QUIT_DELAY = 2500;
094
095    /** Java-friendly location of the path to the welcome message. */
096    private final java.net.URL contents;
097
098    /** Whether or not the window should automatically close. */
099    private final boolean autoQuit;
100
101    /** Delay in milliseconds for auto-quitting. */
102    private final long autoQuitDelay;
103
104    /** 
105     * Creates new form WelcomeWindow.
106     */
107    public WelcomeWindow() {
108        this(false, Long.MAX_VALUE);
109    }
110
111    public WelcomeWindow(boolean autoQuit, long delay) {
112        this.autoQuit = autoQuit;
113        this.autoQuitDelay = delay;
114        this.contents = WelcomeWindow.class.getResource(WELCOME_HTML);
115        initComponents();
116    }
117
118    /** 
119     * This method is called from within the constructor to
120     * initialize the form.
121     * WARNING: Do NOT modify this code. The content of this method is
122     * always regenerated by the Form Editor.
123     */
124    // <editor-fold defaultstate="collapsed" desc="Generated Code">
125    private void initComponents() {
126
127        setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
128        setTitle("Welcome to McIDAS-V");
129        setLocationByPlatform(true);
130        addWindowListener(new WindowAdapter() {
131            @Override public void windowClosing(WindowEvent evt) {
132                formWindowClosing(evt);
133            }
134        });
135
136        logoPanel.setLayout(new GridBagLayout());
137
138        logoLabel.setIcon(new ImageIcon(getClass().getResource(LOGO_PATH))); // NOI18N
139        logoPanel.add(logoLabel, new GridBagConstraints());
140
141        textPane.setEditable(false);
142        try {
143            textPane.setPage(contents);
144        } catch (IOException e) {
145            textPane.setText(ERROR_MESSAGE);
146            logger.error("Could not change contents of textPane", e);
147        }
148        textPane.addHyperlinkListener(this::textPaneHyperlinkUpdate);
149        scrollPane.setViewportView(textPane);
150
151        GroupLayout mainPanelLayout = new GroupLayout(mainPanel);
152        mainPanel.setLayout(mainPanelLayout);
153        mainPanelLayout.setHorizontalGroup(
154            mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
155            .addComponent(scrollPane, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 360, Short.MAX_VALUE)
156        );
157        mainPanelLayout.setVerticalGroup(
158            mainPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
159            .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 273, Short.MAX_VALUE)
160        );
161
162        setButtonImage(startButton, McVGuiUtils.ICON_APPLY_SMALL);
163        startButton.addActionListener(this::startButtonActionPerformed);
164
165        setButtonImage(quitButton, McVGuiUtils.ICON_CANCEL_SMALL);
166        quitButton.addActionListener(this::quitButtonActionPerformed);
167
168        GroupLayout buttonPanelLayout = new GroupLayout(buttonPanel);
169        buttonPanel.setLayout(buttonPanelLayout);
170        buttonPanelLayout.setHorizontalGroup(
171            buttonPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
172            .addGroup(GroupLayout.Alignment.TRAILING, buttonPanelLayout.createSequentialGroup()
173                .addContainerGap(144, Short.MAX_VALUE)
174                .addComponent(quitButton)
175                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
176                .addComponent(startButton))
177        );
178        buttonPanelLayout.setVerticalGroup(
179            buttonPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
180            .addGroup(buttonPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
181                .addComponent(startButton)
182                .addComponent(quitButton))
183        );
184
185        GroupLayout layout = new GroupLayout(getContentPane());
186        getContentPane().setLayout(layout);
187        layout.setHorizontalGroup(
188            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
189            .addGroup(layout.createSequentialGroup()
190                .addContainerGap()
191                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
192                    .addComponent(mainPanel, GroupLayout.Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
193                    .addComponent(buttonPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
194                    .addComponent(logoPanel, GroupLayout.DEFAULT_SIZE, 360, Short.MAX_VALUE))
195                .addContainerGap())
196        );
197        layout.setVerticalGroup(
198            layout.createParallelGroup(GroupLayout.Alignment.LEADING)
199            .addGroup(layout.createSequentialGroup()
200                .addContainerGap()
201                .addComponent(logoPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
202                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
203                .addComponent(mainPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
204                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
205                .addComponent(buttonPanel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
206                .addContainerGap())
207        );
208
209        pack();
210        setLocationRelativeTo(null);
211        setSize(WINDOW_SIZE);
212        setModal(true);
213    }// </editor-fold>
214
215    /**
216     * Show or close the Welcome Window.
217     *
218     * <p>Overridden in McV to handle auto-quitting. If we're supposed to
219     * auto-quit, a thread will be started that calls {@link JButton#doClick()}.
220     * </p>
221     *
222     * @param visible Whether or not the dialog should be opened or closed.
223     */
224    @Override public void setVisible(boolean visible) {
225        if (autoQuit) {
226            new Thread() {
227                public void run() {
228                    try {
229                        sleep(autoQuitDelay);
230                    } catch (InterruptedException ex) {
231                        // not much else to be done...
232                        logger.error("Problem changing visibility", ex);
233                    } finally {
234                        EventQueue.invokeLater(startButton::doClick);
235                    }
236                }
237            }.start();
238        }
239        super.setVisible(visible);
240    }
241
242    /**
243     * Handles the user clicking on {@link #startButton}. 
244     *
245     * @param evt Event to handle. Currently ignored.
246     */
247    private void startButtonActionPerformed(ActionEvent evt) {
248        this.setVisible(false);
249    }
250
251    /**
252     * Handles the user clicking on {@link #quitButton}. Doesn't do anything
253     * aside from handing off things to
254     * {@link #formWindowClosing(WindowEvent)}
255     *
256     * @param evt Event to handle. Currently ignored.
257     *
258     * @see #formWindowClosing(WindowEvent)
259     */
260    private void quitButtonActionPerformed(ActionEvent evt) {
261        formWindowClosing(null);
262    }
263
264    /**
265     * Handles the user opting to close the welcome window
266     * {@link JFrame}. Executes {@code System.exit(1)} in an
267     * effort to signal to the startup scripts that window terminated 
268     * {@literal "abnormally"}.
269     * 
270     * <p>An abnormal termination will result in the startup script 
271     * terminating the launch of McIDAS-V.
272     * 
273     * @param evt Event to handle. Currently ignored.
274     */
275    private void formWindowClosing(WindowEvent evt) {
276        System.exit(1);
277    }
278
279    /**
280     * Listens to {@link #textPane} in order to handle the user clicking on
281     * HTML links.
282     *
283     * @param evt Event to handle. Anything other than
284     * an {@literal "ACTIVATED"}
285     * {@link javax.swing.event.HyperlinkEvent.EventType HyperlinkEvent.EventType}
286     * is ignored.
287     *
288     * @see WebBrowser#browse(String)
289     */
290    private void textPaneHyperlinkUpdate(HyperlinkEvent evt) {
291        if (evt.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
292            return;
293        }
294
295        String url = null;
296        if (evt.getURL() == null) {
297            url = evt.getDescription();
298        } else {
299            url = evt.getURL().toString();
300        }
301
302        WebBrowser.browse(url);
303    }
304
305    /**
306     * Kick the tires.
307     *
308     * @param args Command line arguments
309     */
310    public static void main(String[] args) {
311        EventQueue.invokeLater(() -> {
312            WelcomeWindow ww;
313            if (args.length == 2) {
314                boolean autoQuit = "-autoquit".equals(args[0]);
315                long delay = Long.parseLong(args[1]);
316                ww = new WelcomeWindow(autoQuit, delay);
317            } else if (args.length == 1) {
318                boolean autoQuit = "-autoquit".equals(args[0]);
319                ww = new WelcomeWindow(autoQuit, DEFAULT_QUIT_DELAY);
320            }
321            else {
322                ww = new WelcomeWindow();
323            }
324            ww.setVisible(true);
325        });
326    }
327
328    // boring gui components
329    private final JPanel buttonPanel = new JPanel();
330    private final JLabel logoLabel = new JLabel();
331    private final JPanel logoPanel = new JPanel();
332    private final JPanel mainPanel = new JPanel();
333    private final JButton quitButton = new JButton("Quit");
334    private final JScrollPane scrollPane = new JScrollPane();
335    private final JButton startButton = new JButton("Start McIDAS-V");
336    private final JTextPane textPane = new JTextPane();
337}