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 java.awt.Cursor;
015import java.awt.Frame;
016import java.awt.event.ActionEvent;
017import java.awt.event.ActionListener;
018import java.awt.event.MouseEvent;
019import java.beans.PropertyChangeListener;
020import java.io.File;
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.ResourceBundle;
024
025import javax.swing.ButtonGroup;
026import javax.swing.GroupLayout;
027import javax.swing.JButton;
028import javax.swing.JComponent;
029import javax.swing.JFileChooser;
030import javax.swing.JLabel;
031import javax.swing.JMenuItem;
032import javax.swing.JPanel;
033import javax.swing.JPopupMenu;
034import javax.swing.JRadioButton;
035import javax.swing.JScrollPane;
036import javax.swing.JSpinner;
037import javax.swing.JTabbedPane;
038import javax.swing.JTable;
039import javax.swing.LayoutStyle;
040import javax.swing.SpinnerNumberModel;
041import javax.swing.SwingUtilities;
042import javax.swing.event.ChangeEvent;
043import javax.swing.event.ChangeListener;
044import javax.swing.event.ListSelectionEvent;
045import javax.swing.event.ListSelectionListener;
046import javax.swing.event.PopupMenuEvent;
047import javax.swing.event.PopupMenuListener;
048import javax.swing.filechooser.FileNameExtensionFilter;
049
050import jmri.jmrit.roster.Roster;
051import jmri.swing.PreferencesPanel;
052import jmri.util.FileUtil;
053import jmri.util.prefs.InitializationException;
054import jmri.util.swing.JmriJOptionPane;
055
056import org.jdom2.JDOMException;
057import org.openide.util.lookup.ServiceProvider;
058
059/**
060 * A JPanel suitable for managing {@link jmri.profile.Profile}s within a
061 * preferences window.
062 *
063 * @author Randall Wood
064 */
065@ServiceProvider(service = PreferencesPanel.class)
066public final class ProfilePreferencesPanel extends JPanel implements PreferencesPanel {
067
068    /**
069     * Creates new form ProfilePreferencesPanel
070     */
071    public ProfilePreferencesPanel() {
072        initComponents();
073        this.spinnerTimeout.setValue(ProfileManager.getDefault().getAutoStartActiveProfileTimeout());
074        this.profilesTblValueChanged(null);
075        this.searchPathsTblValueChanged(null);
076        int index = ProfileManager.getDefault().getAllProfiles().indexOf(ProfileManager.getDefault().getActiveProfile());
077        if (index != -1) {
078            this.profilesTbl.setRowSelectionInterval(index, index);
079        }
080    }
081
082    /**
083     * This method is called from within the constructor to initialize the form.
084     * WARNING: Do NOT modify this code. The content of this method is always
085     * regenerated by the Form Editor.
086     */
087    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
088    private void initComponents() {
089
090        profilesPopupMenu = new JPopupMenu();
091        renameMI = new JMenuItem();
092        jSeparator1 = new JPopupMenu.Separator();
093        copyMI = new JMenuItem();
094        deleteMI = new JMenuItem();
095        grpStartWithSelectors = new ButtonGroup();
096        jTabbedPane1 = new JTabbedPane();
097        enabledPanel = new JPanel();
098        jScrollPane1 = new JScrollPane();
099        profilesTbl = new JTable() {
100            //Implement table cell tool tips.
101            @Override
102            public String getToolTipText(MouseEvent e) {
103                try {
104                    return getValueAt(rowAtPoint(e.getPoint()), -1).toString();
105                } catch (RuntimeException e1) {
106                    //catch null pointer exception if mouse is over an empty line
107                }
108                return null;
109            }};
110            btnOpenExistingProfile = new JButton();
111            btnDeleteProfile = new JButton();
112            btnCreateNewProfile = new JButton();
113            btnActivateProfile = new JButton();
114            btnExportProfile = new JButton();
115            btnCopyProfile = new JButton();
116            spinnerTimeout = new JSpinner();
117            jLabel1 = new JLabel();
118            rdoStartWithActiveProfile = new JRadioButton();
119            rdoStartWithProfileSelector = new JRadioButton();
120            searchPathsPanel = new JPanel();
121            btnRemoveSearchPath = new JButton();
122            btnAddSearchPath = new JButton();
123            jScrollPane3 = new JScrollPane();
124            searchPathsTbl = new JTable() {
125                //Implement table cell tool tips.
126                @Override
127                public String getToolTipText(MouseEvent e) {
128                    try {
129                        return getValueAt(rowAtPoint(e.getPoint()), -1).toString();
130                    } catch (RuntimeException e1) {
131                        //catch null pointer exception if mouse is over an empty line
132                    }
133                    return null;
134                }};
135
136                profilesPopupMenu.addPopupMenuListener(new PopupMenuListener() {
137                    @Override
138                    public void popupMenuWillBecomeVisible(PopupMenuEvent evt) {
139                        profilesPopupMenuPopupMenuWillBecomeVisible(evt);
140                    }
141                    @Override
142                    public void popupMenuWillBecomeInvisible(PopupMenuEvent evt) {
143                    }
144                    @Override
145                    public void popupMenuCanceled(PopupMenuEvent evt) {
146                    }
147                });
148
149                ResourceBundle bundle = ResourceBundle.getBundle("jmri/profile/Bundle"); // NOI18N
150                renameMI.setText(bundle.getString("ProfilePreferencesPanel.renameMI.text")); // NOI18N
151                renameMI.addActionListener(new ActionListener() {
152                    @Override
153                    public void actionPerformed(ActionEvent evt) {
154                        renameMIActionPerformed(evt);
155                    }
156                });
157                profilesPopupMenu.add(renameMI);
158                profilesPopupMenu.add(jSeparator1);
159
160                copyMI.setText(bundle.getString("ProfilePreferencesPanel.copyMI.text")); // NOI18N
161                profilesPopupMenu.add(copyMI);
162
163                deleteMI.setText(bundle.getString("ProfilePreferencesPanel.deleteMI.text")); // NOI18N
164                profilesPopupMenu.add(deleteMI);
165
166                if (ProfileManager.getDefault().isAutoStartActiveProfile()) {
167                    this.rdoStartWithActiveProfile.setSelected(true);
168                } else {
169                    this.rdoStartWithProfileSelector.setSelected(true);
170                }
171
172                profilesTbl.setModel(new ProfileTableModel());
173                profilesTbl.getSelectionModel().addListSelectionListener(new ProfilesSelectionListener());
174                profilesTbl.getTableHeader().setReorderingAllowed(false);
175                jScrollPane1.setViewportView(profilesTbl);
176
177                btnOpenExistingProfile.setText(bundle.getString("ProfilePreferencesPanel.btnOpenExistingProfile.text")); // NOI18N
178                btnOpenExistingProfile.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnOpenExistingProfile.toolTipText")); // NOI18N
179                btnOpenExistingProfile.addActionListener(new ActionListener() {
180                    @Override
181                    public void actionPerformed(ActionEvent evt) {
182                        btnOpenExistingProfileActionPerformed(evt);
183                    }
184                });
185
186                btnDeleteProfile.setText(bundle.getString("ProfilePreferencesPanel.btnDeleteProfile.text")); // NOI18N
187                btnDeleteProfile.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnDeleteProfile.toolTipText")); // NOI18N
188                btnDeleteProfile.addActionListener(new ActionListener() {
189                    @Override
190                    public void actionPerformed(ActionEvent evt) {
191                        btnDeleteProfileActionPerformed(evt);
192                    }
193                });
194
195                btnCreateNewProfile.setText(bundle.getString("ProfilePreferencesPanel.btnCreateNewProfile.text")); // NOI18N
196                btnCreateNewProfile.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnCreateNewProfile.toolTipText")); // NOI18N
197                btnCreateNewProfile.addActionListener(new ActionListener() {
198                    @Override
199                    public void actionPerformed(ActionEvent evt) {
200                        btnCreateNewProfileActionPerformed(evt);
201                    }
202                });
203
204                btnActivateProfile.setText(bundle.getString("ProfilePreferencesPanel.btnActivateProfile.text")); // NOI18N
205                btnActivateProfile.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnActivateProfile.toolTipText")); // NOI18N
206                btnActivateProfile.addActionListener(new ActionListener() {
207                    @Override
208                    public void actionPerformed(ActionEvent evt) {
209                        btnActivateProfileActionPerformed(evt);
210                    }
211                });
212
213                btnExportProfile.setText(bundle.getString("ProfilePreferencesPanel.btnExportProfile.text")); // NOI18N
214                btnExportProfile.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnExportProfile.toolTipText")); // NOI18N
215                btnExportProfile.addActionListener(new ActionListener() {
216                    @Override
217                    public void actionPerformed(ActionEvent evt) {
218                        btnExportProfileActionPerformed(evt);
219                    }
220                });
221
222                btnCopyProfile.setText(bundle.getString("ProfilePreferencesPanel.btnCopyProfile.text")); // NOI18N
223                btnCopyProfile.addActionListener(new ActionListener() {
224                    @Override
225                    public void actionPerformed(ActionEvent evt) {
226                        btnCopyProfileActionPerformed(evt);
227                    }
228                });
229
230                spinnerTimeout.setModel(new SpinnerNumberModel(10, 0, 500, 1));
231                spinnerTimeout.addChangeListener(new ChangeListener() {
232                    @Override
233                    public void stateChanged(ChangeEvent evt) {
234                        spinnerTimeoutStateChanged(evt);
235                    }
236                });
237
238                jLabel1.setText(bundle.getString("ProfilePreferencesPanel.jLabel1.text")); // NOI18N
239
240                grpStartWithSelectors.add(rdoStartWithActiveProfile);
241                rdoStartWithActiveProfile.setText(bundle.getString("ProfilePreferencesPanel.rdoStartWithActiveProfile.text")); // NOI18N
242                rdoStartWithActiveProfile.addActionListener(new ActionListener() {
243                    @Override
244                    public void actionPerformed(ActionEvent evt) {
245                        rdoStartWithActiveProfileActionPerformed(evt);
246                    }
247                });
248
249                grpStartWithSelectors.add(rdoStartWithProfileSelector);
250                rdoStartWithProfileSelector.setText(bundle.getString("ProfilePreferencesPanel.rdoStartWithProfileSelector.text")); // NOI18N
251                rdoStartWithProfileSelector.addActionListener(new ActionListener() {
252                    @Override
253                    public void actionPerformed(ActionEvent evt) {
254                        rdoStartWithProfileSelectorActionPerformed(evt);
255                    }
256                });
257
258                GroupLayout enabledPanelLayout = new GroupLayout(enabledPanel);
259                enabledPanel.setLayout(enabledPanelLayout);
260                enabledPanelLayout.setHorizontalGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
261                    .addGroup(enabledPanelLayout.createSequentialGroup()
262                        .addContainerGap()
263                        .addGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
264                            .addComponent(jScrollPane1)
265                            .addGroup(enabledPanelLayout.createSequentialGroup()
266                                .addComponent(btnActivateProfile)
267                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
268                                .addComponent(btnOpenExistingProfile)
269                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
270                                .addComponent(btnCreateNewProfile)
271                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
272                                .addComponent(btnCopyProfile)
273                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
274                                .addComponent(btnExportProfile)
275                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, 60, Short.MAX_VALUE)
276                                .addComponent(btnDeleteProfile))
277                            .addGroup(enabledPanelLayout.createSequentialGroup()
278                                .addGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
279                                    .addComponent(rdoStartWithActiveProfile)
280                                    .addGroup(enabledPanelLayout.createSequentialGroup()
281                                        .addComponent(rdoStartWithProfileSelector)
282                                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
283                                        .addComponent(spinnerTimeout, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
284                                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
285                                        .addComponent(jLabel1)))
286                                .addGap(0, 0, Short.MAX_VALUE)))
287                        .addContainerGap())
288                );
289                enabledPanelLayout.setVerticalGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
290                    .addGroup(enabledPanelLayout.createSequentialGroup()
291                        .addContainerGap()
292                        .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 190, Short.MAX_VALUE)
293                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
294                        .addGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
295                            .addComponent(btnOpenExistingProfile)
296                            .addComponent(btnCreateNewProfile)
297                            .addComponent(btnActivateProfile)
298                            .addComponent(btnExportProfile)
299                            .addComponent(btnDeleteProfile)
300                            .addComponent(btnCopyProfile))
301                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
302                        .addComponent(rdoStartWithActiveProfile)
303                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
304                        .addGroup(enabledPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
305                            .addComponent(rdoStartWithProfileSelector)
306                            .addComponent(spinnerTimeout, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
307                            .addComponent(jLabel1)))
308                );
309
310                jTabbedPane1.addTab(bundle.getString("ProfilePreferencesPanel.enabledPanel.TabConstraints.tabTitle"), enabledPanel); // NOI18N
311
312                btnRemoveSearchPath.setText(bundle.getString("ProfilePreferencesPanel.btnRemoveSearchPath.text")); // NOI18N
313                btnRemoveSearchPath.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnRemoveSearchPath.toolTipText")); // NOI18N
314                btnRemoveSearchPath.addActionListener(new ActionListener() {
315                    @Override
316                    public void actionPerformed(ActionEvent evt) {
317                        btnRemoveSearchPathActionPerformed(evt);
318                    }
319                });
320
321                btnAddSearchPath.setText(bundle.getString("ProfilePreferencesPanel.btnAddSearchPath.text")); // NOI18N
322                btnAddSearchPath.setToolTipText(bundle.getString("ProfilePreferencesPanel.btnAddSearchPath.toolTipText")); // NOI18N
323                btnAddSearchPath.addActionListener(new ActionListener() {
324                    @Override
325                    public void actionPerformed(ActionEvent evt) {
326                        btnAddSearchPathActionPerformed(evt);
327                    }
328                });
329
330                searchPathsTbl.setModel(new SearchPathTableModel());
331                searchPathsTbl.getSelectionModel().addListSelectionListener(new SearchPathSelectionListener());
332                searchPathsTbl.getTableHeader().setReorderingAllowed(false);
333                jScrollPane3.setViewportView(searchPathsTbl);
334
335                GroupLayout searchPathsPanelLayout = new GroupLayout(searchPathsPanel);
336                searchPathsPanel.setLayout(searchPathsPanelLayout);
337                searchPathsPanelLayout.setHorizontalGroup(searchPathsPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
338                    .addGroup(searchPathsPanelLayout.createSequentialGroup()
339                        .addContainerGap()
340                        .addGroup(searchPathsPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
341                            .addComponent(jScrollPane3, GroupLayout.DEFAULT_SIZE, 667, Short.MAX_VALUE)
342                            .addGroup(searchPathsPanelLayout.createSequentialGroup()
343                                .addComponent(btnAddSearchPath)
344                                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
345                                .addComponent(btnRemoveSearchPath)))
346                        .addContainerGap())
347                );
348                searchPathsPanelLayout.setVerticalGroup(searchPathsPanelLayout.createParallelGroup(GroupLayout.Alignment.LEADING)
349                    .addGroup(searchPathsPanelLayout.createSequentialGroup()
350                        .addContainerGap()
351                        .addComponent(jScrollPane3, GroupLayout.DEFAULT_SIZE, 247, Short.MAX_VALUE)
352                        .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
353                        .addGroup(searchPathsPanelLayout.createParallelGroup(GroupLayout.Alignment.BASELINE)
354                            .addComponent(btnAddSearchPath)
355                            .addComponent(btnRemoveSearchPath))
356                        .addContainerGap())
357                );
358
359                jTabbedPane1.addTab(bundle.getString("ProfilePreferencesPanel.searchPathsPanel.TabConstraints.tabTitle_1"), searchPathsPanel); // NOI18N
360
361                GroupLayout layout = new GroupLayout(this);
362                this.setLayout(layout);
363                layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
364                    .addComponent(jTabbedPane1, GroupLayout.Alignment.TRAILING)
365                );
366                layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
367                    .addComponent(jTabbedPane1)
368                );
369
370                jTabbedPane1.getAccessibleContext().setAccessibleName(bundle.getString("ProfilePreferencesPanel.enabledPanel.TabConstraints.tabTitle")); // NOI18N
371            }// </editor-fold>//GEN-END:initComponents
372
373    private void renameMIActionPerformed(ActionEvent evt) {//GEN-FIRST:event_renameMIActionPerformed
374        // TODO add your handling code here:
375    }//GEN-LAST:event_renameMIActionPerformed
376
377    private void profilesPopupMenuPopupMenuWillBecomeVisible(PopupMenuEvent evt) {//GEN-FIRST:event_profilesPopupMenuPopupMenuWillBecomeVisible
378        if (profilesTbl.getSelectedRowCount() == 1) {
379            this.renameMI.setEnabled(true);
380        }
381    }//GEN-LAST:event_profilesPopupMenuPopupMenuWillBecomeVisible
382
383    private void btnAddSearchPathActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnAddSearchPathActionPerformed
384        JFileChooser chooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getHomePath());
385        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
386        chooser.setFileFilter(new ProfileFileFilter());
387        chooser.setFileView(new ProfileFileView());
388        // TODO: Use NetBeans OpenDialog if its availble
389        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
390            try {
391                ProfileManager.getDefault().addSearchPath(chooser.getSelectedFile());
392                int index = ProfileManager.getDefault().getAllSearchPaths().indexOf(chooser.getSelectedFile());
393                this.searchPathsTbl.setRowSelectionInterval(index, index);
394            } catch (IOException ex) {
395                log.warn("Unable to write profiles while adding search path {}", chooser.getSelectedFile().getPath(), ex);
396                JmriJOptionPane.showMessageDialog(this,
397                        Bundle.getMessage("ProfilePreferencesPanel.btnAddSearchPath.errorMessage",
398                                chooser.getSelectedFile().getPath(),
399                                ex.getLocalizedMessage()),
400                        Bundle.getMessage("ProfilePreferencesPanel.btnAddSearchPath.errorTitle"),
401                        JmriJOptionPane.ERROR_MESSAGE);
402            }
403        }
404    }//GEN-LAST:event_btnAddSearchPathActionPerformed
405
406    private void btnRemoveSearchPathActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnRemoveSearchPathActionPerformed
407        ArrayList<File> paths = new ArrayList<>(this.searchPathsTbl.getSelectedRowCount());
408        for (int row : this.searchPathsTbl.getSelectedRows()) {
409            paths.add(ProfileManager.getDefault().getSearchPaths(row));
410        }
411        for (File path : paths) {
412            try {
413                ProfileManager.getDefault().removeSearchPath(path);
414            } catch (IOException ex) {
415                log.warn("Unable to write profiles while removing search path {}", path.getPath(), ex);
416                JmriJOptionPane.showMessageDialog(this,
417                        Bundle.getMessage("ProfilePreferencesPanel.btnRemoveSearchPath.errorMessage", path.getPath(), ex.getLocalizedMessage()),
418                        Bundle.getMessage("ProfilePreferencesPanel.btnRemoveSearchPath.errorTitle"),
419                        JmriJOptionPane.ERROR_MESSAGE);
420            }
421        }
422    }//GEN-LAST:event_btnRemoveSearchPathActionPerformed
423
424    private void btnExportProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnExportProfileActionPerformed
425        Profile p = ProfileManager.getDefault().getProfiles(profilesTbl.getSelectedRow());
426        if (p == null) {
427            // abort if selection does not match an existing profile
428            return;
429        }
430        JFileChooser chooser = new jmri.util.swing.JmriJFileChooser();
431        chooser.setFileFilter(new FileNameExtensionFilter("ZIP Archives", "zip"));
432        chooser.setFileView(new ProfileFileView());
433        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
434        chooser.setSelectedFile(new File(p.getName() + ".zip"));
435        if (chooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
436            try {
437                if (chooser.getSelectedFile().exists()) {
438                    int result = JmriJOptionPane.showConfirmDialog(this,
439                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.overwriteMessage",
440                                    chooser.getSelectedFile().getName(),
441                                    chooser.getSelectedFile().getParentFile().getName()),
442                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.overwriteTitle"),
443                            JmriJOptionPane.YES_NO_OPTION,
444                            JmriJOptionPane.WARNING_MESSAGE);
445                    if (result == JmriJOptionPane.YES_OPTION) {
446                        if (!chooser.getSelectedFile().delete()) {
447                            JmriJOptionPane.showMessageDialog(this,
448                                    Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.failureToDeleteMessage",
449                                            chooser.getSelectedFile().getName(),
450                                            chooser.getSelectedFile().getParentFile().getName()),
451                                    Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.failureToDeleteTitle"),
452                                    JmriJOptionPane.ERROR_MESSAGE);
453                        }
454                    } else {
455                        this.btnExportProfileActionPerformed(evt);
456                        return;
457                    }
458                }
459                boolean exportExternalUserFiles = false;
460                boolean exportExternalRoster = false;
461                if (!(new File(FileUtil.getUserFilesPath())).getCanonicalPath().startsWith(p.getPath().getCanonicalPath())) {
462                    int result = JmriJOptionPane.showConfirmDialog(this,
463                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.externalUserFilesMessage"),
464                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.externalUserFilesTitle"),
465                            JmriJOptionPane.YES_NO_OPTION,
466                            JmriJOptionPane.QUESTION_MESSAGE);
467                    if (result == JmriJOptionPane.YES_OPTION) {
468                        exportExternalUserFiles = true;
469                    }
470                }
471                if (!(new File(Roster.getDefault().getRosterLocation())).getCanonicalPath().startsWith(p.getPath().getCanonicalPath())
472                        && !Roster.getDefault().getRosterLocation().startsWith(FileUtil.getUserFilesPath())) {
473                    int result = JmriJOptionPane.showConfirmDialog(this,
474                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.externalRosterMessage"),
475                            Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.externalRosterTitle"),
476                            JmriJOptionPane.YES_NO_OPTION,
477                            JmriJOptionPane.QUESTION_MESSAGE);
478                    if (result == JmriJOptionPane.YES_OPTION) {
479                        exportExternalRoster = true;
480                    }
481                }
482                //if (ProfileManager.getDefault().getActiveProfile() == p) {
483                //    // TODO: save roster, panels, operations if needed and safe to do so
484                //}
485                ProfileManager.getDefault().export(p, chooser.getSelectedFile(), exportExternalUserFiles, exportExternalRoster);
486                log.info("Profile \"{}\" exported to \"{}\"", p.getName(), chooser.getSelectedFile().getName());
487                JmriJOptionPane.showMessageDialog(this,
488                        Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.successMessage",
489                                p.getName(), chooser.getSelectedFile().getName()),
490                        Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.successTitle"),
491                        JmriJOptionPane.INFORMATION_MESSAGE);
492            } catch (IOException | JDOMException | InitializationException ex) {
493                log.warn("Unable to export profile \"{}\" to {}", p.getName(), chooser.getSelectedFile().getPath(), ex);
494                JmriJOptionPane.showMessageDialog(this,
495                        Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.errorMessage",
496                                p.getName(),
497                                chooser.getSelectedFile().getPath(),
498                                ex.getLocalizedMessage()),
499                        Bundle.getMessage("ProfilePreferencesPanel.btnExportProfile.errorTitle"),
500                        JmriJOptionPane.ERROR_MESSAGE);
501            }
502        }
503    }//GEN-LAST:event_btnExportProfileActionPerformed
504
505    private void btnActivateProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnActivateProfileActionPerformed
506        try {
507            Profile p = ProfileManager.getDefault().getProfiles(profilesTbl.getSelectedRow());
508            ProfileManager.getDefault().setNextActiveProfile(p);
509            ProfileManager.getDefault().saveActiveProfile(p, ProfileManager.getDefault().isAutoStartActiveProfile());
510        } catch (IOException ex) {
511            log.error("Unable to save profile preferences", ex);
512            JmriJOptionPane.showMessageDialog(this, "Usable to save profile preferences.\n" + ex.getLocalizedMessage(), "Error", JmriJOptionPane.ERROR_MESSAGE);
513        }
514    }//GEN-LAST:event_btnActivateProfileActionPerformed
515
516    private void btnCreateNewProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnCreateNewProfileActionPerformed
517        AddProfileDialog apd = new AddProfileDialog((Frame) SwingUtilities.getWindowAncestor(this), true, true);
518        apd.setLocationRelativeTo(this);
519        apd.setVisible(true);
520    }//GEN-LAST:event_btnCreateNewProfileActionPerformed
521
522    private void btnDeleteProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnDeleteProfileActionPerformed
523        ArrayList<Profile> profiles = new ArrayList<>(this.profilesTbl.getSelectedRowCount());
524        for (int row : this.profilesTbl.getSelectedRows()) {
525            profiles.add(ProfileManager.getDefault().getAllProfiles().get(row));
526        }
527        for (Profile deletedProfile : profiles) {
528            int result = JmriJOptionPane.showOptionDialog(this,
529                    Bundle.getMessage("ProfilePreferencesPanel.btnDeleteProfile.dlgMessage", deletedProfile.getName()), // NOI18N
530                    Bundle.getMessage("ProfilePreferencesPanel.btnDeleteProfile.dlgTitle", deletedProfile.getName()), // NOI18N
531                    JmriJOptionPane.OK_CANCEL_OPTION,
532                    JmriJOptionPane.QUESTION_MESSAGE,
533                    null, // use default icon
534                    new String[]{
535                        Bundle.getMessage("ProfilePreferencesPanel.btnDeleteProfile.text"), // NOI18N
536                        Bundle.getMessage("AddProfileDialog.btnCancel.text") // NOI18N
537                    },
538                    JmriJOptionPane.CANCEL_OPTION
539            );
540            if (result == JmriJOptionPane.OK_OPTION) {
541                this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
542                if (!FileUtil.delete(deletedProfile.getPath())) {
543                    log.warn("Unable to delete profile directory {}", deletedProfile.getPath());
544                    this.setCursor(Cursor.getDefaultCursor());
545                    JmriJOptionPane.showMessageDialog(this,
546                            Bundle.getMessage("ProfilePreferencesPanel.btnDeleteProfile.errorMessage", deletedProfile.getPath()),
547                            Bundle.getMessage("ProfilePreferencesPanel.btnDeleteProfile.errorMessage"),
548                            JmriJOptionPane.ERROR_MESSAGE);
549                    this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
550                }
551                ProfileManager.getDefault().removeProfile(deletedProfile);
552                log.info("Removed profile \"{}\" from {}", deletedProfile.getName(), deletedProfile.getPath());
553                this.setCursor(Cursor.getDefaultCursor());
554                profilesTbl.repaint();
555            }
556        }
557    }//GEN-LAST:event_btnDeleteProfileActionPerformed
558
559    private void btnOpenExistingProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnOpenExistingProfileActionPerformed
560        JFileChooser chooser = new jmri.util.swing.JmriJFileChooser(FileUtil.getHomePath());
561        chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
562        chooser.setFileFilter(new ProfileFileFilter());
563        chooser.setFileView(new ProfileFileView());
564        // TODO: Use NetBeans OpenDialog if its availble
565        if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
566            try {
567                Profile p = new Profile(chooser.getSelectedFile());
568                ProfileManager.getDefault().addProfile(p);
569                int index = ProfileManager.getDefault().getAllProfiles().indexOf(p);
570                profilesTbl.setRowSelectionInterval(index, index);
571            } catch (IOException ex) {
572                log.warn("{} is not a profile directory", chooser.getSelectedFile());
573                JmriJOptionPane.showMessageDialog(this,
574                        Bundle.getMessage("ProfilePreferencesPanel.btnOpenExistingProfile.errorMessage", chooser.getSelectedFile().getPath()),
575                        Bundle.getMessage("ProfilePreferencesPanel.btnOpenExistingProfile.errorMessage"),
576                        JmriJOptionPane.ERROR_MESSAGE);
577            }
578        }
579    }//GEN-LAST:event_btnOpenExistingProfileActionPerformed
580
581    private void btnCopyProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_btnCopyProfileActionPerformed
582        AddProfileDialog apd = new AddProfileDialog((Frame) SwingUtilities.getWindowAncestor(this), true, true);
583        apd.setSourceProfile(ProfileManager.getDefault().getAllProfiles().get(profilesTbl.getSelectedRow()));
584        apd.setLocationRelativeTo(this);
585        apd.setVisible(true);
586    }//GEN-LAST:event_btnCopyProfileActionPerformed
587
588    private void spinnerTimeoutStateChanged(ChangeEvent evt) {//GEN-FIRST:event_spinnerTimeoutStateChanged
589        ProfileManager.getDefault().setAutoStartActiveProfileTimeout((Integer) this.spinnerTimeout.getValue());
590        try {
591            ProfileManager.getDefault().saveActiveProfile();
592        } catch (IOException ex) {
593            log.error("Unable to save active profile.", ex);
594        }
595    }//GEN-LAST:event_spinnerTimeoutStateChanged
596
597    private void rdoStartWithActiveProfileActionPerformed(ActionEvent evt) {//GEN-FIRST:event_rdoStartWithActiveProfileActionPerformed
598        this.setAutoStartActiveProfile(true);
599        this.spinnerTimeout.setEnabled(false);
600    }//GEN-LAST:event_rdoStartWithActiveProfileActionPerformed
601
602    private void rdoStartWithProfileSelectorActionPerformed(ActionEvent evt) {//GEN-FIRST:event_rdoStartWithProfileSelectorActionPerformed
603        this.setAutoStartActiveProfile(false);
604        this.spinnerTimeout.setEnabled(true);
605    }//GEN-LAST:event_rdoStartWithProfileSelectorActionPerformed
606
607    private void setAutoStartActiveProfile(boolean automatic) {
608        ProfileManager.getDefault().setAutoStartActiveProfile(automatic);
609        try {
610            ProfileManager.getDefault().saveActiveProfile();
611        } catch (IOException ex) {
612            log.error("Unable to save active profile.", ex);
613        }
614    }
615
616    // Variables declaration - do not modify//GEN-BEGIN:variables
617    private JButton btnActivateProfile;
618    private JButton btnAddSearchPath;
619    private JButton btnCopyProfile;
620    private JButton btnCreateNewProfile;
621    private JButton btnDeleteProfile;
622    private JButton btnExportProfile;
623    private JButton btnOpenExistingProfile;
624    private JButton btnRemoveSearchPath;
625    private JMenuItem copyMI;
626    private JMenuItem deleteMI;
627    private JPanel enabledPanel;
628    private ButtonGroup grpStartWithSelectors;
629    private JLabel jLabel1;
630    private JScrollPane jScrollPane1;
631    private JScrollPane jScrollPane3;
632    private JPopupMenu.Separator jSeparator1;
633    private JTabbedPane jTabbedPane1;
634    private JPopupMenu profilesPopupMenu;
635    private JTable profilesTbl;
636    private JRadioButton rdoStartWithActiveProfile;
637    private JRadioButton rdoStartWithProfileSelector;
638    private JMenuItem renameMI;
639    private JPanel searchPathsPanel;
640    private JTable searchPathsTbl;
641    private JSpinner spinnerTimeout;
642    // End of variables declaration//GEN-END:variables
643
644    @Override
645    public String getPreferencesItem() {
646        return "Profiles"; // NOI18N
647    }
648
649    @Override
650    public String getPreferencesItemText() {
651        return Bundle.getMessage("ProfilePreferencesPanel.enabledPanel.TabConstraints.tabTitle");
652    }
653
654    @Override
655    public String getTabbedPreferencesTitle() {
656        return null;
657    }
658
659    @Override
660    public String getLabelKey() {
661        return null;
662    }
663
664    @Override
665    public JComponent getPreferencesComponent() {
666        return this;
667    }
668
669    @Override
670    public boolean isPersistant() {
671        return false;
672    }
673
674    @Override
675    public String getPreferencesTooltip() {
676        return null;
677    }
678
679    @Override
680    public void savePreferences() {
681        // Nothing to do since ProfileManager preferences are saved immediately
682    }
683
684    @Override
685    public int getSortOrder() {
686        return 1000;
687    }
688
689    public void dispose() {
690        ProfileManager.getDefault().removePropertyChangeListener((PropertyChangeListener) profilesTbl.getModel());
691    }
692
693    private void profilesTblValueChanged(ListSelectionEvent e) {
694        if (profilesTbl.getSelectedRowCount() == 1 && profilesTbl.getSelectedRow() < ProfileManager.getDefault().getAllProfiles().size()) {
695            Profile p = ProfileManager.getDefault().getAllProfiles().get(profilesTbl.getSelectedRow());
696            this.btnDeleteProfile.setEnabled(!p.equals(ProfileManager.getDefault().getActiveProfile()));
697            if (ProfileManager.getDefault().getNextActiveProfile() != null) {
698                this.btnActivateProfile.setEnabled(!p.equals(ProfileManager.getDefault().getNextActiveProfile()));
699            } else {
700                this.btnActivateProfile.setEnabled(!p.equals(ProfileManager.getDefault().getActiveProfile()));
701            }
702            this.btnCopyProfile.setEnabled(true);
703            this.btnExportProfile.setEnabled(true);
704        } else if (this.profilesTbl.getSelectedRowCount() > 1) {
705            this.btnDeleteProfile.setEnabled(true);
706            for (int row : this.profilesTbl.getSelectedRows()) {
707                Profile p = ProfileManager.getDefault().getAllProfiles().get(row);
708                if (p.equals(ProfileManager.getDefault().getActiveProfile())) {
709                    this.btnDeleteProfile.setEnabled(false);
710                    break;
711                }
712            }
713            this.btnCopyProfile.setEnabled(false);
714            this.btnExportProfile.setEnabled(false);
715            this.btnActivateProfile.setEnabled(false);
716        } else {
717            this.btnDeleteProfile.setEnabled(false);
718            this.btnCopyProfile.setEnabled(false);
719            this.btnExportProfile.setEnabled(false);
720            this.btnActivateProfile.setEnabled(false);
721        }
722    }
723
724    private void searchPathsTblValueChanged(ListSelectionEvent e) {
725        if (this.searchPathsTbl.getSelectedRowCount() == 1 && this.searchPathsTbl.getSelectedRow() < ProfileManager.getDefault().getAllSearchPaths().size()) {
726            File sp = ProfileManager.getDefault().getSearchPaths(this.searchPathsTbl.getSelectedRow());
727            if (sp == null || sp.equals(new File(FileUtil.getPreferencesPath()))) {
728                this.btnRemoveSearchPath.setEnabled(false);
729            } else {
730                this.btnRemoveSearchPath.setEnabled(true);
731            }
732        } else if (this.searchPathsTbl.getSelectedRowCount() > 1) {
733            this.btnRemoveSearchPath.setEnabled(true);
734        } else {
735            this.btnRemoveSearchPath.setEnabled(false);
736        }
737    }
738
739    @Override
740    public boolean isDirty() {
741        return false; // ProfileManager preferences are saved immediately, so this is always false
742    }
743
744    /**
745     * {@inheritDoc}
746     * @return if the next profile to use has changed; false otherwise
747     */
748    @Override
749    public boolean isRestartRequired() {
750        // Since next profile defaults to null when application starts, restart
751        // is required only if next profile is not null and is not the same
752        // profile as the current profile
753        Profile ap = ProfileManager.getDefault().getActiveProfile();
754        Profile np = ProfileManager.getDefault().getNextActiveProfile();
755        return np != null && ap != null && !ap.equals(np);
756    }
757
758    @Override
759    public boolean isPreferencesValid() {
760        return true; // no validity checking performed
761    }
762
763    /* Comment out until I get around to utilizing this, so Jenkins does not throw warnings.
764     private static class ZipFileFilter extends FileFilter {
765
766     public ZipFileFilter() {
767     }
768
769     @Override
770     public boolean accept(File f) {
771     if (!f.isDirectory()) {
772     int i = f.getName().lastIndexOf('.');
773     if (i > 0 && i < f.getName().length() - 1) {
774     return f.getName().substring(i + 1).toLowerCase().equalsIgnoreCase("zip"); // NOI18N
775     }
776     return false;
777     }
778     return true;
779     }
780
781     @Override
782     public String getDescription() {
783     return "Zip archives (.zip)";
784     }
785     }
786     */
787    private class SearchPathSelectionListener implements ListSelectionListener {
788
789        @Override
790        public void valueChanged(ListSelectionEvent e) {
791            ProfilePreferencesPanel.this.searchPathsTblValueChanged(e);
792        }
793    }
794
795    private class ProfilesSelectionListener implements ListSelectionListener {
796
797        @Override
798        public void valueChanged(ListSelectionEvent e) {
799            ProfilePreferencesPanel.this.profilesTblValueChanged(e);
800        }
801    }
802
803    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProfilePreferencesPanel.class);
804
805}