001package jmri.jmrit.operations;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import java.util.Optional;
006
007import javax.swing.*;
008import javax.swing.colorchooser.AbstractColorChooserPanel;
009import javax.swing.event.ChangeEvent;
010import javax.swing.table.TableModel;
011import javax.swing.table.TableRowSorter;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import jmri.InstanceManager;
017import jmri.jmrit.operations.rollingstock.cars.CarTypes;
018import jmri.jmrit.operations.setup.Control;
019import jmri.jmrit.operations.trains.TrainCommon;
020import jmri.swing.JTablePersistenceManager;
021import jmri.util.JmriJFrame;
022import jmri.util.swing.SplitButtonColorChooserPanel;
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 dispose() {
039        // The default method does nothing.
040    }
041
042    /**
043     * Increases the width of the ComboBox to the maximum number of characters
044     * for a standard attribute. This prevents names from being truncated when
045     * displayed.
046     * 
047     * @param comboBox the box needing width adjustment
048     */
049    public static void padComboBox(JComboBox<?> comboBox) {
050        padComboBox(comboBox, Control.max_len_string_attibute + 1);
051    }
052
053    /**
054     * Increases the width of the ComboBox so the names don't get truncated when
055     * displayed. If there are names in the ComboxBox that exceed the character
056     * count, then the wider width is used.
057     * 
058     * @param comboBox the box needing width adjustment
059     * @param count    the minimum number of characters to display properly
060     */
061    public static void padComboBox(JComboBox<?> comboBox, int count) {
062        StringBuffer sb = new StringBuffer();
063        for (int i = 0; i < count; i++) {
064            sb.append("X"); // wider than average
065        }
066        JComboBox<String> pad = new JComboBox<>();
067        pad.addItem(sb.toString());
068        if (comboBox.getPreferredSize().getWidth() < pad.getPreferredSize().getWidth()) {
069            Dimension boxDim =
070                    new Dimension((int) pad.getPreferredSize().getWidth(),
071                            (int) comboBox.getPreferredSize().getHeight());
072            comboBox.setPreferredSize(boxDim);
073        }
074    }
075
076    protected void addItem(JComponent c, int x, int y) {
077        GridBagConstraints gc = new GridBagConstraints();
078        gc.gridx = x;
079        gc.gridy = y;
080        gc.weightx = 100.0;
081        gc.weighty = 100.0;
082        this.add(c, gc);
083    }
084
085    protected void addItem(JPanel p, JComponent c, int x, int y) {
086        GridBagConstraints gc = new GridBagConstraints();
087        gc.gridx = x;
088        gc.gridy = y;
089        gc.weightx = 100.0;
090        gc.weighty = 100.0;
091        p.add(c, gc);
092    }
093
094    protected void addItemLeft(JPanel p, JComponent c, int x, int y) {
095        GridBagConstraints gc = new GridBagConstraints();
096        gc.gridx = x;
097        gc.gridy = y;
098        gc.weightx = 100.0;
099        gc.weighty = 100.0;
100        gc.anchor = GridBagConstraints.WEST;
101        p.add(c, gc);
102    }
103
104    protected void addItemTop(JPanel p, JComponent c, int x, int y) {
105        GridBagConstraints gc = new GridBagConstraints();
106        gc.gridx = x;
107        gc.gridy = y;
108        gc.weightx = 100;
109        gc.weighty = 100;
110        gc.anchor = GridBagConstraints.NORTH;
111        p.add(c, gc);
112    }
113
114    protected void addItemWidth(JPanel p, JComponent c, int width, int x, int y) {
115        GridBagConstraints gc = new GridBagConstraints();
116        gc.gridx = x;
117        gc.gridy = y;
118        gc.gridwidth = width;
119        gc.weightx = 100.0;
120        gc.weighty = 100.0;
121        gc.anchor = GridBagConstraints.WEST;
122        p.add(c, gc);
123    }
124
125    private static final int MIN_CHECKBOXES = 5;
126    private static final int MAX_CHECKBOXES = 11;
127
128    protected int getNumberOfCheckboxesPerLine(Dimension size) {
129        if (size == null) {
130            return MIN_CHECKBOXES; // default is 6 checkboxes per row
131        }
132        StringBuilder padding = new StringBuilder("X");
133        for (int i = 0; i < InstanceManager.getDefault(CarTypes.class).getMaxFullNameLength(); i++) {
134            padding.append("X");
135        }
136
137        JCheckBox box = new JCheckBox(padding.toString());
138        int number = size.width / (box.getPreferredSize().width);
139        if (number < MIN_CHECKBOXES) {
140            number = MIN_CHECKBOXES;
141        }
142        if (number > MAX_CHECKBOXES) {
143            number = MAX_CHECKBOXES;
144        }
145        return number;
146    }
147
148    protected void addButtonAction(JButton b) {
149        b.addActionListener((ActionEvent e) -> {
150            buttonActionPerformed(e);
151        });
152    }
153
154    protected void buttonActionPerformed(ActionEvent ae) {
155        log.debug("button action not overridden");
156    }
157
158    protected void addRadioButtonAction(JRadioButton b) {
159        b.addActionListener((ActionEvent e) -> {
160            radioButtonActionPerformed(e);
161        });
162    }
163
164    protected void radioButtonActionPerformed(ActionEvent ae) {
165        log.debug("radio button action not overridden");
166    }
167
168    protected void addCheckBoxAction(JCheckBox b) {
169        b.addActionListener((ActionEvent e) -> {
170            checkBoxActionPerformed(e);
171        });
172    }
173
174    protected void checkBoxActionPerformed(ActionEvent ae) {
175        log.debug("check box action not overridden");
176    }
177
178    protected void addSpinnerChangeListerner(JSpinner s) {
179        s.addChangeListener((ChangeEvent e) -> {
180            spinnerChangeEvent(e);
181        });
182    }
183
184    protected void spinnerChangeEvent(javax.swing.event.ChangeEvent ae) {
185        log.debug("spinner action not overridden");
186    }
187
188    protected void addComboBoxAction(JComboBox<?> b) {
189        b.addActionListener((ActionEvent e) -> {
190            comboBoxActionPerformed(e);
191        });
192    }
193
194    protected void comboBoxActionPerformed(ActionEvent ae) {
195        log.debug("combobox action not overridden");
196    }
197
198    protected void selectNextItemComboBox(JComboBox<?> b) {
199        int newIndex = b.getSelectedIndex() + 1;
200        if (newIndex < b.getItemCount()) {
201            b.setSelectedIndex(newIndex);
202        }
203    }
204
205    /**
206     * Will modify the character column width of a TextArea box to 90% of a
207     * panels width. ScrollPane is set to 95% of panel width.
208     *
209     * @param scrollPane the pane containing the textArea
210     * @param textArea   the textArea to adjust
211     * @param size       the preferred size
212     */
213    protected void adjustTextAreaColumnWidth(JScrollPane scrollPane, JTextArea textArea, Dimension size) {
214        FontMetrics metrics = getFontMetrics(textArea.getFont());
215        int columnWidth = metrics.charWidth('m');
216        int width = size.width;
217        int columns = width / columnWidth * 90 / 100; // make text area 90% of the panel width
218        if (columns > textArea.getColumns()) {
219            log.debug("Increasing text area character width to {} columns", columns);
220            textArea.setColumns(columns);
221        }
222        scrollPane.setMinimumSize(new Dimension(width * 95 / 100, 60));
223    }
224
225    /**
226     * Load the table width, position, and sorting status from the user
227     * preferences file.
228     *
229     * @param table The table to be adjusted.
230     */
231    public void loadTableDetails(JTable table) {
232        loadTableDetails(table, getWindowFrameRef());
233        persist(table);
234    }
235
236    public static void loadTableDetails(JTable table, String name) {
237        if (table.getRowSorter() == null) {
238            TableRowSorter<? extends TableModel> sorter = new TableRowSorter<>(table.getModel());
239            table.setRowSorter(sorter);
240            // only sort on columns that are String, Integer or Boolean (check boxes)
241            for (int i = 0; i < table.getColumnCount(); i++) {
242                if (table.getColumnClass(i) == String.class ||
243                        table.getColumnClass(i) == Integer.class ||
244                        table.getColumnClass(i) == Boolean.class) {
245                    continue; // allow sorting
246                }
247                sorter.setSortable(i, false);
248            }
249        }
250        // set row height
251        table.setRowHeight(new JComboBox<>().getPreferredSize().height);
252        // have to shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541)
253        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
254        // give each cell a bit of space between the vertical lines and text
255        table.setIntercellSpacing(new Dimension(3, 1));
256        // table must have a name
257        table.setName(name + ":table"); // NOI18N
258        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
259        if (manager.isPresent()) {
260            manager.get().resetState(table);
261        }
262    }
263
264    public static void persist(JTable table) {
265        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
266        if (manager.isPresent()) {
267            manager.get().persist(table);
268        }
269    }
270
271    public static void cacheState(JTable table) {
272        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
273        if (manager.isPresent()) {
274            manager.get().cacheState(table);
275        }
276    }
277
278    public static void saveTableState() {
279        Optional<JTablePersistenceManager> manager = InstanceManager.getOptionalDefault(JTablePersistenceManager.class);
280        if (manager.isPresent()) {
281            manager.get().setPaused(false); // cheater way to save preferences.
282        }
283    }
284
285    protected void clearTableSort(JTable table) {
286        if (table.getRowSorter() != null) {
287            table.getRowSorter().setSortKeys(null);
288        }
289    }
290
291    protected void storeValues() {
292        OperationsXml.save();
293    }
294
295    /*
296     * Kludge fix for horizontal scrollbar encroaching buttons at bottom of a
297     * scrollable window.
298     */
299    protected void addHorizontalScrollBarKludgeFix(JScrollPane pane, JPanel panel) {
300        JPanel pad = new JPanel(); // kludge fix for horizontal scrollbar
301        pad.add(new JLabel(" "));
302        panel.add(pad);
303
304        // make sure control panel is the right size
305        pane.setMinimumSize(new Dimension(500, 130));
306        pane.setMaximumSize(new Dimension(2000, 170));
307        pane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
308    }
309
310    protected String getWindowFrameRef() {
311        Container c = this.getTopLevelAncestor();
312        if (c instanceof JmriJFrame) {
313            return ((JmriJFrame) c).getWindowFrameRef();
314        }
315        return null;
316    }
317
318    public static JPanel getColorChooserPanel(String text, JColorChooser chooser) {
319        return getColorChooserPanel(Bundle.getMessage("TextColor"), TrainCommon.getTextColor(text), chooser);
320    }
321
322    public static JPanel getColorChooserPanel(String title, Color color, JColorChooser chooser) {
323        JPanel pTextColorPanel = new JPanel();
324        pTextColorPanel.setBorder(BorderFactory.createTitledBorder(title));
325        chooser.setColor(color);
326        AbstractColorChooserPanel commentColorPanels[] = {new SplitButtonColorChooserPanel()};
327        chooser.setChooserPanels(commentColorPanels);
328        chooser.setPreviewPanel(new JPanel());
329        pTextColorPanel.add(chooser);
330        return pTextColorPanel;
331    }
332
333    public static void loadFontSizeComboBox(JComboBox<Integer> box) {
334        // load font sizes 7 through 18
335        for (int i = 7; i < 19; i++) {
336            box.addItem(i);
337        }
338        Dimension size = box.getPreferredSize();
339        size = new Dimension(size.width + 10, size.height);
340        box.setPreferredSize(size);
341    }
342
343    private final static Logger log = LoggerFactory.getLogger(OperationsPanel.class);
344}