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