001package jmri.jmrit.roster;
002
003import java.awt.*;
004import java.io.IOException;
005import java.util.List;
006
007import javax.swing.*;
008import javax.swing.table.DefaultTableCellRenderer;
009
010import jmri.jmrit.roster.swing.functiontable.FunctionTableCellRenderer;
011import jmri.jmrit.roster.swing.functiontable.FunctionTableModel;
012import jmri.jmrit.roster.swing.functiontable.FunctionTableMouseListener;
013import jmri.jmrit.roster.swing.functiontable.FunctionTableRowTransferHandler;
014import jmri.util.davidflanagan.HardcopyWriter;
015import jmri.util.swing.JmriMouseListener;
016
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020/**
021 * Display and edit the function labels in a RosterEntry.
022 * 
023 * <hr>
024 * This file is part of JMRI.
025 * <p>
026 * JMRI is free software; you can redistribute it and/or modify it under the
027 * terms of version 2 of the GNU General Public License as published by the Free
028 * Software Foundation. See the "COPYING" file for a copy of this license.
029 * <p>
030 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY
031 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
032 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
033 *
034 * @author Bob Jacobsen Copyright (C) 2008
035 * @author Randall Wood Copyright (C) 2014
036 * @author Lionel Jeanson Copyright (C) 2009-2026
037 * 
038 */
039public class FunctionLabelPane extends javax.swing.JPanel {
040
041    private int maxfunction = 28; // default value (28 included [0..28] = 29 functions)
042    private final FunctionTableModel functionTableModel;
043    private JTable functionsTable;
044
045
046    /**
047     * This constructor allows the panel to be used in visual bean editors, but
048     * should not be used in code.
049     */
050    public FunctionLabelPane() {
051        super();
052        functionTableModel = null;
053    }
054
055    public FunctionLabelPane(RosterEntry re) {
056        super();
057        functionTableModel = new FunctionTableModel(re);
058        initGUI();
059    }
060
061    private void initGUI() {
062        maxfunction = functionTableModel.getRosterEntry().getMaxFnNumAsInt();
063        setLayout(new BorderLayout());
064        functionsTable = new JTable(functionTableModel);
065        // renderer
066        FunctionTableCellRenderer renderer = new FunctionTableCellRenderer();
067        functionsTable.setDefaultRenderer(Object.class, renderer);
068        functionsTable.setDefaultRenderer(Boolean.class, renderer); // force boolean values to use the same renderer as other values
069        // allow contextual menu on image cells
070        functionsTable.addMouseListener( JmriMouseListener.adapt(new FunctionTableMouseListener(functionsTable)));
071        // allow drag'n drop of rows to reorder display order
072        functionsTable.setDragEnabled(true);
073        functionsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
074        functionsTable.setDropMode(DropMode.INSERT_ROWS);
075        functionsTable.getTableHeader().setReorderingAllowed(false); // forbid columns reodering
076        functionsTable.setTransferHandler(new FunctionTableRowTransferHandler(functionsTable));
077        // column width and tooltips on columns titles
078        for (int i = 0; i < functionsTable.getColumnCount(); i++) {
079            if (i<functionsTable.getColumnCount()-1) { // let's keep last column large (label)
080                functionsTable.getColumnModel().getColumn(i).setMaxWidth(FunctionTableCellRenderer.columnWidths[i]);
081            }
082            if (FunctionTableCellRenderer.columnTooltips[i] != null) {
083                DefaultTableCellRenderer headerRenderer = new DefaultTableCellRenderer();
084                headerRenderer.setToolTipText(FunctionTableCellRenderer.columnTooltips[i]);
085                functionsTable.getColumnModel().getColumn(i).setHeaderRenderer(headerRenderer);
086            }
087        }
088        functionsTable.setRowHeight(FunctionTableCellRenderer.CELL_HEIGHT);
089        functionsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
090
091        JScrollPane scrollPane = new JScrollPane(functionsTable);
092        add(scrollPane, BorderLayout.CENTER);
093    }
094
095    public List<String> getLabels() {
096        return functionTableModel.getLabels();
097    }
098    
099    public void setLabel(int n, String label) {
100        functionTableModel.setLabel(n, label);
101    }
102
103    public String getLabel(int n) {
104        return functionTableModel.getLabel(n);
105    }    
106
107    public void setLockable(int n, boolean lockable) {
108        functionTableModel.setLockable(n, lockable);
109    }
110
111    public boolean getLockable(int n) {
112        return functionTableModel.getLockable(n);
113    } 
114
115    /**
116     * Check if panel contents differ with a RosterEntry.
117     *
118     * @param r the roster entry to check
119     * @return true if panel contents differ; false otherwise
120     */
121    public boolean guiChanged(RosterEntry r) {
122        RosterEntry re = functionTableModel.getRosterEntry();
123        if (r == null) {
124            return true;
125        }
126        // shunting fn
127        if (r.getShuntingFunction() == null && re.getShuntingFunction() != null) {
128            return true;
129        }
130        if (r.getShuntingFunction() != null && re.getShuntingFunction() == null) {
131            return true;
132        }
133        if (r.getShuntingFunction() != null && re.getShuntingFunction() != null && !r.getShuntingFunction().equals(re.getShuntingFunction())) {
134            return true;
135        }
136        // number of entries
137        if (r.functionEntries.size() != re.functionEntries.size()) {
138            return true;
139        }
140        if (r.functionEntries.size() == 0 && re.functionEntries.size() == 0) {
141            return false;
142        }
143        // compare labels, lockable, visible, image and pressed image 
144        for (int i = 0; i <= maxfunction; i++) {
145            if (r.getFunctionEntry(i) == null && re.getFunctionEntry(i) != null) {
146                return true;   
147            }
148            if (r.getFunctionEntry(i) == null && re.getFunctionEntry(i) == null) {
149                return false;   
150            }
151            if (! r.getFunctionEntry(i).equals(re.getFunctionEntry(i))) {
152                return true;
153            }
154        }
155        // compare function display order
156        if (r.getAttribute(FunctionTableModel.FB_ORDER_ATTR) == null && functionTableModel.getVisibilityOrderAttributeString() != null) {
157            log.debug("Function display order differs : re: {} vs fntbl: {}",r.getAttribute(FunctionTableModel.FB_ORDER_ATTR), functionTableModel.getVisibilityOrderAttributeString());
158            return true;
159        }
160        if (r.getAttribute(FunctionTableModel.FB_ORDER_ATTR) != null && !r.getAttribute(FunctionTableModel.FB_ORDER_ATTR).equals(functionTableModel.getVisibilityOrderAttributeString())) {
161            log.debug("Function display order differs : re: {} vs fntbl: {}",r.getAttribute(FunctionTableModel.FB_ORDER_ATTR), functionTableModel.getVisibilityOrderAttributeString());
162            return true;
163        }
164        return false;
165    }
166
167    /**
168     * Clear all function button inputs on the panel
169     */
170    public void clearFnBtn() {
171        functionTableModel.reset(null,"",null);
172    }
173
174    /**
175     * Reset all function button inputs on the panel to the NMRA standard
176     */
177    public void resetFnBtnNMRA() {
178        functionTableModel.reset(RosterFunctionEntriesDefaults.NMRA_FUNCTION_ENTRIES, RosterFunctionEntriesDefaults.NMRA_SHUNTING_FN,null);
179    }
180    
181    /**
182     * Reset all function button inputs on the panel to the NEM 608 definition
183     */    
184    public void resetFnBtnNEM() {
185        functionTableModel.reset(RosterFunctionEntriesDefaults.NEM_FUNCTION_ENTRIES, RosterFunctionEntriesDefaults.NEM_SHUNTING_FN,null);
186    }
187
188    /**
189     * Reset all function button inputs on the panel to the one defined in the decoder definition, if any
190     * 
191     * @param decre the roster entry in which to look for a decoder definition
192     */      
193    public void resetFnBtnDecDef(RosterEntry decre) {
194        if (decre == null) {
195            return;
196        }
197        functionTableModel.reset(decre.getFunctionEntries(), "", null);
198    }
199
200    /**
201     * Update contents from a RosterEntry object
202     * 
203     * @param re the new contents
204     */
205    public void updateFromEntry(RosterEntry re) {
206        functionTableModel.setRosterEntry(re);
207    }
208
209    /**
210     * Update a RosterEntry object from panel contents.
211     *
212     * @param r the roster entry to update
213     */
214    public void update(RosterEntry r) {
215        RosterEntry re = functionTableModel.getRosterEntry();
216        // function definitions
217        for (int i = 0; i <= maxfunction; i++) {
218            RosterFunctionEntry fn = re.getFunctionEntry(i);
219            if (fn != null) {
220                r.setFunctionEntry(new RosterFunctionEntry(fn));
221            }
222        }
223        // shunting functions
224        r.setShuntingFunction(functionTableModel.getRosterEntry().getShuntingFunction());
225        // function display order
226        r.deleteAttribute(FunctionTableModel.FB_ORDER_ATTR);
227        String dos = functionTableModel.getVisibilityOrderAttributeString();        
228        r.putAttribute(FunctionTableModel.FB_ORDER_ATTR, dos);        
229    }
230
231    public void dispose() {
232        log.debug("dispose");
233    }
234
235    public boolean includeInPrint() {
236        return print;
237    }
238
239    public void includeInPrint(boolean inc) {
240        print = inc;
241    }
242    boolean print = false;
243
244    public void printPane(HardcopyWriter w) {
245        // if pane is empty, don't print anything
246        // if (varList.size() == 0 && cvList.size() == 0) return;
247        // future work needed here to print indexed CVs
248
249        // Define column widths for name and value output.
250        // Make col 2 slightly larger than col 1 and reduce both to allow for
251        // extra spaces that will be added during concatenation
252        int col1Width = w.getCharactersPerLine() / 2 - 3 - 5;
253        int col2Width = w.getCharactersPerLine() / 2 - 3 + 5;
254
255        try {
256            // Create a string of spaces the width of the first column
257            StringBuilder spaces = new StringBuilder();
258            for (int i = 0; i < col1Width; i++) {
259                spaces.append(" ");
260            }
261            // start with pane name in bold
262            String heading1 = Bundle.getMessage("ColumnHeadingFunction");
263            String heading2 = Bundle.getMessage("ColumnHeadingDescription");
264            String s;
265            int interval = spaces.length() - heading1.length();
266            w.setFont(null, Font.BOLD, null);
267            // write the section name and dividing line
268            s = Bundle.getMessage("HeadingFunctionLabels");
269            w.write(s, 0, s.length());
270            w.writeBorders();
271            //Draw horizontal dividing line for each Pane section
272            w.writeLine(w.getCurrentVPos(), 0, w.getCurrentVPos(), w.getPrintablePagesizePoints().width);
273            s = "\n";
274            w.write(s, 0, s.length());
275
276            w.setFont(null, Font.BOLD + Font.ITALIC, null);
277            s = "   " + heading1 + spaces.substring(0, interval) + "   " + heading2;
278            w.write(s, 0, s.length());
279            w.writeBorders();
280            s = "\n";
281            w.write(s, 0, s.length());
282            w.setFont(null, Font.PLAIN, null);
283
284            // index over variables
285            for (int i = 0; i <= maxfunction; i++) {
286                String name = "" + i;
287                if (functionTableModel.getRosterEntry().getFunctionLockable(i)) {
288                    name = name + " (lockable)";
289                }
290                if (! functionTableModel.getRosterEntry().getFunctionVisible(i)) {
291                    name = name + " (not visible)";
292                }
293                String value = functionTableModel.getRosterEntry().getFunctionLabel(i);
294                //Skip Blank functions
295                if (value != null) {
296
297                    //define index values for name and value substrings
298                    int nameLeftIndex = 0;
299                    int nameRightIndex = name.length();
300                    int valueLeftIndex = 0;
301                    int valueRightIndex = value.length();
302                    String trimmedName;
303                    String trimmedValue;
304
305                    // Check the name length to see if it is wider than the column.
306                    // If so, split it and do the same checks for the Value
307                    // Then concatenate the name and value (or the split versions thereof)
308                    // before writing - if split, repeat until all pieces have been output
309                    while ((valueLeftIndex < value.length()) || (nameLeftIndex < name.length())) {
310                        // name split code
311                        if (name.substring(nameLeftIndex).length() > col1Width) {
312                            for (int j = 0; j < col1Width; j++) {
313                                String delimiter = name.substring(nameLeftIndex + col1Width - j - 1,
314                                        nameLeftIndex + col1Width - j);
315                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
316                                    nameRightIndex = nameLeftIndex + col1Width - j;
317                                    break;
318                                }
319                            }
320                            trimmedName = name.substring(nameLeftIndex, nameRightIndex);
321                            nameLeftIndex = nameRightIndex;
322                            int space = spaces.length() - trimmedName.length();
323                            s = "   " + trimmedName + spaces.substring(0, space);
324                        } else {
325                            trimmedName = name.substring(nameLeftIndex);
326                            int space = spaces.length() - trimmedName.length();
327                            s = "   " + trimmedName + spaces.substring(0, space);
328                            name = "";
329                            nameLeftIndex = 0;
330                        }
331                        // value split code
332                        if (value.substring(valueLeftIndex).length() > col2Width) {
333                            for (int j = 0; j < col2Width; j++) {
334                                String delimiter = value.substring(valueLeftIndex + col2Width - j - 1, valueLeftIndex + col2Width - j);
335                                if (delimiter.equals(" ") || delimiter.equals(";") || delimiter.equals(",")) {
336                                    valueRightIndex = valueLeftIndex + col2Width - j;
337                                    break;
338                                }
339                            }
340                            trimmedValue = value.substring(valueLeftIndex, valueRightIndex);
341                            valueLeftIndex = valueRightIndex;
342                            s = s + "   " + trimmedValue;
343                        } else {
344                            trimmedValue = value.substring(valueLeftIndex);
345                            s = s + "   " + trimmedValue;
346                            valueLeftIndex = 0;
347                            value = "";
348                        }
349                        w.write(s, 0, s.length());
350                        w.writeBorders();
351                        s = "\n";
352                        w.write(s, 0, s.length());
353                    }
354                    // handle special cases
355                }
356            }
357            s = "\n";
358            w.writeBorders();
359            w.write(s, 0, s.length());
360            w.writeBorders();
361            w.write(s, 0, s.length());
362        } catch (IOException e) {
363            log.warn("error during printing", e);
364        }
365
366    }
367
368    private static final Logger log = LoggerFactory.getLogger(FunctionLabelPane.class);
369}