001package jmri.jmrit.operations;
002
003import java.awt.Container;
004import java.awt.Dimension;
005import java.awt.FontMetrics;
006import java.awt.GridBagConstraints;
007import java.awt.event.ActionEvent;
008import java.util.Optional;
009
010import javax.swing.*;
011import javax.swing.event.ChangeEvent;
012import javax.swing.table.TableModel;
013import javax.swing.table.TableRowSorter;
014
015import org.slf4j.Logger;
016import org.slf4j.LoggerFactory;
017
018import jmri.InstanceManager;
019import jmri.jmrit.operations.rollingstock.cars.CarTypes;
020import jmri.jmrit.operations.setup.Control;
021import jmri.swing.JTablePersistenceManager;
022import jmri.util.JmriJFrame;
023
024/**
025 * Panel for operations
026 *
027 * @author Dan Boudreau Copyright (C) 2008, 2012
028 */
029public class OperationsPanel extends JPanel {
030
031    public static final String NEW_LINE = "\n"; // NOI18N
032    public static final String NONE = ""; // NOI18N
033
034    public OperationsPanel() {
035        super();
036    }
037
038    public void initMinimumSize() {
039        initMinimumSize(new Dimension(Control.panelWidth500, Control.panelHeight250));
040    }
041
042    public void initMinimumSize(Dimension dimension) {
043        setMinimumSize(dimension);
044    }
045
046    public void dispose() {
047        // The default method does nothing.
048    }
049
050    protected void addItem(JComponent c, int x, int y) {
051        GridBagConstraints gc = new GridBagConstraints();
052        gc.gridx = x;
053        gc.gridy = y;
054        gc.weightx = 100.0;
055        gc.weighty = 100.0;
056        this.add(c, gc);
057    }
058
059    protected void addItem(JPanel p, JComponent c, int x, int y) {
060        GridBagConstraints gc = new GridBagConstraints();
061        gc.gridx = x;
062        gc.gridy = y;
063        gc.weightx = 100.0;
064        gc.weighty = 100.0;
065        p.add(c, gc);
066    }
067
068    protected void addItemLeft(JPanel p, JComponent c, int x, int y) {
069        GridBagConstraints gc = new GridBagConstraints();
070        gc.gridx = x;
071        gc.gridy = y;
072        gc.weightx = 100.0;
073        gc.weighty = 100.0;
074        gc.anchor = GridBagConstraints.WEST;
075        p.add(c, gc);
076    }
077
078    protected void addItemTop(JPanel p, JComponent c, int x, int y) {
079        GridBagConstraints gc = new GridBagConstraints();
080        gc.gridx = x;
081        gc.gridy = y;
082        gc.weightx = 100;
083        gc.weighty = 100;
084        gc.anchor = GridBagConstraints.NORTH;
085        p.add(c, gc);
086    }
087
088    protected void addItemWidth(JPanel p, JComponent c, int width, int x, int y) {
089        GridBagConstraints gc = new GridBagConstraints();
090        gc.gridx = x;
091        gc.gridy = y;
092        gc.gridwidth = width;
093        gc.weightx = 100.0;
094        gc.weighty = 100.0;
095        gc.anchor = GridBagConstraints.WEST;
096        p.add(c, gc);
097    }
098
099    private static final int MIN_CHECKBOXES = 5;
100    private static final int MAX_CHECKBOXES = 11;
101
102    /**
103     * Gets the number of checkboxes(+1) that can fix in one row see
104     * OperationsFrame.minCheckboxes and OperationsFrame.maxCheckboxes
105     *
106     * @return the number of checkboxes, minimum is 5 (6 checkboxes)
107     */
108    protected int getNumberOfCheckboxesPerLine() {
109        return getNumberOfCheckboxesPerLine(this.getPreferredSize());
110    }
111
112    protected int getNumberOfCheckboxesPerLine(Dimension size) {
113        if (size == null) {
114            return MIN_CHECKBOXES; // default is 6 checkboxes per row
115        }
116        StringBuilder padding = new StringBuilder("X");
117        for (int i = 0; i < InstanceManager.getDefault(CarTypes.class).getMaxFullNameLength(); i++) {
118            padding.append("X");
119        }
120
121        JCheckBox box = new JCheckBox(padding.toString());
122        int number = size.width / (box.getPreferredSize().width);
123        if (number < MIN_CHECKBOXES) {
124            number = MIN_CHECKBOXES;
125        }
126        if (number > MAX_CHECKBOXES) {
127            number = MAX_CHECKBOXES;
128        }
129        return number;
130    }
131
132    protected void addButtonAction(JButton b) {
133        b.addActionListener((ActionEvent e) -> {
134            buttonActionPerformed(e);
135        });
136    }
137
138    protected void buttonActionPerformed(ActionEvent ae) {
139        log.debug("button action not overridden");
140    }
141
142    protected void addRadioButtonAction(JRadioButton b) {
143        b.addActionListener((ActionEvent e) -> {
144            radioButtonActionPerformed(e);
145        });
146    }
147
148    protected void radioButtonActionPerformed(ActionEvent ae) {
149        log.debug("radio button action not overridden");
150    }
151
152    protected void addCheckBoxAction(JCheckBox b) {
153        b.addActionListener((ActionEvent e) -> {
154            checkBoxActionPerformed(e);
155        });
156    }
157
158    protected void checkBoxActionPerformed(ActionEvent ae) {
159        log.debug("check box action not overridden");
160    }
161
162    protected void addSpinnerChangeListerner(JSpinner s) {
163        s.addChangeListener((ChangeEvent e) -> {
164            spinnerChangeEvent(e);
165        });
166    }
167
168    protected void spinnerChangeEvent(javax.swing.event.ChangeEvent ae) {
169        log.debug("spinner action not overridden");
170    }
171
172    protected void addComboBoxAction(JComboBox<?> b) {
173        b.addActionListener((ActionEvent e) -> {
174            comboBoxActionPerformed(e);
175        });
176    }
177
178    protected void comboBoxActionPerformed(ActionEvent ae) {
179        log.debug("combobox action not overridden");
180    }
181
182    protected void selectNextItemComboBox(JComboBox<?> b) {
183        int newIndex = b.getSelectedIndex() + 1;
184        if (newIndex < b.getItemCount()) {
185            b.setSelectedIndex(newIndex);
186        }
187    }
188
189    /**
190     * Will modify the character column width of a TextArea box to 90% of a
191     * panels width. ScrollPane is set to 95% of panel width.
192     *
193     * @param scrollPane the pane containing the textArea
194     * @param textArea   the textArea to adjust
195     */
196    protected void adjustTextAreaColumnWidth(JScrollPane scrollPane, JTextArea textArea) {
197        this.adjustTextAreaColumnWidth(scrollPane, textArea, this.getPreferredSize());
198    }
199
200    protected void adjustTextAreaColumnWidth(JScrollPane scrollPane, JTextArea textArea, Dimension size) {
201        FontMetrics metrics = getFontMetrics(textArea.getFont());
202        int columnWidth = metrics.charWidth('m');
203        int width = size.width;
204        int columns = width / columnWidth * 90 / 100; // make text area 90% of the panel width
205        if (columns > textArea.getColumns()) {
206            log.debug("Increasing text area character width to {} columns", columns);
207            textArea.setColumns(columns);
208        }
209        scrollPane.setMinimumSize(new Dimension(width * 95 / 100, 60));
210    }
211
212    /**
213     * Load the table width, position, and sorting status from the user
214     * preferences file.
215     *
216     * @param table The table to be adjusted.
217     */
218    public void loadTableDetails(JTable table) {
219        loadTableDetails(table, getWindowFrameRef());
220        persist(table);
221    }
222    
223    public static void loadTableDetails(JTable table, String name) {
224        if (table.getRowSorter() == null) {
225            TableRowSorter<? extends TableModel> sorter = new TableRowSorter<>(table.getModel());
226            table.setRowSorter(sorter);
227            // only sort on columns that are String, Integer or Boolean (check boxes)
228            for (int i = 0; i < table.getColumnCount(); i++) {
229                if (table.getColumnClass(i) == String.class ||
230                        table.getColumnClass(i) == Integer.class ||
231                        table.getColumnClass(i) == Boolean.class) {
232                    continue; // allow sorting
233                }
234                sorter.setSortable(i, false);
235            }
236        }
237        // set row height
238        table.setRowHeight(new JComboBox<>().getPreferredSize().height);
239        // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
240        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
241        // give each cell a bit of space between the vertical lines and text
242        table.setIntercellSpacing(new Dimension(3, 1));
243        // table must have a name
244        table.setName(name + ":table"); // NOI18N
245        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
246        if (manager.isPresent()) {
247            manager.get().resetState(table);
248        }
249    }
250    
251    public static void persist(JTable table) {
252        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
253        if (manager.isPresent()) {
254            manager.get().persist(table);
255        }
256    }
257    
258    public static void cacheState(JTable table) {
259        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
260        if (manager.isPresent()) {
261            manager.get().cacheState(table);
262        }
263    }
264    
265    public static void saveTableState() {
266        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
267        if (manager.isPresent()) {
268            manager.get().setPaused(false); // cheater way to save preferences.
269        }
270    }
271
272    protected void clearTableSort(JTable table) {
273        if (table.getRowSorter() != null) {
274            table.getRowSorter().setSortKeys(null);
275        }
276    }
277
278    protected void storeValues() {
279        OperationsXml.save();
280    }
281
282/*
283 * Kludge fix for horizontal scrollbar encroaching buttons at bottom of a scrollable window.
284 */
285    protected void addHorizontalScrollBarKludgeFix(JScrollPane pane, JPanel panel) {
286        JPanel pad = new JPanel(); // kludge fix for horizontal scrollbar
287        pad.add(new JLabel(" "));
288        panel.add(pad);
289
290        // make sure control panel is the right size
291        pane.setMinimumSize(new Dimension(500, 130));
292        pane.setMaximumSize(new Dimension(2000, 170));
293        pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
294    }
295
296    protected String getWindowFrameRef() {
297        Container c = this.getTopLevelAncestor();
298        if (c instanceof JmriJFrame) {
299            return ((JmriJFrame) c).getWindowFrameRef();
300        }
301        return null;
302    }
303
304    private final static Logger log = LoggerFactory.getLogger(OperationsPanel.class);
305}