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