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 255 try (BufferedReader in = new BufferedReader(new InputStreamReader( 256 new FileInputStream(file), StandardCharsets.UTF_8)); 257 PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( 258 new FileOutputStream(buildReport), StandardCharsets.UTF_8)), true);) { 259 260 String line; 261 while (true) { 262 try { 263 line = in.readLine(); 264 if (line == null) { 265 break; 266 } 267 line = filterBuildReport(line, Setup.isBuildReportIndentEnabled()); 268 if (line.isEmpty()) { 269 continue; 270 } 271 out.println(line); // indent lines for each level 272 } catch (IOException e) { 273 log.debug("Print read failed"); 274 break; 275 } 276 } 277 // and force completion of the printing 278 try { 279 in.close(); 280 } catch (IOException e) { 281 log.debug("Close failed"); 282 } 283 out.close(); 284 // open the file 285 TrainUtilities.openDesktop(buildReport); 286 } catch (FileNotFoundException e) { 287 log.error("Build file doesn't exist", e); 288 } catch (IOException e) { 289 log.error("Can not create build report file", e); 290 } 291 } 292 293 /* 294 * Removes the print levels from the build report 295 */ 296 private static String filterBuildReport(String line, boolean indent) { 297 String[] inputLine = line.split("\\s+"); // NOI18N 298 if (inputLine.length == 0) { 299 return ""; 300 } 301 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 302 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 303 inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 304 inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 305 306 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_MINIMAL)) { 307 if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR) || 308 inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 309 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 310 return ""; // don't print this line 311 } 312 } 313 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_NORMAL)) { 314 if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR) || 315 inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 316 return ""; // don't print this line 317 } 318 } 319 if (Setup.getBuildReportLevel().equals(Setup.BUILD_REPORT_DETAILED)) { 320 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 321 return ""; // don't print this line 322 } 323 } 324 // do not indent if false 325 int start = 0; 326 if (indent) { 327 // indent lines based on level 328 if (inputLine[0].equals(Setup.BUILD_REPORT_VERY_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 329 inputLine[0] = " "; 330 } else if (inputLine[0].equals(Setup.BUILD_REPORT_DETAILED + TrainCommon.BUILD_REPORT_CHAR)) { 331 inputLine[0] = " "; 332 } else if (inputLine[0].equals(Setup.BUILD_REPORT_NORMAL + TrainCommon.BUILD_REPORT_CHAR)) { 333 inputLine[0] = " "; 334 } else if (inputLine[0].equals(Setup.BUILD_REPORT_MINIMAL + TrainCommon.BUILD_REPORT_CHAR)) { 335 inputLine[0] = ""; 336 } 337 } else { 338 start = 1; 339 } 340 // rebuild line 341 StringBuffer buf = new StringBuffer(); 342 for (int i = start; i < inputLine.length; i++) { 343 buf.append(inputLine[i] + " "); 344 } 345 // blank line? 346 if (buf.length() == 0) { 347 return " "; 348 } 349 return buf.toString(); 350 } else { 351 log.debug("ERROR first characters of build report not valid ({})", line); 352 return "ERROR " + line; // NOI18N 353 } 354 } 355 356 public static JComboBox<String> getPrinterJComboBox() { 357 JComboBox<String> box = new JComboBox<>(); 358 PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null); 359 for (PrintService printService : services) { 360 box.addItem(printService.getName()); 361 } 362 363 // Set to default printer 364 box.setSelectedItem(getDefaultPrinterName()); 365 366 return box; 367 } 368 369 public static String getDefaultPrinterName() { 370 if (PrintServiceLookup.lookupDefaultPrintService() != null) { 371 return PrintServiceLookup.lookupDefaultPrintService().getName(); 372 } 373 return ""; // no default printer specified 374 } 375 376 private final static Logger log = LoggerFactory.getLogger(TrainPrintUtilities.class); 377}