001package jmri.jmrit.operations.trains; 002 003import java.awt.*; 004import java.io.*; 005import java.nio.charset.StandardCharsets; 006 007import javax.print.PrintService; 008import javax.print.PrintServiceLookup; 009import javax.swing.*; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import jmri.InstanceManager; 015import jmri.jmrit.operations.setup.Setup; 016import jmri.util.davidflanagan.HardcopyWriter; 017 018/** 019 * Train print utilities. Used for train manifests and build reports. 020 * 021 * @author Daniel Boudreau (C) 2010 022 */ 023public class TrainPrintUtilities { 024 025 static final String NEW_LINE = "\n"; // NOI18N 026 static final char HORIZONTAL_LINE_SEPARATOR = '-'; // NOI18N 027 static final char VERTICAL_LINE_SEPARATOR = '|'; // NOI18N 028 static final char SPACE = ' '; 029 030 /** 031 * Print or preview a train manifest, build report, or switch list. 032 * 033 * @param file File to be printed or previewed 034 * @param name Title of document 035 * @param isPreview true if preview 036 * @param fontName optional font to use when printing document 037 * @param isBuildReport true if build report 038 * @param logoURL optional pathname for logo 039 * @param printerName optional default printer name 040 * @param orientation Setup.LANDSCAPE, Setup.PORTRAIT, or Setup.HANDHELD 041 * @param fontSize font size 042 * @param printHeader when true print page header 043 */ 044 public static void printReport(File file, String name, boolean isPreview, String fontName, boolean isBuildReport, 045 String logoURL, String printerName, String orientation, int fontSize, boolean printHeader) { 046 // obtain a HardcopyWriter to do this 047 048 boolean isLandScape = false; 049 double margin = .5; 050 Dimension pagesize = null; // HardcopyWritter provides default page 051 // sizes for portrait and landscape 052 if (orientation.equals(Setup.LANDSCAPE)) { 053 margin = .65; 054 isLandScape = true; 055 } 056 if (orientation.equals(Setup.HANDHELD) || orientation.equals(Setup.HALFPAGE)) { 057 printHeader = false; 058 // add margins to page size 059 pagesize = new Dimension(TrainCommon.getPageSize(orientation).width + TrainCommon.PAPER_MARGINS.width, 060 TrainCommon.getPageSize(orientation).height + TrainCommon.PAPER_MARGINS.height); 061 } 062 try (HardcopyWriter writer = new HardcopyWriter(new Frame(), name, fontSize, margin, 063 margin, .5, .5, isPreview, printerName, isLandScape, printHeader, pagesize); 064 BufferedReader in = new BufferedReader(new InputStreamReader( 065 new FileInputStream(file), StandardCharsets.UTF_8));) { 066 067 // set font 068 if (!fontName.isEmpty()) { 069 writer.setFontName(fontName); 070 } 071 072 // now get the build file to print 073 074 String line; 075 076 if (!isBuildReport && logoURL != null && !logoURL.equals(Setup.NONE)) { 077 ImageIcon icon = new ImageIcon(logoURL); 078 if (icon.getIconWidth() == -1) { 079 log.error("Logo not found: {}", logoURL); 080 } else { 081 writer.write(icon.getImage(), new JLabel(icon)); 082 } 083 } 084 Color c = null; 085 boolean printingColor = false; 086 while (true) { 087 try { 088 line = in.readLine(); 089 } catch (IOException e) { 090 log.debug("Print read failed"); 091 break; 092 } 093 if (line == null) { 094 if (isPreview) { 095 try { 096 writer.write(" "); // need to do this in case the 097 // input 098 // file was empty to create 099 // preview 100 } catch (IOException e) { 101 log.debug("Print write failed for null line"); 102 } 103 } 104 break; 105 } 106 // log.debug("Line: {}", line.toString()); 107 // check for build report print level 108 if (isBuildReport) { 109 line = filterBuildReport(line, false); // no indent 110 if (line.isEmpty()) { 111 continue; 112 } 113 // printing the train manifest 114 } else { 115 // determine if there's a line separator 116 if (line.length() > 0) { 117 boolean horizontialLineSeparatorFound = true; 118 for (int i = 0; i < line.length(); i++) { 119 if (line.charAt(i) != HORIZONTAL_LINE_SEPARATOR) { 120 horizontialLineSeparatorFound = false; 121 break; 122 } 123 } 124 if (horizontialLineSeparatorFound) { 125 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber(), 126 line.length() + 1); 127 c = null; 128 continue; 129 } 130 } 131 132 // determine if line is a pickup or drop 133 if ((!Setup.getPickupEnginePrefix().trim().isEmpty() && 134 line.startsWith(Setup.getPickupEnginePrefix() + TrainCommon.SPACE)) || 135 (!Setup.getPickupCarPrefix().trim().isEmpty() && 136 line.startsWith(Setup.getPickupCarPrefix() + TrainCommon.SPACE)) || 137 (!Setup.getSwitchListPickupCarPrefix().trim().isEmpty() && 138 line.startsWith(Setup.getSwitchListPickupCarPrefix() + TrainCommon.SPACE))) { 139 c = Setup.getPickupColor(); 140 } else if ((!Setup.getDropEnginePrefix().trim().isEmpty() && 141 line.startsWith(Setup.getDropEnginePrefix() + TrainCommon.SPACE)) || 142 (!Setup.getDropCarPrefix().trim().isEmpty() && 143 line.startsWith(Setup.getDropCarPrefix() + TrainCommon.SPACE)) || 144 (!Setup.getSwitchListDropCarPrefix().trim().isEmpty() && 145 line.startsWith(Setup.getSwitchListDropCarPrefix() + TrainCommon.SPACE))) { 146 c = Setup.getDropColor(); 147 } else if ((!Setup.getLocalPrefix().trim().isEmpty() && 148 line.startsWith(Setup.getLocalPrefix() + TrainCommon.SPACE)) || 149 (!Setup.getSwitchListLocalPrefix().trim().isEmpty() && 150 line.startsWith(Setup.getSwitchListLocalPrefix() + TrainCommon.SPACE))) { 151 c = Setup.getLocalColor(); 152 } else if (line.contains(TrainCommon.TEXT_COLOR_START)) { 153 c = TrainCommon.getTextColor(line); 154 if (line.contains(TrainCommon.TEXT_COLOR_END)) { 155 printingColor = false; 156 } else { 157 // printing multiple lines in color 158 printingColor = true; 159 } 160 // could be a color change when using two column format 161 if (line.contains(Character.toString(VERTICAL_LINE_SEPARATOR))) { 162 String s = line.substring(0, line.indexOf(VERTICAL_LINE_SEPARATOR)); 163 s = TrainCommon.getTextColorString(s); 164 try { 165 writer.write(c, s); // 1st half of line printed 166 } catch (IOException e) { 167 log.debug("Print write color failed"); 168 break; 169 } 170 // get the new color and text 171 line = line.substring(line.indexOf(VERTICAL_LINE_SEPARATOR)); 172 c = TrainCommon.getTextColor(line); 173 // pad out string 174 StringBuffer sb = new StringBuffer(); 175 for (int i = 0; i < s.length(); i++) { 176 sb.append(SPACE); 177 } 178 // 2nd half of line to be printed 179 line = sb.append(TrainCommon.getTextColorString(line)).toString(); 180 } else { 181 // simple case only one color 182 line = TrainCommon.getTextColorString(line); 183 } 184 } else if (line.contains(TrainCommon.TEXT_COLOR_END)) { 185 printingColor = false; 186 line = TrainCommon.getTextColorString(line); 187 } else if (!line.startsWith(TrainCommon.TAB) && !printingColor) { 188 c = null; 189 } 190 for (int i = 0; i < line.length(); i++) { 191 if (line.charAt(i) == VERTICAL_LINE_SEPARATOR) { 192 // make a frame (two column format) 193 if (Setup.isTabEnabled()) { 194 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber() + 1, 0); 195 writer.write(writer.getCurrentLineNumber(), line.length() + 1, 196 writer.getCurrentLineNumber() + 1, line.length() + 1); 197 } 198 writer.write(writer.getCurrentLineNumber(), i + 1, writer.getCurrentLineNumber() + 1, 199 i + 1); 200 } 201 } 202 line = line.replace(VERTICAL_LINE_SEPARATOR, SPACE); 203 204 if (c != null) { 205 try { 206 writer.write(c, line + NEW_LINE); 207 continue; 208 } catch (IOException e) { 209 log.debug("Print write color failed"); 210 break; 211 } 212 } 213 } 214 try { 215 writer.write(line + NEW_LINE); 216 } catch (IOException e) { 217 log.debug("Print write failed"); 218 break; 219 } 220 } 221 try { 222 in.close(); 223 } catch (IOException e) { 224 log.debug("Could not close in stream"); 225 } 226 227 // and force completion of the printing 228 // close is no longer needed when using the try / catch declaration 229 // writer.close(); 230 } catch (FileNotFoundException e) { 231 log.error("Build file doesn't exist", e); 232 } catch (HardcopyWriter.PrintCanceledException ex) { 233 log.debug("Print cancelled"); 234 } catch (IOException e) { 235 log.warn("Exception printing: {}", e.getLocalizedMessage()); 236 } 237 } 238 239 /** 240 * Creates a new build report file with the print detail numbers replaced by 241 * indentations. Then calls open desktop editor. 242 * 243 * @param file build file 244 * @param name train name 245 */ 246 public static void editReport(File file, String name) { 247 // make a new file with the build report levels removed 248 File buildReport = InstanceManager.getDefault(TrainManagerXml.class) 249 .createTrainBuildReportFile(Bundle.getMessage("Report") + " " + name); 250 editReport(file, buildReport); 251 // open the file 252 TrainUtilities.openDesktop(buildReport); 253 } 254 255 /** 256 * Creates a new build report file with the print detail numbers replaced by 257 * indentations. 258 * 259 * @param file Raw file with detail level numbers 260 * @param fileOut Formated file with indentations 261 */ 262 public static void editReport(File file, File fileOut) { 263 264 try (BufferedReader in = new BufferedReader(new InputStreamReader( 265 new FileInputStream(file), StandardCharsets.UTF_8)); 266 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( 267 new FileOutputStream(fileOut), StandardCharsets.UTF_8)), true);) { 268 269 String line; 270 while (true) { 271 try { 272 line = in.readLine(); 273 if (line == null) { 274 break; 275 } 276 line = filterBuildReport(line, Setup.isBuildReportIndentEnabled()); 277 if (line.isEmpty()) { 278 continue; 279 } 280 out.println(line); // indent lines for each level 281 } catch (IOException e) { 282 log.debug("Print read failed"); 283 break; 284 } 285 } 286 // and force completion of the printing 287 try { 288 in.close(); 289 } catch (IOException e) { 290 log.debug("Close failed"); 291 } 292 out.close(); 293 } catch (FileNotFoundException e) { 294 log.error("Build file doesn't exist: {}", e.getLocalizedMessage()); 295 } catch (IOException e) { 296 log.error("Can not create build report file: {}", e.getLocalizedMessage()); 297 } 298 } 299 300 /* 301 * Removes the print levels from the build report 302 */ 303 private static String filterBuildReport(String line, boolean indent) { 304 String[] inputLine = line.split("\\s+"); // NOI18N 305 if (inputLine.length == 0) { 306 return ""; 307 } 308 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 309 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 310 inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 311 inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 312 313 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_MINIMAL)) { 314 if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 315 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 316 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 317 return ""; // don't print this line 318 } 319 } 320 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 321 if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 322 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 323 return ""; // don't print this line 324 } 325 } 326 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED)) { 327 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 328 return ""; // don't print this line 329 } 330 } 331 // do not indent if false 332 int start = 0; 333 if (indent) { 334 // indent lines based on level 335 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 336 inputLine[0] = " "; 337 } else if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 338 inputLine[0] = " "; 339 } else if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR)) { 340 inputLine[0] = " "; 341 } else if (inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 342 inputLine[0] = ""; 343 } 344 } else { 345 start = 1; 346 } 347 // rebuild line 348 StringBuffer buf = new StringBuffer(); 349 for (int i = start; i < inputLine.length; i++) { 350 buf.append(inputLine[i] + " "); 351 } 352 // blank line? 353 if (buf.length() == 0) { 354 return " "; 355 } 356 return buf.toString(); 357 } else { 358 log.debug("ERROR first characters of build report not valid ({})", line); 359 return "ERROR " + line; // NOI18N 360 } 361 } 362 363 public static JComboBox<String> getPrinterJComboBox() { 364 JComboBox<String> box = new JComboBox<>(); 365 PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); 366 for (PrintService printService : services) { 367 box.addItem(printService.getName()); 368 } 369 370 // Set to default printer 371 box.setSelectedItem(getDefaultPrinterName()); 372 373 return box; 374 } 375 376 public static String getDefaultPrinterName() { 377 if (PrintServiceLookup.lookupDefaultPrintService() != null) { 378 return PrintServiceLookup.lookupDefaultPrintService().getName(); 379 } 380 return ""; // no default printer specified 381 } 382 383 private final static Logger log = LoggerFactory.getLogger(TrainPrintUtilities.class); 384}