001/*============================================================================*
002 * WARNING      This class contains automatically modified code.      WARNING *
003 *                                                                            *
004 * The method initComponents() and the variable declarations between the      *
005 * "// Variables declaration - do not modify" and                             *
006 * "// End of variables declaration" comments will be overwritten if modified *
007 * by hand. Using the NetBeans IDE to edit this file is strongly recommended. *
008 *                                                                            *
009 * See http://jmri.org/help/en/html/doc/Technical/NetBeansGUIEditor.shtml for *
010 * more information.                                                          *
011 *============================================================================*/
012package jmri.profile;
013
014import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
015import java.awt.Dimension;
016import java.awt.Frame;
017import java.awt.event.ActionEvent;
018import java.awt.event.ActionListener;
019import java.awt.event.AdjustmentEvent;
020import java.awt.event.KeyAdapter;
021import java.awt.event.KeyEvent;
022import java.awt.event.MouseAdapter;
023import java.awt.event.MouseEvent;
024import java.awt.event.WindowAdapter;
025import java.awt.event.WindowEvent;
026import java.beans.PropertyChangeEvent;
027import java.io.IOException;
028import javax.swing.GroupLayout;
029import javax.swing.JButton;
030import javax.swing.JDialog;
031import javax.swing.JFileChooser;
032import javax.swing.JLabel;
033import javax.swing.JList;
034import javax.swing.JScrollPane;
035import javax.swing.LayoutStyle;
036import javax.swing.ListSelectionModel;
037import javax.swing.Timer;
038import javax.swing.WindowConstants;
039import javax.swing.event.ListSelectionEvent;
040import javax.swing.event.ListSelectionListener;
041import jmri.util.FileUtil;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * Display a list of {@link Profile}s that can be selected to start a JMRI
047 * application.
048 * <p>
049 * This dialog is designed to be displayed while an application is starting. If
050 * the last profile used for the application can be found, this dialog will
051 * automatically start the application with that profile after 10 seconds unless
052 * the user intervenes.
053 *
054 * @author Randall Wood
055 */
056public class ProfileManagerDialog extends JDialog {
057
058    private Timer timer;
059    private int countDown;
060    private boolean disableTimer;
061
062    /**
063     * Creates new form ProfileManagerDialog
064     *
065     * @param parent The frame containing this dialog
066     * @param modal The modal parameter for parent JDialog
067     */
068    public ProfileManagerDialog(Frame parent, boolean modal) {
069        this(parent, modal, false);
070    }
071
072    /**
073     * Creates new form ProfileManagerDialog
074     *
075     * @param parent The frame containing this dialog
076     * @param modal The modal parameter for parent JDialog
077     * @param disableTimer true if the timer should be disabled
078     */
079    public ProfileManagerDialog(Frame parent, boolean modal, boolean disableTimer) {
080        super(parent, modal);
081        this.disableTimer = disableTimer;
082        initComponents();
083        ProfileManager.getDefault().addPropertyChangeListener(ProfileManager.ACTIVE_PROFILE, (PropertyChangeEvent evt) -> {
084            profiles.setSelectedValue(ProfileManager.getDefault().getActiveProfile(), true);
085            profiles.ensureIndexIsVisible(profiles.getSelectedIndex());
086            profiles.repaint();
087        });
088        ProfileManager.getDefault().addPropertyChangeListener(Profile.NAME, (PropertyChangeEvent evt) -> {
089            if (evt.getSource().getClass().equals(Profile.class) && evt.getPropertyName().equals(Profile.NAME)) {
090                profileNameChanged(((Profile) evt.getSource()));
091            }
092        });
093        this.jScrollPane1.getVerticalScrollBar().addAdjustmentListener((AdjustmentEvent e) -> {
094            profilesValueChanged(null);
095        });
096    }
097
098    /**
099     * This method is called from within the constructor to initialize the form.
100     * WARNING: Do NOT modify this code. The content of this method is always
101     * regenerated by the Form Editor.
102     */
103     // This uses the deprecated {@link JComponent#setNextFocusableComponent} method.
104     // Because it's autogenerated code, we leave that in place for now.
105    @SuppressWarnings( "deprecation" ) // JComponent#setNextFocusableComponent
106    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
107    private void initComponents() {
108
109        listLabel = new JLabel();
110        jScrollPane1 = new JScrollPane();
111        profiles = new JList<>();
112        btnSelect = new JButton();
113        btnCreate = new JButton();
114        btnUseExisting = new JButton();
115        countDownLbl = new JLabel();
116
117        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
118        setTitle(Bundle.getMessage("ProfileManagerDialog.title")); // NOI18N
119        setMinimumSize(new Dimension(310, 110));
120        addMouseListener(new MouseAdapter() {
121            @Override
122            public void mousePressed(MouseEvent evt) {
123                formMousePressed(evt);
124            }
125        });
126        addWindowListener(new WindowAdapter() {
127            @Override
128            public void windowOpened(WindowEvent evt) {
129                formWindowOpened(evt);
130            }
131            @Override
132            public void windowClosed(WindowEvent evt) {
133                formWindowClosed(evt);
134            }
135        });
136
137        listLabel.setText(Bundle.getMessage("ProfileManagerDialog.listLabel.text")); // NOI18N
138
139        profiles.setModel(new ProfileListModel());
140        profiles.setSelectedValue(ProfileManager.getDefault().getActiveProfile(), true);
141        profiles.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
142        profiles.setToolTipText(Bundle.getMessage("ProfileManagerDialog.profiles.toolTipText")); // NOI18N
143        profiles.setCellRenderer(new ProfileListCellRenderer());
144
145
146        profiles.setNextFocusableComponent(btnSelect);
147
148
149        profiles.addKeyListener(new KeyAdapter() {
150            @Override
151            public void keyPressed(KeyEvent evt) {
152                profilesKeyPressed(evt);
153            }
154        });
155        profiles.addListSelectionListener(new ListSelectionListener() {
156            @Override
157            public void valueChanged(ListSelectionEvent evt) {
158                profilesValueChanged(evt);
159            }
160        });
161        jScrollPane1.setViewportView(profiles);
162        profiles.ensureIndexIsVisible(profiles.getSelectedIndex());
163        profiles.getAccessibleContext().setAccessibleName(Bundle.getMessage("ProfileManagerDialog.profiles.AccessibleContext.accessibleName")); // NOI18N
164        profiles.getAccessibleContext().setAccessibleDescription(Bundle.getMessage("ProfileManagerDialog.profiles.toolTipText")); // NOI18N
165
166        btnSelect.setText(Bundle.getMessage("ProfileManagerDialog.btnSelect.text")); // NOI18N
167        btnSelect.addActionListener(new ActionListener() {
168            @Override
169            public void actionPerformed(ActionEvent evt) {
170                btnSelectActionPerformed(evt);
171            }
172        });
173
174        btnCreate.setText(Bundle.getMessage("ProfileManagerDialog.btnCreate.text")); // NOI18N
175        btnCreate.setToolTipText(Bundle.getMessage("ProfilePreferencesPanel.btnCreateNewProfile.toolTipText")); // NOI18N
176        btnCreate.addActionListener(new ActionListener() {
177            @Override
178            public void actionPerformed(ActionEvent evt) {
179                btnCreateActionPerformed(evt);
180            }
181        });
182
183        btnUseExisting.setText(Bundle.getMessage("ProfileManagerDialog.btnUseExisting.text")); // NOI18N
184        btnUseExisting.addActionListener(new ActionListener() {
185            @Override
186            public void actionPerformed(ActionEvent evt) {
187                btnUseExistingActionPerformed(evt);
188            }
189        });
190
191        countDownLbl.setText(Bundle.getMessage("ProfileManagerDialog.countDownLbl.text")); // NOI18N
192        countDownLbl.setToolTipText(Bundle.getMessage("ProfileManagerDialog.countDownLbl.toolTipText")); // NOI18N
193
194        GroupLayout layout = new GroupLayout(getContentPane());
195        getContentPane().setLayout(layout);
196        layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
197            .addGroup(layout.createSequentialGroup()
198                .addContainerGap()
199                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
200                    .addComponent(listLabel)
201                    .addComponent(countDownLbl))
202                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
203                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
204                    .addGroup(layout.createSequentialGroup()
205                        .addGap(0, 0, Short.MAX_VALUE)
206                        .addComponent(btnUseExisting)
207                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
208                        .addComponent(btnCreate)
209                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
210                        .addComponent(btnSelect))
211                    .addComponent(jScrollPane1))
212                .addContainerGap())
213        );
214        layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
215            .addGroup(layout.createSequentialGroup()
216                .addContainerGap()
217                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
218                    .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 168, Short.MAX_VALUE)
219                    .addComponent(listLabel))
220                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
221                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
222                    .addComponent(btnSelect)
223                    .addComponent(btnCreate)
224                    .addComponent(btnUseExisting)
225                    .addComponent(countDownLbl))
226                .addContainerGap())
227        );
228
229        listLabel.getAccessibleContext().setAccessibleName(Bundle.getMessage("ProfileManagerDialog.listLabel.text")); // NOI18N
230
231        pack();
232    }// </editor-fold>//GEN-END:initComponents
233
234    private void btnSelectActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnSelectActionPerformed
235        timer.stop();
236        countDown = -1;
237        countDownLbl.setVisible(false);
238        if (profiles.getSelectedValue() != null) {
239            ProfileManager.getDefault().setActiveProfile(profiles.getSelectedValue());
240            dispose();
241        }
242    }//GEN-LAST:event_btnSelectActionPerformed
243
244    private void btnCreateActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnCreateActionPerformed
245        timer.stop();
246        countDownLbl.setVisible(false);
247        AddProfileDialog apd = new AddProfileDialog(this, true, false);
248        apd.setLocationRelativeTo(this);
249        apd.setVisible(true);
250    }//GEN-LAST:event_btnCreateActionPerformed
251
252    private void btnUseExistingActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnUseExistingActionPerformed
253        timer.stop();
254        countDownLbl.setVisible(false);
255        JFileChooser chooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getHomePath());
256        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
257        chooser.setFileFilter(new ProfileFileFilter());
258        chooser.setFileView(new ProfileFileView());
259        // TODO: Use NetBeans OpenDialog if its availble
260        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
261            try {
262                Profile p = new Profile(chooser.getSelectedFile());
263                ProfileManager.getDefault().addProfile(p);
264                profiles.setSelectedValue(p, true);
265            } catch (IOException ex) {
266                log.warn("{} is not a profile directory", chooser.getSelectedFile());
267                // TODO: Display error dialog - selected file is not a profile directory
268            }
269        }
270    }//GEN-LAST:event_btnUseExistingActionPerformed
271
272    private void formWindowOpened(WindowEvent evt) {//GEN-FIRST:event_formWindowOpened
273        countDown = ProfileManager.getDefault().getAutoStartActiveProfileTimeout();
274        if (disableTimer) {
275            countDownLbl.setText("");
276        } else {
277            countDownLbl.setText(Integer.toString(countDown));
278        }
279        timer = new Timer(1000, (ActionEvent e) -> {
280            if (disableTimer) {
281                return;
282            }
283            if (countDown > 0) {
284                countDown--;
285                countDownLbl.setText(Integer.toString(countDown));
286            } else {
287                setVisible(false);
288                Profile profile = profiles.getSelectedValue();
289                ProfileManager.getDefault().setActiveProfile(profile);
290                if (profile != null) {
291                    log.info("Automatically starting with profile {} after timeout.", profile.getId());
292                } else {
293                    log.info("Automatically starting without a profile");
294                }
295                timer.stop();
296                countDown = -1;
297                dispose();
298            }
299        });
300        timer.setRepeats(true);
301        if (profiles.getModel().getSize() > 0
302                && null != ProfileManager.getDefault().getActiveProfile()
303                && countDown > 0) {
304            timer.start();
305        } else {
306            countDownLbl.setVisible(false);
307            btnSelect.setEnabled(false);
308        }
309    }//GEN-LAST:event_formWindowOpened
310
311    /**
312     * Get the active profile or display a dialog to prompt the user for it.
313     *
314     * @param f  The {@link java.awt.Frame} to display the dialog over
315     * @return the active or selected {@link Profile}
316     * @throws java.io.IOException if unable to read or set the starting Profile
317     * @see ProfileManager#getStartingProfile()
318     */
319    public static Profile getStartingProfile(Frame f) throws IOException {
320        ProfileManager manager = ProfileManager.getDefault();
321        if (ProfileManager.getStartingProfile() == null
322                || (System.getProperty(ProfileManager.SYSTEM_PROPERTY) == null
323                && !manager.isAutoStartActiveProfile())) {
324            Profile last = manager.getActiveProfile();
325            ProfileManagerDialog pmd = new ProfileManagerDialog(f, true);
326            pmd.setLocationRelativeTo(f);
327            pmd.setVisible(true);
328            if (last == null || !last.equals(manager.getActiveProfile())) {
329                manager.saveActiveProfile();
330            }
331        }
332        return manager.getActiveProfile();
333    }
334
335    private void profileNameChanged(Profile p) {
336        p.save();
337        log.info("Saving profile {}", p.getId());
338    }
339
340    private void profilesValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_profilesValueChanged
341        timer.stop();
342        countDownLbl.setVisible(false);
343        btnSelect.setEnabled(true);
344    }//GEN-LAST:event_profilesValueChanged
345
346    private void formMousePressed(MouseEvent evt) {//GEN-FIRST:event_formMousePressed
347        this.profilesValueChanged(null);
348    }//GEN-LAST:event_formMousePressed
349
350    private void profilesKeyPressed(KeyEvent evt) {//GEN-FIRST:event_profilesKeyPressed
351        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
352            this.btnSelect.doClick();
353        }
354    }//GEN-LAST:event_profilesKeyPressed
355
356    @SuppressFBWarnings(value = "DM_EXIT", justification = "This exit ensures launch is aborted if a profile is not selected or autostarted")
357    private void formWindowClosed(WindowEvent evt) {//GEN-FIRST:event_formWindowClosed
358        if (countDown != -1) {
359            // prevent an attempt to reclose this window from blocking application exit
360            countDown = -1;
361            // exit with an error code to indicate abnormal exit
362            System.exit(255);
363        }
364    }//GEN-LAST:event_formWindowClosed
365
366    // Variables declaration - do not modify//GEN-BEGIN:variables
367    private JButton btnCreate;
368    private JButton btnSelect;
369    private JButton btnUseExisting;
370    private JLabel countDownLbl;
371    private JScrollPane jScrollPane1;
372    private JLabel listLabel;
373    private JList<Profile> profiles;
374    // End of variables declaration//GEN-END:variables
375    private static final Logger log = LoggerFactory.getLogger(ProfileManagerDialog.class);
376}