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