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