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}