001package apps.startup; 002 003import jmri.util.startup.StartupModel; 004 005import apps.ConfigBundle; 006 007import jmri.util.startup.StartupActionsManager; 008 009import java.awt.Component; 010import java.awt.event.ActionEvent; 011import java.awt.event.ActionListener; 012import java.awt.event.MouseEvent; 013import java.beans.IndexedPropertyChangeEvent; 014import java.beans.PropertyChangeEvent; 015import java.beans.PropertyChangeListener; 016import java.util.ArrayList; 017import java.util.ResourceBundle; 018 019import javax.swing.*; 020import javax.swing.event.ListSelectionEvent; 021import javax.swing.table.AbstractTableModel; 022import javax.swing.table.TableColumn; 023 024import jmri.InstanceManager; 025import jmri.profile.Profile; 026import jmri.profile.ProfileManager; 027import jmri.swing.PreferencesPanel; 028 029import org.openide.util.lookup.ServiceProvider; 030 031/** 032 * Preferences panel to configure optional actions taken at startup. 033 * <p> 034 * This panel will only display (and save) preferences where 035 * {@link StartupModel#isValid()} is true. 036 * 037 * @author Randall Wood Copyright 2016, 2020 038 */ 039@ServiceProvider(service = PreferencesPanel.class) 040public class StartupActionsPreferencesPanel extends JPanel implements PreferencesPanel { 041 042 private static final int COLUMN_NAME = 0; 043 private static final int COLUMN_TYPE = 1; 044 private static final int COLUMN_ENABLED = 2; 045 046 /** 047 * Creates new form StartupActionsPreferencesPanel 048 */ 049 public StartupActionsPreferencesPanel() { 050 initComponents(); 051 052 JButton testButton = new JButton("XXXXXX"); // NOI18N 053 TableColumn enableColumn = this.actionsTbl.getColumnModel().getColumn(COLUMN_ENABLED); 054 enableColumn.setMinWidth(testButton.getPreferredSize().width); 055 enableColumn.setMaxWidth(testButton.getPreferredSize().width); 056 057 this.actionsTbl.getSelectionModel().addListSelectionListener((ListSelectionEvent e) -> { 058 int row = this.actionsTbl.getSelectedRow(); 059 this.upBtn.setEnabled(row != 0 && row != -1); 060 this.downBtn.setEnabled(row != this.actionsTbl.getRowCount() - 1 && row != -1); 061 this.removeBtn.setEnabled(row != -1); 062 }); 063 ArrayList<JMenuItem> items = new ArrayList<>(); 064 InstanceManager.getDefault(StartupActionsManager.class).getFactories().values().stream().forEach((factory) -> { 065 JMenuItem item = new JMenuItem(factory.getActionText()); 066 item.addActionListener((ActionEvent e) -> { 067 StartupModel model = factory.newModel(); 068 factory.editModel(model, this.getTopLevelAncestor()); 069 if (model.isValid()) { 070 InstanceManager.getDefault(StartupActionsManager.class).addAction(model); 071 } 072 }); 073 items.add(item); 074 }); 075 items.sort((JMenuItem o1, JMenuItem o2) -> o1.getText().compareTo(o2.getText())); 076 items.stream().forEach((item) -> { 077 this.actionsMenu.add(item); 078 }); 079 } 080 081 /** 082 * This method is called from within the constructor to performAction the 083 * form. WARNING: Do NOT modify this code. The content of this method is 084 * always regenerated by the Form Editor. 085 */ 086 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents 087 private void initComponents() { 088 089 actionsMenu = new JPopupMenu(); 090 jScrollPane1 = new JScrollPane(); 091 actionsTbl = new JTable() { 092 093 //Implement table cell tool tips. 094 @Override 095 public String getToolTipText(MouseEvent e) { 096 try { 097 return getValueAt(rowAtPoint(e.getPoint()), -1).toString(); 098 } catch (RuntimeException e1) { 099 //catch null pointer exception if mouse is over an empty line 100 } 101 return null; 102 } 103 }; 104 addBtn = new JButton(); 105 removeBtn = new JButton(); 106 startupLbl = new JLabel(); 107 upBtn = new JButton(); 108 downBtn = new JButton(); 109 moveLbl = new JLabel(); 110 recommendationsLbl = new JLabel(); 111 112 actionsTbl.setDefaultRenderer(StartupModel.class, new StartupModelCellRenderer()); 113 actionsTbl.setDefaultEditor(StartupModel.class, new StartupModelCellEditor()); 114 actionsTbl.setModel(new TableModel(InstanceManager.getDefault(StartupActionsManager.class))); 115 actionsTbl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 116 actionsTbl.getTableHeader().setReorderingAllowed(false); 117 jScrollPane1.setViewportView(actionsTbl); 118 actionsTbl.getColumnModel().getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 119 120 ResourceBundle bundle = ResourceBundle.getBundle("apps/startup/Bundle"); // NOI18N 121 addBtn.setText(bundle.getString("StartupActionsPreferencesPanel.addBtn.text")); // NOI18N 122 addBtn.addActionListener(new ActionListener() { 123 @Override 124 public void actionPerformed(ActionEvent evt) { 125 addBtnActionPerformed(evt); 126 } 127 }); 128 129 removeBtn.setText(bundle.getString("StartupActionsPreferencesPanel.removeBtn.text")); // NOI18N 130 removeBtn.setEnabled(false); 131 removeBtn.addActionListener(new ActionListener() { 132 @Override 133 public void actionPerformed(ActionEvent evt) { 134 removeBtnActionPerformed(evt); 135 } 136 }); 137 138 startupLbl.setText(bundle.getString("StartupActionsPreferencesPanel.startupLbl.text")); // NOI18N 139 140 upBtn.setText(bundle.getString("StartupActionsPreferencesPanel.upBtn.text")); // NOI18N 141 upBtn.setEnabled(false); 142 upBtn.addActionListener(new ActionListener() { 143 @Override 144 public void actionPerformed(ActionEvent evt) { 145 upBtnActionPerformed(evt); 146 } 147 }); 148 149 downBtn.setText(bundle.getString("StartupActionsPreferencesPanel.downBtn.text")); // NOI18N 150 downBtn.setEnabled(false); 151 downBtn.addActionListener(new ActionListener() { 152 @Override 153 public void actionPerformed(ActionEvent evt) { 154 downBtnActionPerformed(evt); 155 } 156 }); 157 158 moveLbl.setText(bundle.getString("StartupActionsPreferencesPanel.moveLbl.text")); // NOI18N 159 160 recommendationsLbl.setText(bundle.getString("StartupActionsPreferencesPanel.recommendationsLbl.text")); // NOI18N 161 162 GroupLayout layout = new GroupLayout(this); 163 this.setLayout(layout); 164 layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) 165 .addGroup(layout.createSequentialGroup() 166 .addContainerGap() 167 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) 168 .addComponent(recommendationsLbl) 169 .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 487, Short.MAX_VALUE) 170 .addGroup(layout.createSequentialGroup() 171 .addComponent(addBtn) 172 .addGap(18, 18, 18) 173 .addComponent(moveLbl) 174 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 175 .addComponent(upBtn) 176 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 177 .addComponent(downBtn) 178 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) 179 .addComponent(removeBtn)) 180 .addComponent(startupLbl)) 181 .addContainerGap()) 182 ); 183 layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) 184 .addGroup(layout.createSequentialGroup() 185 .addContainerGap() 186 .addComponent(startupLbl, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 187 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 188 .addComponent(jScrollPane1, GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE) 189 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 190 .addComponent(recommendationsLbl, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE) 191 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) 192 .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) 193 .addComponent(addBtn) 194 .addComponent(removeBtn) 195 .addComponent(upBtn) 196 .addComponent(downBtn) 197 .addComponent(moveLbl)) 198 .addContainerGap()) 199 ); 200 }// </editor-fold>//GEN-END:initComponents 201 202 private void addBtnActionPerformed(ActionEvent evt) {//GEN-FIRST:event_addBtnActionPerformed 203 Component c = (Component) evt.getSource(); 204 this.actionsMenu.show(c, 0 - 1, c.getHeight()); 205 }//GEN-LAST:event_addBtnActionPerformed 206 207 private void removeBtnActionPerformed(ActionEvent evt) {//GEN-FIRST:event_removeBtnActionPerformed 208 int row = this.actionsTbl.getSelectedRow(); 209 if (row != -1) { 210 StartupModel model = InstanceManager.getDefault(StartupActionsManager.class).getActions(row); 211 InstanceManager.getDefault(StartupActionsManager.class).removeAction(model); 212 } 213 }//GEN-LAST:event_removeBtnActionPerformed 214 215 private void upBtnActionPerformed(ActionEvent evt) {//GEN-FIRST:event_upBtnActionPerformed 216 int row = this.actionsTbl.getSelectedRow(); 217 if (row != 0) { 218 InstanceManager.getDefault(StartupActionsManager.class).moveAction(row, row - 1); 219 this.actionsTbl.setRowSelectionInterval(row - 1, row - 1); 220 } 221 }//GEN-LAST:event_upBtnActionPerformed 222 223 private void downBtnActionPerformed(ActionEvent evt) {//GEN-FIRST:event_downBtnActionPerformed 224 int row = this.actionsTbl.getSelectedRow(); 225 if (row != this.actionsTbl.getRowCount() - 1) { 226 InstanceManager.getDefault(StartupActionsManager.class).moveAction(row, row + 1); 227 this.actionsTbl.setRowSelectionInterval(row + 1, row + 1); 228 } 229 }//GEN-LAST:event_downBtnActionPerformed 230 231 @Override 232 public String getPreferencesItem() { 233 return "STARTUP"; // NOI18N 234 } 235 236 @Override 237 public String getPreferencesItemText() { 238 return ConfigBundle.getMessage("MenuStartUp"); // NOI18N 239 } 240 241 @Override 242 public String getTabbedPreferencesTitle() { 243 return null; 244 } 245 246 @Override 247 public String getLabelKey() { 248 return null; 249 } 250 251 @Override 252 public JComponent getPreferencesComponent() { 253 return this; 254 } 255 256 @Override 257 public boolean isPersistant() { 258 return true; 259 } 260 261 @Override 262 public String getPreferencesTooltip() { 263 return null; 264 } 265 266 @Override 267 public void savePreferences() { 268 Profile profile = ProfileManager.getDefault().getActiveProfile(); 269 InstanceManager.getDefault(StartupActionsManager.class).savePreferences(profile); 270 } 271 272 @Override 273 public boolean isDirty() { 274 return InstanceManager.getDefault(StartupActionsManager.class).isDirty(); 275 } 276 277 @Override 278 public boolean isRestartRequired() { 279 return InstanceManager.getDefault(StartupActionsManager.class).isRestartRequired(); 280 } 281 282 @Override 283 public boolean isPreferencesValid() { 284 // To really test would require that the models know their valid state 285 // they don't, and it can change externally, so we don't really check. 286 return true; 287 } 288 289 290 // Variables declaration - do not modify//GEN-BEGIN:variables 291 JPopupMenu actionsMenu; 292 JTable actionsTbl; 293 JButton addBtn; 294 JButton downBtn; 295 JScrollPane jScrollPane1; 296 JLabel moveLbl; 297 JLabel recommendationsLbl; 298 JButton removeBtn; 299 JLabel startupLbl; 300 JButton upBtn; 301 // End of variables declaration//GEN-END:variables 302 303 private static class TableModel extends AbstractTableModel implements PropertyChangeListener { 304 305 private final StartupActionsManager manager; 306 307// @SuppressWarnings("LeakingThisInConstructor") 308 public TableModel(StartupActionsManager manager) { 309 this.manager = manager; 310 this.manager.addPropertyChangeListener(this); 311 } 312 313 @Override 314 public int getRowCount() { 315 return this.manager.getActions().length; 316 } 317 318 @Override 319 public int getColumnCount() { 320 return 3; 321 } 322 323 @Override 324 public Object getValueAt(int rowIndex, int columnIndex) { 325 StartupModel model = this.manager.getActions(rowIndex); 326 switch (columnIndex) { 327 case -1: // tooltip 328 return model.toString(); 329 case COLUMN_NAME: 330 return model; 331 case COLUMN_TYPE: 332 return this.manager.getFactories(model.getClass()).getDescription(); 333 case COLUMN_ENABLED: 334 return model.isEnabled(); 335 default: 336 return null; 337 } 338 339 } 340 341 @Override 342 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 343 switch (columnIndex) { 344 case COLUMN_ENABLED: 345 if (aValue instanceof Boolean) { 346 StartupModel model = this.manager.getActions(rowIndex); 347 model.setEnabled((boolean)aValue); 348 this.manager.setRestartRequired(); 349 } 350 break; 351 default: 352 // Do nothing 353 } 354 } 355 356 @Override 357 public String getColumnName(int columnIndex) { 358 switch (columnIndex) { 359 case COLUMN_NAME: 360 return Bundle.getMessage("StartupActionsTableModel.name"); // NOI18N 361 case COLUMN_TYPE: 362 return Bundle.getMessage("StartupActionsTableModel.type"); // NOI18N 363 case COLUMN_ENABLED: 364 return Bundle.getMessage("StartupActionsTableModel.enabled"); // NOI18N 365 default: 366 return null; 367 } 368 } 369 370 @Override 371 public Class<?> getColumnClass(int columnIndex) { 372 switch (columnIndex) { 373 case COLUMN_NAME: 374 return StartupModel.class; 375 case COLUMN_TYPE: 376 return String.class; 377 case COLUMN_ENABLED: 378 return Boolean.class; 379 default: 380 return null; 381 } 382 } 383 384 @Override 385 public boolean isCellEditable(int rowIndex, int columnIndex) { 386 return columnIndex != COLUMN_TYPE; 387 } 388 389 @Override 390 public void propertyChange(PropertyChangeEvent evt) { 391 int index = -1; 392 if (evt instanceof IndexedPropertyChangeEvent) { 393 index = ((IndexedPropertyChangeEvent) evt).getIndex(); 394 } 395 if (index != -1 && evt.getOldValue() instanceof Integer) { 396 this.fireTableRowsUpdated((Integer) evt.getOldValue(), index); 397 } else { 398 this.fireTableDataChanged(); 399 } 400 } 401 } 402}