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