001package jmri.jmrit.throttle.panels;
002
003import java.awt.*;
004import java.awt.event.ActionEvent;
005import javax.swing.*;
006
007import jmri.Throttle;
008import jmri.util.FileUtil;
009import jmri.util.swing.EditableResizableImagePanel;
010import jmri.util.swing.JmriJOptionPane;
011
012/**
013 * A very specific dialog for editing the properties of a FunctionButton object.
014 * 
015 * <hr>
016 * This file is part of JMRI.
017 * <p>
018 * JMRI is free software; you can redistribute it and/or modify it under the
019 * terms of version 2 of the GNU General Public License as published by the Free
020 * Software Foundation. See the "COPYING" file for a copy of this license.
021 * <p>
022 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
023 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
024 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
025 * 
026 */
027public final class FunctionButtonPropertyEditor extends JDialog {
028
029    private final FunctionButton button;
030
031    private JTextField textField;
032    private JCheckBox lockableCheckBox;
033    private JTextField idField;
034    private JTextField fontField;
035    private JCheckBox visibleCheckBox;
036    private EditableResizableImagePanel _imageFilePath;
037    private EditableResizableImagePanel _imagePressedFilePath;
038    private JTextField imageSize;
039    static final int BUT_IMG_SIZE = 45;
040
041    /**
042     * Constructor. Create it and pack it.
043     * @param btn the functionButton
044     */
045    public FunctionButtonPropertyEditor(FunctionButton btn) {
046        button = btn;
047        initGUI();
048        resetProperties();
049    }
050
051    /**
052     * Create, initialise, and place the GUI objects.
053     */
054    private void initGUI() {
055        this.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
056        this.setTitle(Bundle.getMessage("ButtonEditFunction"));
057        JPanel mainPanel = new JPanel();
058        this.setContentPane(mainPanel);
059        mainPanel.setLayout(new BorderLayout());
060        
061        JPanel propertyPanel = new JPanel();
062        propertyPanel.setLayout(new GridBagLayout());
063        GridBagConstraints constraints = new GridBagConstraints();
064        constraints.anchor = GridBagConstraints.WEST;
065        constraints.fill = GridBagConstraints.HORIZONTAL;
066        constraints.gridheight = 1;
067        constraints.gridwidth = 1;
068        constraints.ipadx = 0;
069        constraints.ipady = 0;
070        Insets insets = new Insets(2, 2, 2, 2);
071        constraints.insets = insets;
072        constraints.weightx = 1;
073        constraints.weighty = 1;
074        constraints.gridx = 0;
075        constraints.gridy = 0;
076
077        idField = new JTextField();
078        idField.setColumns(1);
079        propertyPanel.add(new JLabel(Bundle.getMessage("LabelFunctionNumber")), constraints);
080
081        constraints.anchor = GridBagConstraints.CENTER;
082        constraints.gridx = 1;
083        propertyPanel.add(idField, constraints);
084
085        constraints.anchor = GridBagConstraints.WEST;
086        constraints.gridx = 0;
087        constraints.gridy ++;
088        textField = new JTextField();
089        textField.setColumns(10);
090        propertyPanel.add(new JLabel(Bundle.getMessage("LabelText")), constraints);
091
092        constraints.anchor = GridBagConstraints.CENTER;
093        constraints.gridx = 1;
094        propertyPanel.add(textField, constraints);
095
096        constraints.anchor = GridBagConstraints.WEST;
097        constraints.gridx = 0;
098        constraints.gridy ++;
099        fontField = new JTextField();
100        fontField.setColumns(10);
101        propertyPanel.add(new JLabel(Bundle.getMessage("LabelFontSize")), constraints);
102
103        constraints.anchor = GridBagConstraints.CENTER;
104        constraints.gridx = 1;
105        propertyPanel.add(fontField, constraints);
106        
107        constraints.anchor = GridBagConstraints.WEST;
108        constraints.gridx = 0;
109        constraints.gridy ++;
110        imageSize = new JTextField();
111        imageSize.setColumns(10);
112        propertyPanel.add(new JLabel(Bundle.getMessage("LabelFunctionImageSize")), constraints);
113
114        constraints.anchor = GridBagConstraints.CENTER;
115        constraints.gridx = 1;
116        propertyPanel.add(imageSize, constraints);
117
118        lockableCheckBox = new JCheckBox(Bundle.getMessage("CheckBoxLockable"));
119        constraints.anchor = GridBagConstraints.CENTER;
120        constraints.gridx = 0;
121        constraints.gridy ++;
122        propertyPanel.add(lockableCheckBox, constraints);
123
124        visibleCheckBox = new JCheckBox(Bundle.getMessage("CheckBoxVisible"));
125        constraints.anchor = GridBagConstraints.CENTER;
126        constraints.gridx = 0;
127        constraints.gridy ++;
128        propertyPanel.add(visibleCheckBox, constraints);
129
130        constraints.gridy ++;
131        constraints.gridx = 0;
132        propertyPanel.add(new JLabel(Bundle.getMessage("OffIcon")), constraints);
133
134        constraints.gridx = 1;
135        propertyPanel.add(new JLabel(Bundle.getMessage("OnIcon")), constraints);
136
137        constraints.gridy ++;
138        constraints.fill = GridBagConstraints.BOTH;
139        constraints.weighty = 100;
140        constraints.gridx = 0;        
141        _imageFilePath = new EditableResizableImagePanel("", BUT_IMG_SIZE, BUT_IMG_SIZE);
142        _imageFilePath.setDropFolder(FileUtil.getUserResourcePath()); // will be updated later on if curent throttle is in roster db
143        _imageFilePath.setBackground(new Color(0, 0, 0, 0));
144        _imageFilePath.setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
145        _imageFilePath.addMenuItemBrowseFolder(Bundle.getMessage("OpenSystemFileBrowserOnJMRIfnButtonsRessources"), FileUtil.getExternalFilename("resources/icons/functionicons"));
146        _imageFilePath.addComponentListener(_imageFilePath); // listen to itself, will rescale image when need
147        propertyPanel.add(_imageFilePath, constraints);
148
149        constraints.gridx = 1;
150        _imagePressedFilePath = new EditableResizableImagePanel("", BUT_IMG_SIZE, BUT_IMG_SIZE);
151        _imagePressedFilePath.setDropFolder(FileUtil.getUserResourcePath()); // will be updated later on if curent throttle is in roster db
152        _imagePressedFilePath.setBackground(new Color(0, 0, 0, 0));
153        _imagePressedFilePath.setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
154        _imagePressedFilePath.addMenuItemBrowseFolder(Bundle.getMessage("OpenSystemFileBrowserOnJMRIfnButtonsRessources"), FileUtil.getExternalFilename("resources/icons/functionicons"));
155        _imagePressedFilePath.addComponentListener(_imagePressedFilePath);  // listen to itself, will rescale image when needed
156        propertyPanel.add(_imagePressedFilePath, constraints);
157
158        JPanel buttonPanel = new JPanel();
159        buttonPanel.setLayout(new GridLayout(1, 2, 4, 4));
160
161        JButton applyButton = new JButton(Bundle.getMessage("ButtonApply"));
162        applyButton.addActionListener((ActionEvent e) -> {
163            saveProperties();
164        });
165                
166        JButton resetButton = new JButton(Bundle.getMessage("ButtonReset"));
167        resetButton.addActionListener((ActionEvent e) -> {
168            resetProperties();           
169        });        
170        
171        JButton closeButton = new JButton(Bundle.getMessage("ButtonClose"));
172        closeButton.addActionListener((ActionEvent e) -> {
173            finishEdit();
174        });
175
176        buttonPanel.add(resetButton);
177        buttonPanel.add(closeButton);        
178        buttonPanel.add(applyButton);
179
180        mainPanel.add(propertyPanel, BorderLayout.CENTER);
181        mainPanel.add(buttonPanel, BorderLayout.SOUTH);
182
183        pack();
184    }
185
186    /**
187     * Initialize GUI from button properties.
188     *
189     */
190    public void resetProperties() {
191        textField.setText(button.getButtonLabel());
192        lockableCheckBox.setSelected(button.getIsLockable());
193        idField.setText(String.valueOf(button.getIdentity()));
194        Throttle mThrottle = button.getThrottle();
195        if (mThrottle!=null) {
196            idField.setToolTipText(Bundle.getMessage("MaxFunction",mThrottle.getFunctions().length -1));
197        }
198        fontField.setText(String.valueOf(button.getFont().getSize()));
199        imageSize.setText(String.valueOf(button.getButtonImageSize()));
200        visibleCheckBox.setSelected(button.getDisplay());
201        _imageFilePath.setImagePath(button.getIconPath());
202        _imagePressedFilePath.setImagePath(button.getSelectedIconPath());
203        textField.requestFocus();
204    }
205
206    /**
207     * Save the user-modified properties back to the FunctionButton.
208     */
209    private void saveProperties() {
210        if (isDataValid()) {
211            button.setButtonLabel(textField.getText());
212            button.setIsLockable(lockableCheckBox.isSelected());
213            button.setIdentity(Integer.parseInt(idField.getText()));
214            String name = button.getFont().getName();
215            button.setFont(new Font(name,
216                    button.getFont().getStyle(),
217                    Integer.parseInt(fontField.getText())));
218            button.setButtonImageSize( Integer.parseInt(imageSize.getText()) );
219            button.setDisplay(visibleCheckBox.isSelected());
220            button.setIconPath(_imageFilePath.getImagePath());
221            button.setSelectedIconPath(_imagePressedFilePath.getImagePath());
222            button.setDirty(true);
223            button.updateLnF();
224        }
225    }
226
227    /**
228     * Finish the editing process. Hide the dialog.
229     */
230    private void finishEdit() {
231        this.setVisible(false);
232    }
233
234    /**
235     * Verify the data on the dialog. If invalid, notify user of errors.
236     * @return true if valid, else false.
237     */
238    private boolean isDataValid() {
239        StringBuffer errors = new StringBuffer();
240        int errorNumber = 0;
241        /* ID >=0 && ID <= 28 */
242        
243        Throttle mThrottle = button.getThrottle();
244        if (mThrottle==null) {
245            return false;
246        }
247        
248        try {
249            int id = Integer.parseInt(idField.getText());
250            if ((id < 0) || id >= mThrottle.getFunctions().length) {
251                throw new NumberFormatException("");
252            }
253        } catch (NumberFormatException ex) {
254            errors.append(String.valueOf(++errorNumber)).append(". ");
255            errors.append(Bundle.getMessage("ErrorFunctionKeyRange",
256                mThrottle.getFunctions().length-1)).append("\n");
257        }
258
259        /* font > 0 */
260        try {
261            int size = Integer.parseInt(fontField.getText());
262            if (size < 1) {
263                throw new NumberFormatException("");
264            }
265        } catch (NumberFormatException ex) {
266            errors.append(String.valueOf(++errorNumber)).append(". ");
267            errors.append( Bundle.getMessage("ErrorFontSize"));
268        }
269
270        /* image size > 0 */
271        try {
272            int size = Integer.parseInt(imageSize.getText());
273            if (size < 1) {
274                throw new NumberFormatException("");
275            }
276        } catch (NumberFormatException ex) {
277            errors.append(String.valueOf(++errorNumber)).append(". ");
278            errors.append( Bundle.getMessage("ErrorImageSize"));
279        }        
280
281        if (errorNumber > 0) {
282            JmriJOptionPane.showMessageDialog(this, errors,
283                Bundle.getMessage("ErrorOnPage"), JmriJOptionPane.ERROR_MESSAGE);
284            return false;
285        }
286        return true;
287    }
288
289    /**
290     * Set the folder where droped images in Property panel will be stored
291     * 
292     * @param dropFolder the folder path
293     * 
294     */
295    void setDropFolder(String dropFolder) {
296        _imageFilePath.setDropFolder(dropFolder);
297        _imagePressedFilePath.setDropFolder(dropFolder);
298    }
299
300    void destroy() {
301        if (_imageFilePath != null) {
302            _imageFilePath.removeDnd();
303        }
304        if (_imagePressedFilePath != null) {
305            _imagePressedFilePath.removeDnd();
306        }
307    }
308}