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