001package jmri.jmrit.operations.locations.schedules;
002
003import java.awt.Color;
004import java.awt.event.ActionEvent;
005import java.beans.PropertyChangeEvent;
006import java.beans.PropertyChangeListener;
007import java.util.Hashtable;
008import java.util.List;
009
010import javax.swing.*;
011import javax.swing.table.TableCellEditor;
012import javax.swing.table.TableColumnModel;
013
014import jmri.InstanceManager;
015import jmri.jmrit.operations.OperationsTableModel;
016import jmri.jmrit.operations.OperationsXml;
017import jmri.jmrit.operations.locations.LocationManager;
018import jmri.jmrit.operations.locations.Track;
019import jmri.jmrit.operations.setup.Control;
020import jmri.util.swing.JmriJOptionPane;
021import jmri.util.table.ButtonEditor;
022import jmri.util.table.ButtonRenderer;
023
024/**
025 * Table Model for edit of schedules used by operations
026 *
027 * @author Daniel Boudreau Copyright (C) 2009, 2011, 2013
028 */
029public class SchedulesTableModel extends OperationsTableModel implements PropertyChangeListener {
030
031    ScheduleManager scheduleManager; // There is only one manager
032
033    // Defines the columns
034    static final int ID_COLUMN = 0;
035    static final int NAME_COLUMN = ID_COLUMN + 1;
036    static final int SCHEDULE_STATUS_COLUMN = NAME_COLUMN + 1;
037    static final int SPUR_NUMBER_COLUMN = SCHEDULE_STATUS_COLUMN + 1;
038    static final int SPUR_COLUMN = SPUR_NUMBER_COLUMN + 1;
039    static final int STATUS_COLUMN = SPUR_COLUMN + 1;
040    static final int MODE_COLUMN = STATUS_COLUMN + 1;
041    static final int EDIT_COLUMN = MODE_COLUMN + 1;
042    static final int DELETE_COLUMN = EDIT_COLUMN + 1;
043
044    private static final int HIGHEST_COLUMN = DELETE_COLUMN + 1;
045
046    public SchedulesTableModel() {
047        super();
048        scheduleManager = InstanceManager.getDefault(ScheduleManager.class);
049        scheduleManager.addPropertyChangeListener(this);
050        updateList();
051    }
052
053    public final int SORTBYNAME = 1;
054    public final int SORTBYID = 2;
055
056    private int _sort = SORTBYNAME;
057
058    public void setSort(int sort) {
059        _sort = sort;
060        updateList();
061        fireTableDataChanged();
062    }
063
064    private void updateList() {
065        // first, remove listeners from the individual objects
066        removePropertyChangeSchedules();
067        removePropertyChangeTracks();
068
069        if (_sort == SORTBYID) {
070            sysList = scheduleManager.getSchedulesByIdList();
071        } else {
072            sysList = scheduleManager.getSchedulesByNameList();
073        }
074        // and add them back in
075        for (Schedule sch : sysList) {
076            sch.addPropertyChangeListener(this);
077        }
078        addPropertyChangeTracks();
079    }
080
081    List<Schedule> sysList = null;
082
083    public void initTable(SchedulesTableFrame frame, JTable table) {
084        super.initTable(table);
085        
086        // Install the button handlers
087        TableColumnModel tcm = table.getColumnModel();
088        ButtonRenderer buttonRenderer = new ButtonRenderer();
089        TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton());
090        tcm.getColumn(EDIT_COLUMN).setCellRenderer(buttonRenderer);
091        tcm.getColumn(EDIT_COLUMN).setCellEditor(buttonEditor);
092        tcm.getColumn(DELETE_COLUMN).setCellRenderer(buttonRenderer);
093        tcm.getColumn(DELETE_COLUMN).setCellEditor(buttonEditor);
094
095        // set column preferred widths
096        table.getColumnModel().getColumn(ID_COLUMN).setPreferredWidth(40);
097        table.getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(200);
098        table.getColumnModel().getColumn(SCHEDULE_STATUS_COLUMN).setPreferredWidth(80);
099        table.getColumnModel().getColumn(SPUR_NUMBER_COLUMN).setPreferredWidth(40);
100        table.getColumnModel().getColumn(SPUR_COLUMN).setPreferredWidth(350);
101        table.getColumnModel().getColumn(STATUS_COLUMN).setPreferredWidth(150);
102        table.getColumnModel().getColumn(MODE_COLUMN).setPreferredWidth(70);
103        table.getColumnModel().getColumn(EDIT_COLUMN).setPreferredWidth(70);
104        table.getColumnModel().getColumn(DELETE_COLUMN).setPreferredWidth(90);
105
106        frame.loadTableDetails(table);
107    }
108
109    @Override
110    public int getRowCount() {
111        return sysList.size();
112    }
113
114    @Override
115    public int getColumnCount() {
116        return HIGHEST_COLUMN;
117    }
118
119    @Override
120    public String getColumnName(int col) {
121        switch (col) {
122            case ID_COLUMN:
123                return Bundle.getMessage("Id");
124            case NAME_COLUMN:
125                return Bundle.getMessage("Name");
126            case SCHEDULE_STATUS_COLUMN:
127                return Bundle.getMessage("Status");
128            case SPUR_NUMBER_COLUMN:
129                return Bundle.getMessage("Number");
130            case SPUR_COLUMN:
131                return Bundle.getMessage("Spurs");
132            case STATUS_COLUMN:
133                return Bundle.getMessage("StatusSpur");
134            case MODE_COLUMN:
135                return Bundle.getMessage("ScheduleMode");
136            case EDIT_COLUMN:
137                return Bundle.getMessage("ButtonEdit");
138            case DELETE_COLUMN:
139                return Bundle.getMessage("ButtonDelete"); // titles above all
140                                                          // columns
141            default:
142                return "unknown"; // NOI18N
143        }
144    }
145
146    @Override
147    public Class<?> getColumnClass(int col) {
148        switch (col) {
149            case ID_COLUMN:
150            case NAME_COLUMN:
151            case SCHEDULE_STATUS_COLUMN:
152            case STATUS_COLUMN:
153            case MODE_COLUMN:
154                return String.class;
155            case SPUR_COLUMN:
156                return JComboBox.class;
157            case SPUR_NUMBER_COLUMN:
158                return Integer.class;
159            case EDIT_COLUMN:
160            case DELETE_COLUMN:
161                return JButton.class;
162            default:
163                return null;
164        }
165    }
166
167    @Override
168    public boolean isCellEditable(int row, int col) {
169        switch (col) {
170            case EDIT_COLUMN:
171            case DELETE_COLUMN:
172            case SPUR_COLUMN:
173                return true;
174            default:
175                return false;
176        }
177    }
178
179    @Override
180    public Object getValueAt(int row, int col) {
181        if (row >= getRowCount()) {
182            return "ERROR row " + row; // NOI18N
183        }
184        Schedule schedule = sysList.get(row);
185        if (schedule == null) {
186            return "ERROR schedule unknown " + row; // NOI18N
187        }
188        switch (col) {
189            case ID_COLUMN:
190                return schedule.getId();
191            case NAME_COLUMN:
192                return schedule.getName();
193            case SCHEDULE_STATUS_COLUMN:
194                return getScheduleStatus(row);
195            case SPUR_NUMBER_COLUMN:
196                return scheduleManager.getSpursByScheduleComboBox(schedule).getItemCount();
197            case SPUR_COLUMN: {
198                return getComboBox(row, schedule);
199            }
200            case STATUS_COLUMN:
201                return getSpurStatus(row);
202            case MODE_COLUMN:
203                return getSpurMode(row);
204            case EDIT_COLUMN:
205                return Bundle.getMessage("ButtonEdit");
206            case DELETE_COLUMN:
207                return Bundle.getMessage("ButtonDelete");
208            default:
209                return "unknown " + col; // NOI18N
210        }
211    }
212
213    @Override
214    public void setValueAt(Object value, int row, int col) {
215        switch (col) {
216            case EDIT_COLUMN:
217                editSchedule(row);
218                break;
219            case DELETE_COLUMN:
220                deleteSchedule(row);
221                break;
222            case SPUR_COLUMN:
223                selectJComboBox(value, row);
224                break;
225            default:
226                break;
227        }
228    }
229    
230    @Override
231    protected Color getForegroundColor(int row) {
232        if (!getScheduleStatus(row).equals(Bundle.getMessage("ButtonOK"))) {
233            return Color.red;
234        }
235        return super.getForegroundColor(row);
236    }
237
238    ScheduleEditFrame sef = null;
239
240    private void editSchedule(int row) {
241        log.debug("Edit schedule");
242        if (sef != null) {
243            sef.dispose();
244        }
245        Schedule sch = sysList.get(row);
246        LocationTrackPair ltp = getLocationTrackPair(row);
247        if (ltp == null) {
248            log.debug("Need location track pair");
249            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("AssignSchedule", sch.getName()),
250                    Bundle.getMessage("CanNotSchedule", Bundle.getMessage("ButtonEdit")),
251                    JmriJOptionPane.ERROR_MESSAGE);
252            return;
253        }
254        // use invokeLater so new window appears on top
255        SwingUtilities.invokeLater(() -> {
256            sef = new ScheduleEditFrame(sch, ltp.getTrack());
257        });
258    }
259
260    private void deleteSchedule(int row) {
261        log.debug("Delete schedule");
262        Schedule sch = sysList.get(row);
263        if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("DoYouWantToDeleteSchedule", sch.getName()),
264                Bundle.getMessage("DeleteSchedule?"), JmriJOptionPane.YES_NO_OPTION) == JmriJOptionPane.YES_OPTION) {
265            scheduleManager.deregister(sch);
266            OperationsXml.save();
267        }
268    }
269
270    protected Hashtable<Schedule, String> comboSelect = new Hashtable<Schedule, String>();
271
272    private void selectJComboBox(Object value, int row) {
273        Schedule schedule = sysList.get(row);
274        JComboBox<?> box = (JComboBox<?>) value;
275        if (box.getSelectedIndex() >= 0) {
276            comboSelect.put(schedule, Integer.toString(box.getSelectedIndex()));
277        }
278        fireTableRowsUpdated(row, row);
279    }
280
281    private LocationTrackPair getLocationTrackPair(int row) {
282        Schedule s = sysList.get(row);
283        JComboBox<LocationTrackPair> box = scheduleManager.getSpursByScheduleComboBox(s);
284        String index = comboSelect.get(sysList.get(row));
285        LocationTrackPair ltp;
286        if (index != null) {
287            ltp = box.getItemAt(Integer.parseInt(index));
288        } else {
289            ltp = box.getItemAt(0);
290        }
291        return ltp;
292    }
293
294    private String getScheduleStatus(int row) {
295        Schedule sch = sysList.get(row);
296        JComboBox<?> box = scheduleManager.getSpursByScheduleComboBox(sch);
297        for (int i = 0; i < box.getItemCount(); i++) {
298            LocationTrackPair ltp = (LocationTrackPair) box.getItemAt(i);
299            String status = ltp.getTrack().checkScheduleValid();
300            if (!status.equals(Schedule.SCHEDULE_OKAY)) {
301                return Bundle.getMessage("ErrorTitle");
302            }
303        }
304        return Bundle.getMessage("ButtonOK");
305    }
306
307    private JComboBox<LocationTrackPair> getComboBox(int row, Schedule schedule) {
308        JComboBox<LocationTrackPair> box = scheduleManager.getSpursByScheduleComboBox(schedule);
309        String index = comboSelect.get(sysList.get(row));
310        if (index != null && box.getItemCount() > Integer.parseInt(index)) {
311            box.setSelectedIndex(Integer.parseInt(index));
312        }
313        box.addActionListener((ActionEvent e) -> {
314            comboBoxActionPerformed(e);
315        });
316        return box;
317    }
318
319    protected void comboBoxActionPerformed(ActionEvent ae) {
320        log.debug("combobox action");
321        if (_table.isEditing()) {
322            _table.getCellEditor().stopCellEditing(); // Allows the table
323                                                     // contents to update
324        }
325    }
326
327    private String getSpurStatus(int row) {
328        LocationTrackPair ltp = getLocationTrackPair(row);
329        if (ltp == null) {
330            return "";
331        }
332        String status = ltp.getTrack().checkScheduleValid();
333        if (!status.equals(Schedule.SCHEDULE_OKAY)) {
334            return status;
335        }
336        return Bundle.getMessage("ButtonOK");
337    }
338
339    private String getSpurMode(int row) {
340        LocationTrackPair ltp = getLocationTrackPair(row);
341        if (ltp == null) {
342            return "";
343        }
344        return ltp.getTrack().getScheduleModeName();
345    }
346
347    private void removePropertyChangeSchedules() {
348        if (sysList != null) {
349            for (Schedule sch : sysList) {
350                sch.removePropertyChangeListener(this);
351            }
352        }
353    }
354
355    private void addPropertyChangeTracks() {
356        // only spurs have schedules
357        for (Track track : InstanceManager.getDefault(LocationManager.class).getTracks(Track.SPUR)) {
358            track.addPropertyChangeListener(this);
359        }
360    }
361
362    private void removePropertyChangeTracks() {
363        for (Track track : InstanceManager.getDefault(LocationManager.class).getTracks(Track.SPUR)) {
364            track.removePropertyChangeListener(this);
365        }
366    }
367
368    public void dispose() {
369        if (sef != null) {
370            sef.dispose();
371        }
372        scheduleManager.removePropertyChangeListener(this);
373        removePropertyChangeSchedules();
374        removePropertyChangeTracks();
375    }
376
377    // check for change in number of schedules, or a change in a schedule
378    @Override
379    public void propertyChange(PropertyChangeEvent e) {
380        if (Control.SHOW_PROPERTY) {
381            log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e
382                    .getNewValue());
383        }
384        if (e.getPropertyName().equals(ScheduleManager.LISTLENGTH_CHANGED_PROPERTY)) {
385            updateList();
386            fireTableDataChanged();
387        } else if (e.getSource().getClass().equals(Schedule.class)) {
388            Schedule schedule = (Schedule) e.getSource();
389            int row = sysList.indexOf(schedule);
390            if (Control.SHOW_PROPERTY) {
391                log.debug("Update schedule table row: {} name: {}", row, schedule.getName());
392            }
393            if (row >= 0) {
394                fireTableRowsUpdated(row, row);
395            }
396        }
397        if (e.getSource().getClass().equals(Track.class)) {
398            Track track = (Track) e.getSource();
399            Schedule schedule = track.getSchedule();
400            int row = sysList.indexOf(schedule);
401            if (row >= 0) {
402                fireTableRowsUpdated(row, row);
403            } else {
404                fireTableDataChanged();
405            }
406        }
407
408        if (e.getPropertyName().equals(Track.SCHEDULE_ID_CHANGED_PROPERTY)) {
409            fireTableDataChanged();
410        }
411    }
412
413    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SchedulesTableModel.class);
414}