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