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 if (line.contains(TrainCommon.TEXT_COLOR_START)) { 133 c = TrainCommon.getTextColor(line); 134 if (line.contains(TrainCommon.TEXT_COLOR_END)) { 135 printingColor = false; 136 } else { 137 // printing multiple lines in color 138 printingColor = true; 139 } 140 // could be a color change when using two column format 141 if (line.contains(Character.toString(VERTICAL_LINE_SEPARATOR))) { 142 String s = line.substring(0, line.indexOf(VERTICAL_LINE_SEPARATOR)); 143 s = TrainCommon.getTextColorString(s); 144 try { 145 writer.write(c, s); // 1st half of line printed 146 } catch (IOException e) { 147 log.debug("Print write color failed"); 148 break; 149 } 150 // get the new color and text 151 line = line.substring(line.indexOf(VERTICAL_LINE_SEPARATOR)); 152 c = TrainCommon.getTextColor(line); 153 // pad out string 154 StringBuffer sb = new StringBuffer(); 155 for (int i = 0; i < s.length(); i++) { 156 sb.append(SPACE); 157 } 158 // 2nd half of line to be printed 159 line = sb.append(TrainCommon.getTextColorString(line)).toString(); 160 } else { 161 // simple case only one color 162 line = TrainCommon.getTextColorString(line); 163 } 164 } else if (line.contains(TrainCommon.TEXT_COLOR_END)) { 165 printingColor = false; 166 line = TrainCommon.getTextColorString(line); 167 } else if (!line.startsWith(TrainCommon.TAB) && !printingColor) { 168 c = null; 169 } 170 for (int i = 0; i < line.length(); i++) { 171 if (line.charAt(i) == VERTICAL_LINE_SEPARATOR) { 172 // make a frame (two column format) 173 if (Setup.isTabEnabled()) { 174 writer.write(writer.getCurrentLineNumber(), 0, writer.getCurrentLineNumber() + 1, 0); 175 writer.write(writer.getCurrentLineNumber(), line.length() + 1, 176 writer.getCurrentLineNumber() + 1, line.length() + 1); 177 } 178 writer.write(writer.getCurrentLineNumber(), i + 1, writer.getCurrentLineNumber() + 1, 179 i + 1); 180 } 181 } 182 line = line.replace(VERTICAL_LINE_SEPARATOR, SPACE); 183 184 if (c != null) { 185 try { 186 writer.write(c, line + NEW_LINE); 187 continue; 188 } catch (IOException e) { 189 log.debug("Print write color failed"); 190 break; 191 } 192 } 193 } 194 try { 195 writer.write(line + NEW_LINE); 196 } catch (IOException e) { 197 log.debug("Print write failed"); 198 break; 199 } 200 } 201 try { 202 in.close(); 203 } catch (IOException e) { 204 log.debug("Could not close in stream"); 205 } 206 207 // and force completion of the printing 208 // close is no longer needed when using the try / catch declaration 209 // writer.close(); 210 } catch (FileNotFoundException e) { 211 log.error("Build file doesn't exist", e); 212 } catch (HardcopyWriter.PrintCanceledException ex) { 213 log.debug("Print cancelled"); 214 } catch (IOException e) { 215 log.warn("Exception printing: {}", e.getLocalizedMessage()); 216 } 217 } 218 219 /** 220 * Creates a new build report file with the print detail numbers replaced by 221 * indentations. Then calls open desktop editor. 222 * 223 * @param file build file 224 * @param name train name 225 */ 226 public static void editReport(File file, String name) { 227 // make a new file with the build report levels removed 228 File buildReport = InstanceManager.getDefault(TrainManagerXml.class) 229 .createTrainBuildReportFile(Bundle.getMessage("Report") + " " + name); 230 editReport(file, buildReport); 231 // open the file 232 TrainUtilities.openDesktop(buildReport); 233 } 234 235 /** 236 * Creates a new build report file with the print detail numbers replaced by 237 * indentations. 238 * 239 * @param file Raw file with detail level numbers 240 * @param fileOut Formated file with indentations 241 */ 242 public static void editReport(File file, File fileOut) { 243 244 try (BufferedReader in = new BufferedReader(new InputStreamReader( 245 new FileInputStream(file), StandardCharsets.UTF_8)); 246 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( 247 new FileOutputStream(fileOut), StandardCharsets.UTF_8)), true);) { 248 249 String line; 250 while (true) { 251 try { 252 line = in.readLine(); 253 if (line == null) { 254 break; 255 } 256 line = filterBuildReport(line, Setup.isBuildReportIndentEnabled()); 257 if (line.isEmpty()) { 258 continue; 259 } 260 out.println(line); // indent lines for each level 261 } catch (IOException e) { 262 log.debug("Print read failed"); 263 break; 264 } 265 } 266 // and force completion of the printing 267 try { 268 in.close(); 269 } catch (IOException e) { 270 log.debug("Close failed"); 271 } 272 out.close(); 273 } catch (FileNotFoundException e) { 274 log.error("Build file doesn't exist: {}", e.getLocalizedMessage()); 275 } catch (IOException e) { 276 log.error("Can not create build report file: {}", e.getLocalizedMessage()); 277 } 278 } 279 280 /* 281 * Removes the print levels from the build report 282 */ 283 private static String filterBuildReport(String line, boolean indent) { 284 String[] inputLine = line.split("\\s+"); // NOI18N 285 if (inputLine.length == 0) { 286 return ""; 287 } 288 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 289 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 290 inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 291 inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 292 293 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_MINIMAL)) { 294 if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 295 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 296 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 297 return ""; // don't print this line 298 } 299 } 300 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 301 if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 302 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 303 return ""; // don't print this line 304 } 305 } 306 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED)) { 307 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 308 return ""; // don't print this line 309 } 310 } 311 // do not indent if false 312 int start = 0; 313 if (indent) { 314 // indent lines based on level 315 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 316 inputLine[0] = " "; 317 } else if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 318 inputLine[0] = " "; 319 } else if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR)) { 320 inputLine[0] = " "; 321 } else if (inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 322 inputLine[0] = ""; 323 } 324 } else { 325 start = 1; 326 } 327 // rebuild line 328 StringBuffer buf = new StringBuffer(); 329 for (int i = start; i < inputLine.length; i++) { 330 buf.append(inputLine[i] + " "); 331 } 332 // blank line? 333 if (buf.length() == 0) { 334 return " "; 335 } 336 return buf.toString(); 337 } else { 338 log.debug("ERROR first characters of build report not valid ({})", line); 339 return "ERROR " + line; // NOI18N 340 } 341 } 342 343 public static JComboBox<String> getPrinterJComboBox() { 344 JComboBox<String> box = new JComboBox<>(); 345 PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); 346 for (PrintService printService : services) { 347 box.addItem(printService.getName()); 348 } 349 350 // Set to default printer 351 box.setSelectedItem(getDefaultPrinterName()); 352 353 return box; 354 } 355 356 public static String getDefaultPrinterName() { 357 if (PrintServiceLookup.lookupDefaultPrintService() != null) { 358 return PrintServiceLookup.lookupDefaultPrintService().getName(); 359 } 360 return ""; // no default printer specified 361 } 362 363 private final static Logger log = LoggerFactory.getLogger(TrainPrintUtilities.class); 364}