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" )
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            public void valueChanged(ListSelectionEvent evt) {
157                profilesValueChanged(evt);
158            }
159        });
160        jScrollPane1.setViewportView(profiles);
161        profiles.ensureIndexIsVisible(profiles.getSelectedIndex());
162        profiles.getAccessibleContext().setAccessibleName(Bundle.getMessage("ProfileManagerDialog.profiles.AccessibleContext.accessibleName")); // NOI18N
163        profiles.getAccessibleContext().setAccessibleDescription(Bundle.getMessage("ProfileManagerDialog.profiles.toolTipText")); // NOI18N
164
165        btnSelect.setText(Bundle.getMessage("ProfileManagerDialog.btnSelect.text")); // NOI18N
166        btnSelect.addActionListener(new ActionListener() {
167            public void actionPerformed(ActionEvent evt) {
168                btnSelectActionPerformed(evt);
169            }
170        });
171
172        btnCreate.setText(Bundle.getMessage("ProfileManagerDialog.btnCreate.text")); // NOI18N
173        btnCreate.setToolTipText(Bundle.getMessage("ProfilePreferencesPanel.btnCreateNewProfile.toolTipText")); // NOI18N
174        btnCreate.addActionListener(new ActionListener() {
175            public void actionPerformed(ActionEvent evt) {
176                btnCreateActionPerformed(evt);
177            }
178        });
179
180        btnUseExisting.setText(Bundle.getMessage("ProfileManagerDialog.btnUseExisting.text")); // NOI18N
181        btnUseExisting.addActionListener(new ActionListener() {
182            public void actionPerformed(ActionEvent evt) {
183                btnUseExistingActionPerformed(evt);
184            }
185        });
186
187        countDownLbl.setText(Bundle.getMessage("ProfileManagerDialog.countDownLbl.text")); // NOI18N
188        countDownLbl.setToolTipText(Bundle.getMessage("ProfileManagerDialog.countDownLbl.toolTipText")); // NOI18N
189
190        GroupLayout layout = new GroupLayout(getContentPane());
191        getContentPane().setLayout(layout);
192        layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
193            .addGroup(layout.createSequentialGroup()
194                .addContainerGap()
195                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
196                    .addComponent(listLabel)
197                    .addComponent(countDownLbl))
198                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
199                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
200                    .addGroup(layout.createSequentialGroup()
201                        .addGap(0, 0, Short.MAX_VALUE)
202                        .addComponent(btnUseExisting)
203                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
204                        .addComponent(btnCreate)
205                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
206                        .addComponent(btnSelect))
207                    .addComponent(jScrollPane1))
208                .addContainerGap())
209        );
210        layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
211            .addGroup(layout.createSequentialGroup()
212                .addContainerGap()
213                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
214                    .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 168, Short.MAX_VALUE)
215                    .addComponent(listLabel))
216                .addPreferredGap(LayoutStyle.ComponentPlacement.UNRELATED)
217                .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
218                    .addComponent(btnSelect)
219                    .addComponent(btnCreate)
220                    .addComponent(btnUseExisting)
221                    .addComponent(countDownLbl))
222                .addContainerGap())
223        );
224
225        listLabel.getAccessibleContext().setAccessibleName(Bundle.getMessage("ProfileManagerDialog.listLabel.text")); // NOI18N
226
227        pack();
228    }// </editor-fold>//GEN-END:initComponents
229
230    private void btnSelectActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnSelectActionPerformed
231        timer.stop();
232        countDown = -1;
233        countDownLbl.setVisible(false);
234        if (profiles.getSelectedValue() != null) {
235            ProfileManager.getDefault().setActiveProfile(profiles.getSelectedValue());
236            dispose();
237        }
238    }//GEN-LAST:event_btnSelectActionPerformed
239
240    private void btnCreateActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnCreateActionPerformed
241        timer.stop();
242        countDownLbl.setVisible(false);
243        AddProfileDialog apd = new AddProfileDialog(this, true, false);
244        apd.setLocationRelativeTo(this);
245        apd.setVisible(true);
246    }//GEN-LAST:event_btnCreateActionPerformed
247
248    private void btnUseExistingActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnUseExistingActionPerformed
249        timer.stop();
250        countDownLbl.setVisible(false);
251        JFileChooser chooser = new JFileChooser(FileUtil.getHomePath());
252        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
253        chooser.setFileFilter(new ProfileFileFilter());
254        chooser.setFileView(new ProfileFileView());
255        // TODO: Use NetBeans OpenDialog if its availble
256        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
257            try {
258                Profile p = new Profile(chooser.getSelectedFile());
259                ProfileManager.getDefault().addProfile(p);
260                profiles.setSelectedValue(p, true);
261            } catch (IOException ex) {
262                log.warn("{} is not a profile directory", chooser.getSelectedFile());
263                // TODO: Display error dialog - selected file is not a profile directory
264            }
265        }
266    }//GEN-LAST:event_btnUseExistingActionPerformed
267
268    private void formWindowOpened(WindowEvent evt) {//GEN-FIRST:event_formWindowOpened
269        countDown = ProfileManager.getDefault().getAutoStartActiveProfileTimeout();
270        if (disableTimer) {
271            countDownLbl.setText("");
272        } else {
273            countDownLbl.setText(Integer.toString(countDown));
274        }
275        timer = new Timer(1000, (ActionEvent e) -> {
276            if (disableTimer) {
277                return;
278            }
279            if (countDown > 0) {
280                countDown--;
281                countDownLbl.setText(Integer.toString(countDown));
282            } else {
283                setVisible(false);
284                Profile profile = profiles.getSelectedValue();
285                ProfileManager.getDefault().setActiveProfile(profile);
286                if (profile != null) {
287                    log.info("Automatically starting with profile {} after timeout.", profile.getId());
288                } else {
289                    log.info("Automatically starting without a profile");
290                }
291                timer.stop();
292                countDown = -1;
293                dispose();
294            }
295        });
296        timer.setRepeats(true);
297        if (profiles.getModel().getSize() > 0
298                && null != ProfileManager.getDefault().getActiveProfile()
299                && countDown > 0) {
300            timer.start();
301        } else {
302            countDownLbl.setVisible(false);
303            btnSelect.setEnabled(false);
304        }
305    }//GEN-LAST:event_formWindowOpened
306
307    /**
308     * Get the active profile or display a dialog to prompt the user for it.
309     *
310     * @param f  The {@link java.awt.Frame} to display the dialog over
311     * @return the active or selected {@link Profile}
312     * @throws java.io.IOException if unable to read or set the starting Profile
313     * @see ProfileManager#getStartingProfile()
314     */
315    public static Profile getStartingProfile(Frame f) throws IOException {
316        ProfileManager manager = ProfileManager.getDefault();
317        if (ProfileManager.getStartingProfile() == null
318                || (System.getProperty(ProfileManager.SYSTEM_PROPERTY) == null
319                && !manager.isAutoStartActiveProfile())) {
320            Profile last = manager.getActiveProfile();
321            ProfileManagerDialog pmd = new ProfileManagerDialog(f, true);
322            pmd.setLocationRelativeTo(f);
323            pmd.setVisible(true);
324            if (last == null || !last.equals(manager.getActiveProfile())) {
325                manager.saveActiveProfile();
326            }
327        }
328        return manager.getActiveProfile();
329    }
330
331    private void profileNameChanged(Profile p) {
332        p.save();
333        log.info("Saving profile {}", p.getId());
334    }
335
336    private void profilesValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_profilesValueChanged
337        timer.stop();
338        countDownLbl.setVisible(false);
339        btnSelect.setEnabled(true);
340    }//GEN-LAST:event_profilesValueChanged
341
342    private void formMousePressed(MouseEvent evt) {//GEN-FIRST:event_formMousePressed
343        this.profilesValueChanged(null);
344    }//GEN-LAST:event_formMousePressed
345
346    private void profilesKeyPressed(KeyEvent evt) {//GEN-FIRST:event_profilesKeyPressed
347        if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
348            this.btnSelect.doClick();
349        }
350    }//GEN-LAST:event_profilesKeyPressed
351
352    @SuppressFBWarnings(value = "DM_EXIT", justification = "This exit ensures launch is aborted if a profile is not selected or autostarted")
353    private void formWindowClosed(WindowEvent evt) {//GEN-FIRST:event_formWindowClosed
354        if (countDown != -1) {
355            // prevent an attempt to reclose this window from blocking application exit
356            countDown = -1;
357            // exit with an error code to indicate abnormal exit
358            System.exit(255);
359        }
360    }//GEN-LAST:event_formWindowClosed
361
362    // Variables declaration - do not modify//GEN-BEGIN:variables
363    private JButton btnCreate;
364    private JButton btnSelect;
365    private JButton btnUseExisting;
366    private JLabel countDownLbl;
367    private JScrollPane jScrollPane1;
368    private JLabel listLabel;
369    private JList<Profile> profiles;
370    // End of variables declaration//GEN-END:variables
371    private static final Logger log = LoggerFactory.getLogger(ProfileManagerDialog.class);
372}