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}