001package jmri.jmrit.roster;
002
003import java.awt.*;
004import java.io.IOException;
005
006import javax.swing.*;
007
008import jmri.util.davidflanagan.HardcopyWriter;
009import jmri.util.swing.EditableResizableImagePanel;
010
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Display and edit the function labels in a RosterEntry.
016 *
017 * @author Bob Jacobsen Copyright (C) 2008
018 * @author Randall Wood Copyright (C) 2014
019 */
020public class FunctionLabelPane extends javax.swing.JPanel {
021
022    RosterEntry re;
023
024    JTextField[] labels;
025    public JTextField getLabel(int index) { return labels[index]; }
026
027    JCheckBox[] lockable;
028    public JCheckBox getLockable(int index) { return lockable[index]; }
029
030    JRadioButton[] shunterMode;
031    ButtonGroup shunterModeGroup;
032    EditableResizableImagePanel[] _imageFilePath;
033    EditableResizableImagePanel[] _imagePressedFilePath;
034
035    private int maxfunction = 28; // default value
036
037    /**
038     * This constructor allows the panel to be used in visual bean editors, but
039     * should not be used in code.
040     */
041    public FunctionLabelPane() {
042        super();
043    }
044
045    public FunctionLabelPane(RosterEntry r) {
046        super();
047        re = r;
048        initGUI();
049    }
050
051    private void initGUI() {
052        maxfunction = re.getMaxFnNumAsInt();
053        GridBagLayout gbLayout = new GridBagLayout();
054        GridBagConstraints cL = new GridBagConstraints();
055        setLayout(gbLayout);
056
057        labels = new JTextField[maxfunction + 1];
058        lockable = new JCheckBox[maxfunction + 1];
059        shunterMode = new JRadioButton[maxfunction + 1];
060        shunterModeGroup = new ButtonGroup();
061        _imageFilePath = new EditableResizableImagePanel[maxfunction + 1];
062        _imagePressedFilePath = new EditableResizableImagePanel[maxfunction + 1];
063
064        cL.gridx = 0;
065        cL.gridy = 0;
066        cL.ipadx = 3;
067        cL.anchor = GridBagConstraints.NORTHWEST;
068        cL.insets = new Insets(0, 0, 0, 15);
069        cL.fill = GridBagConstraints.HORIZONTAL;
070        cL.weighty = 1.0;
071        int nextx = 0;
072
073        // column labels
074        // first column
075        add(new JLabel(Bundle.getMessage("FunctionButtonN")), cL);
076        cL.gridx++;
077        add(new JLabel(Bundle.getMessage("FunctionButtonLabel")), cL);
078        cL.gridx++;
079        add(new JLabel(Bundle.getMessage("FunctionButtonLockable")), cL);
080        cL.gridx++;
081        add(new JLabel(Bundle.getMessage("FunctionButtonImageOff")), cL);
082        cL.gridx++;
083        add(new JLabel(Bundle.getMessage("FunctionButtonImageOn")), cL);
084        cL.gridx++;
085        add(new JLabel(Bundle.getMessage("FunctionButtonShunterFn")), cL);
086        cL.gridx++;
087        // divider
088        add(new JLabel("|"));
089        cL.gridx++;
090        // second column
091        add(new JLabel(Bundle.getMessage("FunctionButtonN")), cL);
092        cL.gridx++;
093        add(new JLabel(Bundle.getMessage("FunctionButtonLabel")), cL);
094        cL.gridx++;
095        add(new JLabel(Bundle.getMessage("FunctionButtonLockable")), cL);
096        cL.gridx++;
097        add(new JLabel(Bundle.getMessage("FunctionButtonImageOff")), cL);
098        cL.gridx++;
099        add(new JLabel(Bundle.getMessage("FunctionButtonImageOn")), cL);
100        cL.gridx++;
101        add(new JLabel(Bundle.getMessage("FunctionButtonShunterFn")), cL);
102
103        cL.gridx = 0;
104        cL.gridy = 1;
105        // add function rows
106        for (int i = 0; i <= maxfunction; i++) {
107            // label the row
108            add(new JLabel("" + i), cL);
109            cL.gridx++;
110
111            // add the label
112            labels[i] = new JTextField(20);
113            if (re.getFunctionLabel(i) != null) {
114                labels[i].setText(re.getFunctionLabel(i));
115            }
116            add(labels[i], cL);
117            cL.gridx++;
118
119            // add the checkbox
120            lockable[i] = new JCheckBox();
121            lockable[i].setSelected(re.getFunctionLockable(i));
122            lockable[i].setToolTipText(Bundle.getMessage("FunctionButtonLockableToolTip"));
123            add(lockable[i], cL);
124            cL.gridx++;
125
126            // add the function buttons
127            _imageFilePath[i] = new EditableResizableImagePanel(re.getFunctionImage(i), 20, 20);
128            _imageFilePath[i].setDropFolder(Roster.getDefault().getRosterFilesLocation());
129            _imageFilePath[i].setBackground(new Color(0, 0, 0, 0));
130            _imageFilePath[i].setToolTipText(Bundle.getMessage("FunctionButtonRosterImageToolTip"));
131            _imageFilePath[i].setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
132            _imageFilePath[i].addMenuItemBrowseFolder(Bundle.getMessage("MediaRosterOpenSystemFileBrowserOnJMRIfnButtonsRessources"), jmri.util.FileUtil.getExternalFilename("resources/icons/functionicons"));
133            add(_imageFilePath[i], cL);
134            cL.gridx++;
135
136            _imagePressedFilePath[i] = new EditableResizableImagePanel(re.getFunctionSelectedImage(i), 20, 20);
137            _imagePressedFilePath[i].setDropFolder(Roster.getDefault().getRosterFilesLocation());
138            _imagePressedFilePath[i].setBackground(new Color(0, 0, 0, 0));
139            _imagePressedFilePath[i].setToolTipText(Bundle.getMessage("FunctionButtonPressedRosterImageToolTip"));
140            _imagePressedFilePath[i].setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
141            _imagePressedFilePath[i].addMenuItemBrowseFolder(Bundle.getMessage("MediaRosterOpenSystemFileBrowserOnJMRIfnButtonsRessources"), jmri.util.FileUtil.getExternalFilename("resources/icons/functionicons"));
142            add(_imagePressedFilePath[i], cL);
143            cL.gridx++;
144
145            shunterMode[i] = new JRadioButton();
146            shunterModeGroup.add(shunterMode[i]);
147            if (("F" + i).compareTo(re.getShuntingFunction()) == 0) {
148                shunterMode[i].setSelected(true);
149            }
150            shunterMode[i].setToolTipText(Bundle.getMessage("ShuntButtonToolTip"));
151            add(shunterMode[i], cL);
152            if (cL.gridx == 5) {
153                cL.gridx++;
154                // add divider
155                add(new JLabel("|"), cL);
156            }
157            // advance position
158            cL.gridy++;
159            if (cL.gridy == ((maxfunction + 2) / 2) + 1) {
160                cL.gridy = 1;  // skip titles
161                nextx = nextx + 7;
162            }
163            cL.gridx = nextx;
164        }
165    }
166
167    /**
168     * Check if panel contents differ with a RosterEntry.
169     *
170     * @param r the roster entry to check
171     * @return true if panel contents differ; false otherwise
172     */
173    public boolean guiChanged(RosterEntry r) {
174        if (labels != null) {
175            for (int i = 0; i < labels.length; i++) {
176                if (labels[i] != null) {
177                    if (r.getFunctionLabel(i) == null && !labels[i].getText().equals("")) {
178                        return true;
179                    }
180                    if (r.getFunctionLabel(i) != null && !r.getFunctionLabel(i).equals(labels[i].getText())) {
181                        return true;
182                    }
183                }
184            }
185        }
186        if (lockable != null) {
187            for (int i = 0; i < lockable.length; i++) {
188                if (lockable[i] != null) {
189                    if (r.getFunctionLockable(i) && !lockable[i].isSelected()) {
190                        return true;
191                    }
192                    if (!r.getFunctionLockable(i) && lockable[i].isSelected()) {
193                        return true;
194                    }
195                }
196            }
197        }
198        if (_imageFilePath != null) {
199            for (int i = 0; i < _imageFilePath.length; i++) {
200                if (_imageFilePath[i] != null) {
201                    if (r.getFunctionImage(i) == null && _imageFilePath[i].getImagePath() != null) {
202                        return true;
203                    }
204                    if (r.getFunctionImage(i) != null && !r.getFunctionImage(i).equals(_imageFilePath[i].getImagePath())) {
205                        return true;
206                    }
207                }
208            }
209        }
210        if (_imagePressedFilePath != null) {
211            for (int i = 0; i < _imagePressedFilePath.length; i++) {
212                if (_imagePressedFilePath[i] != null) {
213                    if (r.getFunctionSelectedImage(i) == null && _imagePressedFilePath[i].getImagePath() != null) {
214                        return true;
215                    }
216                    if (r.getFunctionSelectedImage(i) != null && !r.getFunctionSelectedImage(i).equals(_imagePressedFilePath[i].getImagePath())) {
217                        return true;
218                    }
219                }
220            }
221        }
222        if (shunterMode != null) {
223            String shunFn = "";
224            for (int i = 0; i < shunterMode.length; i++) {
225                if ((shunterMode[i] != null) && (shunterMode[i].isSelected())) {
226                    shunFn = "F" + i;
227                }
228            }
229            if (shunFn.compareTo(r.getShuntingFunction()) != 0) {
230                return true;
231            }
232        }
233        return false;
234    }
235
236    /**
237     * Update contents from a RosterEntry object
238     * <p>TODO: This doesn't do every element.
239     * @param re the new contents
240     */
241    public void updateFromEntry(RosterEntry re) {
242        if (labels != null) {
243             for (int i = 0; i < labels.length; i++) {
244                labels[i].setText(re.getFunctionLabel(i));
245                lockable[i].setSelected(re.getFunctionLockable(i));
246             }
247        }
248    }
249
250    /**
251     * Update a RosterEntry object from panel contents.
252     *
253     * @param r the roster entry to update
254     */
255    public void update(RosterEntry r) {
256        if (labels != null) {
257            String shunFn = "";
258            for (int i = 0; i < labels.length; i++) {
259                if (labels[i] != null && !labels[i].getText().equals("")) {
260                    r.setFunctionLabel(i, labels[i].getText());
261                    r.setFunctionLockable(i, lockable[i].isSelected());
262                    r.setFunctionImage(i, _imageFilePath[i].getImagePath());
263                    r.setFunctionSelectedImage(i, _imagePressedFilePath[i].getImagePath());
264                } else if (labels[i] != null && labels[i].getText().equals("")) {
265                    if (r.getFunctionLabel(i) != null) {
266                        r.setFunctionLabel(i, null);
267                        r.setFunctionImage(i, null);
268                        r.setFunctionSelectedImage(i, null);
269                    }
270                }
271                if ((shunterMode[i] != null) && (shunterMode[i].isSelected())) {
272                    shunFn = "F" + i;
273                }
274            }
275            r.setShuntingFunction(shunFn);
276        }
277    }
278
279    public void dispose() {
280        log.debug("dispose");
281    }
282
283    public boolean includeInPrint() {
284        return print;
285    }
286
287    public void includeInPrint(boolean inc) {
288        print = inc;
289    }
290    boolean print = false;
291
292    public void printPane(HardcopyWriter w) {
293        // if pane is empty, don't print anything
294        //if (varList.size() == 0 && cvList.size() == 0) return;
295        // future work needed here to print indexed CVs
296
297        // Define column widths for name and value output.
298        // Make col 2 slightly larger than col 1 and reduce both to allow for
299        // extra spaces that will be added during concatenation
300        int col1Width = w.getCharactersPerLine() / 2 - 3 - 5;
301        int col2Width = w.getCharactersPerLine() / 2 - 3 + 5;
302
303        try {
304            //Create a string of spaces the width of the first column
305            StringBuilder spaces = new StringBuilder();
306            for (int i = 0; i < col1Width; i++) {
307                spaces.append(" ");
308            }
309            // start with pane name in bold
310            String heading1 = Bundle.getMessage("ColumnHeadingFunction");
311            String heading2 = Bundle.getMessage("ColumnHeadingDescription");
312            String s;
313            int interval = spaces.length() - heading1.length();
314            w.setFontStyle(Font.BOLD);
315            // write the section name and dividing line
316            s = Bundle.getMessage("HeadingFunctionLabels");
317            w.write(s, 0, s.length());
318            w.writeBorders();
319            //Draw horizontal dividing line for each Pane section
320            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(),
321                    w.getCharactersPerLine() + 1);
322            s = "\n";
323            w.write(s, 0, s.length());
324
325            w.setFontStyle(Font.BOLD + Font.ITALIC);
326            s = "   " + heading1 + spaces.substring(0, interval) + "   " + heading2;
327            w.write(s, 0, s.length());
328            w.writeBorders();
329            s = "\n";
330            w.write(s, 0, s.length());
331            w.setFontStyle(Font.PLAIN);
332
333            // index over variables
334            for (int i = 0; i <= maxfunction; i++) {
335                String name = "" + i;
336                if (re.getFunctionLockable(i)) {
337                    name = name + " (locked)";
338                }
339                String value = re.getFunctionLabel(i);
340                //Skip Blank functions
341                if (value != null) {
342
343                    //define index values for name and value substrings
344                    int nameLeftIndex = 0;
345                    int nameRightIndex = name.length();
346                    int valueLeftIndex = 0;
347                    int valueRightIndex = value.length();
348                    String trimmedName;
349                    String trimmedValue;
350
351                    // Check the name length to see if it is wider than the column.
352                    // If so, split it and do the same checks for the Value
353                    // Then concatenate the name and value (or the split versions thereof)
354                    // before writing - if split, repeat until all pieces have been output
355                    while ((valueLeftIndex < value.length()) || (nameLeftIndex < name.length())) {
356                        // name split code
357                        if (name.substring(nameLeftIndex).length() > col1Width) {
358                            for (int j = 0; j < col1Width; j++) {
359                                String delimiter = name.substring(nameLeftIndex + col1Width - j - 1,
360                                        nameLeftIndex + col1Width - j);
361                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
362                                    nameRightIndex = nameLeftIndex + col1Width - j;
363                                    break;
364                                }
365                            }
366                            trimmedName = name.substring(nameLeftIndex, nameRightIndex);
367                            nameLeftIndex = nameRightIndex;
368                            int space = spaces.length() - trimmedName.length();
369                            s = "   " + trimmedName + spaces.substring(0, space);
370                        } else {
371                            trimmedName = name.substring(nameLeftIndex);
372                            int space = spaces.length() - trimmedName.length();
373                            s = "   " + trimmedName + spaces.substring(0, space);
374                            name = "";
375                            nameLeftIndex = 0;
376                        }
377                        // value split code
378                        if (value.substring(valueLeftIndex).length() > col2Width) {
379                            for (int j = 0; j < col2Width; j++) {
380                                String delimiter = value.substring(valueLeftIndex + col2Width - j - 1, valueLeftIndex + col2Width - j);
381                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
382                                    valueRightIndex = valueLeftIndex + col2Width - j;
383                                    break;
384                                }
385                            }
386                            trimmedValue = value.substring(valueLeftIndex, valueRightIndex);
387                            valueLeftIndex = valueRightIndex;
388                            s = s + "   " + trimmedValue;
389                        } else {
390                            trimmedValue = value.substring(valueLeftIndex);
391                            s = s + "   " + trimmedValue;
392                            valueLeftIndex = 0;
393                            value = "";
394                        }
395                        w.write(s, 0, s.length());
396                        w.writeBorders();
397                        s = "\n";
398                        w.write(s, 0, s.length());
399                    }
400                    // handle special cases
401                }
402            }
403            s = "\n";
404            w.writeBorders();
405            w.write(s, 0, s.length());
406            w.writeBorders();
407            w.write(s, 0, s.length());
408        } catch (IOException e) {
409            log.warn("error during printing", e);
410        }
411
412    }
413
414    private final static Logger log = LoggerFactory.getLogger(FunctionLabelPane.class);
415
416}