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            add(lockable[i], cL);
123            cL.gridx++;
124
125            // add the function buttons
126            _imageFilePath[i] = new EditableResizableImagePanel(re.getFunctionImage(i), 20, 20);
127            _imageFilePath[i].setDropFolder(Roster.getDefault().getRosterFilesLocation());
128            _imageFilePath[i].setBackground(new Color(0, 0, 0, 0));
129            _imageFilePath[i].setToolTipText(Bundle.getMessage("FunctionButtonRosterImageToolTip"));
130            _imageFilePath[i].setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
131            _imageFilePath[i].addMenuItemBrowseFolder(Bundle.getMessage("MediaRosterOpenSystemFileBrowserOnJMRIfnButtonsRessources"), jmri.util.FileUtil.getExternalFilename("resources/icons/functionicons"));
132            add(_imageFilePath[i], cL);
133            cL.gridx++;
134
135            _imagePressedFilePath[i] = new EditableResizableImagePanel(re.getFunctionSelectedImage(i), 20, 20);
136            _imagePressedFilePath[i].setDropFolder(Roster.getDefault().getRosterFilesLocation());
137            _imagePressedFilePath[i].setBackground(new Color(0, 0, 0, 0));
138            _imagePressedFilePath[i].setToolTipText(Bundle.getMessage("FunctionButtonPressedRosterImageToolTip"));
139            _imagePressedFilePath[i].setBorder(BorderFactory.createLineBorder(java.awt.Color.blue));
140            _imagePressedFilePath[i].addMenuItemBrowseFolder(Bundle.getMessage("MediaRosterOpenSystemFileBrowserOnJMRIfnButtonsRessources"), jmri.util.FileUtil.getExternalFilename("resources/icons/functionicons"));
141            add(_imagePressedFilePath[i], cL);
142            cL.gridx++;
143
144            shunterMode[i] = new JRadioButton();
145            shunterModeGroup.add(shunterMode[i]);
146            if (("F" + i).compareTo(re.getShuntingFunction()) == 0) {
147                shunterMode[i].setSelected(true);
148            }
149            shunterMode[i].setToolTipText(Bundle.getMessage("ShuntButtonToolTip"));
150            add(shunterMode[i], cL);
151            if (cL.gridx == 5) {
152                cL.gridx++;
153                // add divider
154                add(new JLabel("|"), cL);
155            }
156            // advance position
157            cL.gridy++;
158            if (cL.gridy == ((maxfunction + 2) / 2) + 1) {
159                cL.gridy = 1;  // skip titles
160                nextx = nextx + 7;
161            }
162            cL.gridx = nextx;
163        }
164    }
165
166    /**
167     * Check if panel contents differ with a RosterEntry.
168     *
169     * @param r the roster entry to check
170     * @return true if panel contents differ; false otherwise
171     */
172    public boolean guiChanged(RosterEntry r) {
173        if (labels != null) {
174            for (int i = 0; i < labels.length; i++) {
175                if (labels[i] != null) {
176                    if (r.getFunctionLabel(i) == null && !labels[i].getText().equals("")) {
177                        return true;
178                    }
179                    if (r.getFunctionLabel(i) != null && !r.getFunctionLabel(i).equals(labels[i].getText())) {
180                        return true;
181                    }
182                }
183            }
184        }
185        if (lockable != null) {
186            for (int i = 0; i < lockable.length; i++) {
187                if (lockable[i] != null) {
188                    if (r.getFunctionLockable(i) && !lockable[i].isSelected()) {
189                        return true;
190                    }
191                    if (!r.getFunctionLockable(i) && lockable[i].isSelected()) {
192                        return true;
193                    }
194                }
195            }
196        }
197        if (_imageFilePath != null) {
198            for (int i = 0; i < _imageFilePath.length; i++) {
199                if (_imageFilePath[i] != null) {
200                    if (r.getFunctionImage(i) == null && _imageFilePath[i].getImagePath() != null) {
201                        return true;
202                    }
203                    if (r.getFunctionImage(i) != null && !r.getFunctionImage(i).equals(_imageFilePath[i].getImagePath())) {
204                        return true;
205                    }
206                }
207            }
208        }
209        if (_imagePressedFilePath != null) {
210            for (int i = 0; i < _imagePressedFilePath.length; i++) {
211                if (_imagePressedFilePath[i] != null) {
212                    if (r.getFunctionSelectedImage(i) == null && _imagePressedFilePath[i].getImagePath() != null) {
213                        return true;
214                    }
215                    if (r.getFunctionSelectedImage(i) != null && !r.getFunctionSelectedImage(i).equals(_imagePressedFilePath[i].getImagePath())) {
216                        return true;
217                    }
218                }
219            }
220        }
221        if (shunterMode != null) {
222            String shunFn = "";
223            for (int i = 0; i < shunterMode.length; i++) {
224                if ((shunterMode[i] != null) && (shunterMode[i].isSelected())) {
225                    shunFn = "F" + i;
226                }
227            }
228            if (shunFn.compareTo(r.getShuntingFunction()) != 0) {
229                return true;
230            }
231        }
232        return false;
233    }
234
235    /**
236     * Update contents from a RosterEntry object
237     * <p>TODO: This doesn't do every element.
238     * @param re the new contents
239     */
240    public void updateFromEntry(RosterEntry re) {
241        if (labels != null) {
242             for (int i = 0; i < labels.length; i++) {
243                labels[i].setText(re.getFunctionLabel(i));
244                lockable[i].setSelected(re.getFunctionLockable(i));
245             }
246        }
247    }
248
249    /**
250     * Update a RosterEntry object from panel contents.
251     *
252     * @param r the roster entry to update
253     */
254    public void update(RosterEntry r) {
255        if (labels != null) {
256            String shunFn = "";
257            for (int i = 0; i < labels.length; i++) {
258                if (labels[i] != null && !labels[i].getText().equals("")) {
259                    r.setFunctionLabel(i, labels[i].getText());
260                    r.setFunctionLockable(i, lockable[i].isSelected());
261                    r.setFunctionImage(i, _imageFilePath[i].getImagePath());
262                    r.setFunctionSelectedImage(i, _imagePressedFilePath[i].getImagePath());
263                } else if (labels[i] != null && labels[i].getText().equals("")) {
264                    if (r.getFunctionLabel(i) != null) {
265                        r.setFunctionLabel(i, null);
266                        r.setFunctionImage(i, null);
267                        r.setFunctionSelectedImage(i, null);
268                    }
269                }
270                if ((shunterMode[i] != null) && (shunterMode[i].isSelected())) {
271                    shunFn = "F" + i;
272                }
273            }
274            r.setShuntingFunction(shunFn);
275        }
276    }
277
278    public void dispose() {
279        log.debug("dispose");
280    }
281
282    public boolean includeInPrint() {
283        return print;
284    }
285
286    public void includeInPrint(boolean inc) {
287        print = inc;
288    }
289    boolean print = false;
290
291    public void printPane(HardcopyWriter w) {
292        // if pane is empty, don't print anything
293        //if (varList.size() == 0 && cvList.size() == 0) return;
294        // future work needed here to print indexed CVs
295
296        // Define column widths for name and value output.
297        // Make col 2 slightly larger than col 1 and reduce both to allow for
298        // extra spaces that will be added during concatenation
299        int col1Width = w.getCharactersPerLine() / 2 - 3 - 5;
300        int col2Width = w.getCharactersPerLine() / 2 - 3 + 5;
301
302        try {
303            //Create a string of spaces the width of the first column
304            StringBuilder spaces = new StringBuilder();
305            for (int i = 0; i < col1Width; i++) {
306                spaces.append(" ");
307            }
308            // start with pane name in bold
309            String heading1 = Bundle.getMessage("ColumnHeadingFunction");
310            String heading2 = Bundle.getMessage("ColumnHeadingDescription");
311            String s;
312            int interval = spaces.length() - heading1.length();
313            w.setFontStyle(Font.BOLD);
314            // write the section name and dividing line
315            s = Bundle.getMessage("HeadingFunctionLabels");
316            w.write(s, 0, s.length());
317            w.writeBorders();
318            //Draw horizontal dividing line for each Pane section
319            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(),
320                    w.getCharactersPerLine() + 1);
321            s = "\n";
322            w.write(s, 0, s.length());
323
324            w.setFontStyle(Font.BOLD + Font.ITALIC);
325            s = "   " + heading1 + spaces.substring(0, interval) + "   " + heading2;
326            w.write(s, 0, s.length());
327            w.writeBorders();
328            s = "\n";
329            w.write(s, 0, s.length());
330            w.setFontStyle(Font.PLAIN);
331
332            // index over variables
333            for (int i = 0; i <= maxfunction; i++) {
334                String name = "" + i;
335                if (re.getFunctionLockable(i)) {
336                    name = name + " (locked)";
337                }
338                String value = re.getFunctionLabel(i);
339                //Skip Blank functions
340                if (value != null) {
341
342                    //define index values for name and value substrings
343                    int nameLeftIndex = 0;
344                    int nameRightIndex = name.length();
345                    int valueLeftIndex = 0;
346                    int valueRightIndex = value.length();
347                    String trimmedName;
348                    String trimmedValue;
349
350                    // Check the name length to see if it is wider than the column.
351                    // If so, split it and do the same checks for the Value
352                    // Then concatenate the name and value (or the split versions thereof)
353                    // before writing - if split, repeat until all pieces have been output
354                    while ((valueLeftIndex < value.length()) || (nameLeftIndex < name.length())) {
355                        // name split code
356                        if (name.substring(nameLeftIndex).length() > col1Width) {
357                            for (int j = 0; j < col1Width; j++) {
358                                String delimiter = name.substring(nameLeftIndex + col1Width - j - 1,
359                                        nameLeftIndex + col1Width - j);
360                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
361                                    nameRightIndex = nameLeftIndex + col1Width - j;
362                                    break;
363                                }
364                            }
365                            trimmedName = name.substring(nameLeftIndex, nameRightIndex);
366                            nameLeftIndex = nameRightIndex;
367                            int space = spaces.length() - trimmedName.length();
368                            s = "   " + trimmedName + spaces.substring(0, space);
369                        } else {
370                            trimmedName = name.substring(nameLeftIndex);
371                            int space = spaces.length() - trimmedName.length();
372                            s = "   " + trimmedName + spaces.substring(0, space);
373                            name = "";
374                            nameLeftIndex = 0;
375                        }
376                        // value split code
377                        if (value.substring(valueLeftIndex).length() > col2Width) {
378                            for (int j = 0; j < col2Width; j++) {
379                                String delimiter = value.substring(valueLeftIndex + col2Width - j - 1, valueLeftIndex + col2Width - j);
380                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
381                                    valueRightIndex = valueLeftIndex + col2Width - j;
382                                    break;
383                                }
384                            }
385                            trimmedValue = value.substring(valueLeftIndex, valueRightIndex);
386                            valueLeftIndex = valueRightIndex;
387                            s = s + "   " + trimmedValue;
388                        } else {
389                            trimmedValue = value.substring(valueLeftIndex);
390                            s = s + "   " + trimmedValue;
391                            valueLeftIndex = 0;
392                            value = "";
393                        }
394                        w.write(s, 0, s.length());
395                        w.writeBorders();
396                        s = "\n";
397                        w.write(s, 0, s.length());
398                    }
399                    // handle special cases
400                }
401            }
402            s = "\n";
403            w.writeBorders();
404            w.write(s, 0, s.length());
405            w.writeBorders();
406            w.write(s, 0, s.length());
407        } catch (IOException e) {
408            log.warn("error during printing", e);
409        }
410
411    }
412
413    private final static Logger log = LoggerFactory.getLogger(FunctionLabelPane.class);
414
415}