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