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