001package jmri.jmrit.roster; 002 003import java.awt.*; 004import java.io.IOException; 005import java.util.ArrayList; 006import java.util.Enumeration; 007import java.util.Hashtable; 008import java.util.List; 009import javax.swing.BorderFactory; 010import javax.swing.BoxLayout; 011import javax.swing.ImageIcon; 012import javax.swing.JButton; 013import javax.swing.JCheckBox; 014import javax.swing.JFrame; 015import javax.swing.JLabel; 016import javax.swing.JPanel; 017import javax.swing.border.EmptyBorder; 018 019import jmri.InstanceManager; 020import jmri.jmrit.XmlFile; 021import jmri.jmrit.decoderdefn.DecoderFile; 022import jmri.jmrit.decoderdefn.DecoderIndexFile; 023import jmri.jmrit.symbolicprog.CvTableModel; 024import jmri.jmrit.symbolicprog.ResetTableModel; 025import jmri.jmrit.symbolicprog.VariableTableModel; 026import jmri.jmrit.symbolicprog.tabbedframe.PaneContainer; 027import jmri.jmrit.symbolicprog.tabbedframe.PaneProgFrame; 028import jmri.jmrit.symbolicprog.tabbedframe.PaneProgPane; 029import jmri.util.BusyGlassPane; 030import jmri.util.FileUtil; 031import jmri.util.JmriJFrame; 032import jmri.util.davidflanagan.HardcopyWriter; 033import org.jdom2.*; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037public class PrintRosterEntry implements PaneContainer { 038 039 RosterEntry _rosterEntry; 040 041 /** 042 * List of {@link jmri.jmrit.symbolicprog.tabbedframe.PaneProgPane} JPanels. 043 * Built up at line 150 or passed as argument paneList in line 188 via 044 * {link #PrintRosterEntry(RosterEntry, List, FunctionLabelPane, RosterMediaPane, JmriJFrame)} 045 */ 046 List<JPanel> _paneList = new ArrayList<>(); 047 FunctionLabelPane _flPane; 048 RosterMediaPane _rMPane; 049 JmriJFrame _parent; 050 051 /** 052 * Constructor for a Print roster item (programmer tabs) selection pane from an XML definition file. 053 * Includes <pane> elements (tabs) from Programmer (generic) as well as rosterEntry decoder.xml 054 * Called from RosterFrame > PreviewAll context menu. 055 * 056 * @param rosterEntry Roster item, either as a selection or object 057 * @param parent window over which this dialog will be centered 058 * @param programmerFilename xml file name for programmer used in printing. 059 */ 060 public PrintRosterEntry(RosterEntry rosterEntry, JmriJFrame parent, String programmerFilename) { 061 _rosterEntry = rosterEntry; 062 _flPane = new FunctionLabelPane(rosterEntry); 063 _rMPane = new RosterMediaPane(rosterEntry); 064 _parent = parent; 065 JLabel progStatus = new JLabel(Bundle.getMessage("StateIdle")); 066 ResetTableModel resetModel = new ResetTableModel(progStatus, null); // no programmer 067 068 log.debug("Try PrintRosterEntry {} from file {}", _rosterEntry.getDisplayName(), programmerFilename); 069 XmlFile pf = new XmlFile() { 070 }; 071 Element programmerRoot; 072 Element programmerBase; // base of programmer file pane elements 073 074 try { 075 programmerRoot = pf.rootFromName(programmerFilename); 076 if (programmerRoot == null) { 077 log.error("Programmer file name incorrect {}", programmerFilename); 078 return; 079 } 080 if ((programmerBase = programmerRoot.getChild("programmer")) == null) { 081 log.error("xml file top element is not 'programmer'"); 082 return; 083 } 084 log.debug("Success: xml file top element is 'programmer'"); 085 } catch (JDOMException | java.io.IOException e) { 086 log.error("exception reading programmer file {}", programmerFilename, e); 087 return; 088 } 089 090 CvTableModel cvModel = new CvTableModel(progStatus, null); // no programmer 091 092 VariableTableModel variableModel = new VariableTableModel(progStatus, new String[] {"Name", "Value"}, cvModel); // NOI18N 093 094 String decoderModel = _rosterEntry.getDecoderModel(); 095 String decoderFamily = _rosterEntry.getDecoderFamily(); 096 097 log.debug("selected loco uses decoder {} {}", decoderFamily, decoderModel); 098 // locate a decoder like that 099 List<DecoderFile> l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, decoderFamily, null, null, null, decoderModel); 100 log.debug("found {} matches", l.size()); 101 if (l.isEmpty()) { 102 log.debug("Loco uses {} {} decoder, but no such decoder defined", decoderFamily, decoderModel); 103 // fall back to use just the decoder name, not family 104 l = InstanceManager.getDefault(DecoderIndexFile.class).matchingDecoderList(null, null, null, null, null, decoderModel); 105 log.debug("found {} matches without family key", l.size()); 106 } 107 DecoderFile decoderFile = null; 108 if (l.size() > 0) { 109 decoderFile = l.get(0); 110 } else { 111 if (decoderModel.equals("")) { 112 log.debug("blank decoderModel requested, so nothing loaded"); 113 } else { 114 log.warn("no matching \"{}\" decoder found for loco, no decoder info loaded", decoderModel); 115 } 116 } 117 118 if (decoderFile == null) { 119 log.warn("no decoder file found for this loco"); 120 return; 121 } 122 // save the pointer to the model element to check for include/exclude before adding to paneList 123 Element modelElem = decoderFile.getModelElement(); 124 125 Element decoderRoot; 126 log.debug("Try to read decoder root from {} {}", DecoderFile.fileLocation, decoderFile.getFileName()); 127 128 try { 129 decoderRoot = decoderFile.rootFromName(DecoderFile.fileLocation + decoderFile.getFileName()); 130 if ((decoderRoot.getChild("decoder")) == null) { 131 log.error("xml file top element is not 'decoder'"); 132 return; 133 } 134 } catch (org.jdom2.JDOMException exj) { 135 log.error("could not parse {}: {}", decoderFile.getFileName(), exj.getMessage()); 136 return; 137 } catch (java.io.IOException exj) { 138 log.error("could not read {}: {}", decoderFile.getFileName(), exj.getMessage()); 139 return; 140 } 141 142 // load defaults 143 decoderFile.loadVariableModel(decoderRoot.getChild("decoder"), variableModel); 144 decoderFile.loadResetModel(decoderRoot.getChild("decoder"), resetModel); 145 146 // load the specific contents for this entry from rosterEntry file 147 rosterEntry.readFile(); 148 rosterEntry.loadCvModel(variableModel, cvModel); 149 150 // add pane names from programmer 151 List<Element> rawPaneList = programmerBase.getChildren("pane"); 152 log.debug("rawPaneList P size = {}", rawPaneList.size()); 153 for (Element elPane : rawPaneList) { 154 // load each pane to store in _paneList and fetch its name element (i18n) to show in Select items pane 155 Element _name = elPane.getChild("name"); // multiple languages 156 // There is no name attribute of pane in Basic.xml nor (for the Comprehensive programmer) in include.parts.Basic.xml 157 // Instead, it's a separate element inside programmer.pane, fixed 4.7.2 158 String name = "Tab name"; // temporary name 159 if (_name != null) { 160 name = _name.getText(); // NOI18N 161 log.debug("Tab '{}' added from Programmer", name); 162 } else { 163 log.debug("Did not find name element in pane"); 164 } 165 // include/exclude check N/A for prag panes 166 PaneProgPane p = new PaneProgPane(this, name, elPane, cvModel, variableModel, modelElem, _rosterEntry); 167 _paneList.add(p); 168 } 169 170 // compare to PaneProgFrame#loadProgrammerFile(pRosterEntry) 171 // add pane names from programmer 172 rawPaneList = decoderRoot.getChildren("pane"); 173 log.debug("rawPaneList D size = {}", rawPaneList.size()); 174 for (Element elPane : rawPaneList) { 175 // load each pane to store in _paneList and fetch its name element (i18n) to show in Select items pane 176 Element _name = elPane.getChild("name"); // multiple languages 177 // There is no name attribute of pane in Basic.xml nor (for the Comprehensive programmer) in include.parts.Basic.xml 178 // Instead, it's a separate element inside programmer.pane, fixed 4.7.2 179 String name = "Tab name"; // temporary name NOI18N 180 if (_name != null) { 181 name = _name.getText(); // NOI18N 182 log.debug("Tab '{}' added from Decoder", name); 183 } else { 184 log.debug("Did not find name element in pane"); 185 } 186 PaneProgPane p; 187 if (PaneProgFrame.isIncludedFE(elPane, modelElem, _rosterEntry, "", "")) { 188 p = new PaneProgPane(this, name, elPane, cvModel, variableModel, modelElem, _rosterEntry); 189 _paneList.add(p); // possible duplicates with prog pane titles handled by list 190 } 191 } 192 // check for empty panes and I18N happens in #printPanes(boolean) 193 } 194 195 @Override 196 public BusyGlassPane getBusyGlassPane() { 197 return null; 198 } 199 200 @Override 201 public void prepGlassPane(javax.swing.AbstractButton activeButton) { 202 } 203 204 @Override 205 public void enableButtons(boolean enable) { 206 } 207 208 @Override 209 public void paneFinished() { 210 } 211 212 @Override 213 public boolean isBusy() { 214 return false; 215 } 216 217 /** 218 * Configure variable fields and create a PrintRosterEntry instance while doing so. 219 * Includes all (visible) Roster Entry programmer <pane> elements (tabs). 220 * 221 * @param rosterEntry an item in the Roster 222 * @param paneList list of programmer tabs, including all properties 223 * @param flPane extra pane w/checkbox to select printing of "Function List" 224 * @param rMPane pane containing roster media (image) 225 * @param parent window over which this dialog will be centered 226 */ 227 public PrintRosterEntry(RosterEntry rosterEntry, List<JPanel> paneList, FunctionLabelPane flPane, RosterMediaPane rMPane, JmriJFrame parent) { 228 _rosterEntry = rosterEntry; 229 _paneList = paneList; 230 _flPane = flPane; 231 _rMPane = rMPane; 232 _parent = parent; 233 log.debug("New PrintRosterEntry including a paneList of size {}", paneList.size()); 234 } 235 236 /** 237 * Write a series of 'pages' to graphic output using HardcopyWriter. 238 * 239 * @param preview true if output should go to the Preview panel, false to output to a printer 240 */ 241 public void doPrintPanes(boolean preview) { 242 HardcopyWriter w; 243 try { 244 w = new HardcopyWriter(_parent, _rosterEntry.getId(), 10, .8, .5, .5, .5, preview); 245 } catch (HardcopyWriter.PrintCanceledException ex) { 246 log.debug("Print cancelled"); 247 return; 248 } 249 printInfoSection(w); 250 251 if (_flPane.includeInPrint()) { 252 _flPane.printPane(w); 253 } 254 log.debug("List size length: {}", _paneList.size()); 255 for (int i = 0; i < _paneList.size(); i++) { 256 log.debug("start printing page {}", i + 1); 257 PaneProgPane pane = (PaneProgPane) _paneList.get(i); 258 if (pane.includeInPrint()) { 259 pane.printPane(w); // takes care of all I18N 260 } 261 } 262 w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(), w.getCharactersPerLine() + 1); 263 w.close(); 264 } 265 266 /** 267 * Create and display a pane to the user to select which Programmer tabs to include in printout. 268 * 269 * @param preview true if output should go to a Preview pane on screen, false to output to a printer (dialog) 270 */ 271 public void printPanes(final boolean preview) { 272 final JFrame frame = new JFrame(Bundle.getMessage("TitleSelectItemsToPrint")); 273 JPanel p1 = new JPanel(); 274 p1.setBorder(new EmptyBorder(5, 5, 5, 5)); 275 p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS)); 276 277 JPanel instruct = new JPanel(); 278 instruct.setLayout(new BoxLayout(instruct, BoxLayout.PAGE_AXIS)); 279 JLabel l1 = new JLabel(Bundle.getMessage("LabelSelectLine1")); 280 instruct.add(l1); 281 l1 = new JLabel(Bundle.getMessage("LabelSelectLine2")); 282 instruct.add(l1); 283 p1.add(instruct); 284 285 JPanel select = new JPanel(); 286 select.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("ItemsLabel"))); 287 // add checkboxes for all items 288 final Hashtable<JCheckBox, PaneProgPane> printList = new Hashtable<>(); 289 select.setLayout(new BoxLayout(select, BoxLayout.PAGE_AXIS)); 290 final JCheckBox funct = new JCheckBox(Bundle.getMessage("LabelFunctionList")); 291 funct.addActionListener(evt -> _flPane.includeInPrint(funct.isSelected())); 292 _flPane.includeInPrint(false); 293 select.add(funct); 294 295 log.debug("_paneList size length: {}", _paneList.size()); 296 for (JPanel jPanel : _paneList) { 297 log.debug("printPanes === checking tab {}...", jPanel.getName()); 298 if (jPanel instanceof PaneProgPane && !((PaneProgPane) jPanel).isEmpty()) { 299 // add a checkbox to the Preview All pane for each tab (unless empty) 300 // skip tab if empty (won't show up on printout anyway) 301 log.debug("tab {} not empty, adding", jPanel.getName()); 302 final PaneProgPane pane = (PaneProgPane) jPanel; 303 pane.includeInPrint(false); 304 final JCheckBox item = new JCheckBox(jPanel.getName()); 305 // Tab names _paneList.get(i).getName() show up when called from RosterFrame 306 // (are entered in line 147) 307 printList.put(item, pane); 308 item.addActionListener(evt -> pane.includeInPrint(item.isSelected())); 309 select.add(item); 310 } 311 } 312 p1.add(select); 313 314 // Add "Select All" checkbox below titled set of item boxes 315 JPanel selectAllBox = new JPanel(); 316 final JCheckBox selectAll = new JCheckBox(Bundle.getMessage("SelectAll")); 317 selectAll.addActionListener(evt -> { 318 _flPane.includeInPrint(selectAll.isSelected()); 319 funct.setSelected(selectAll.isSelected()); 320 Enumeration<JCheckBox> en = printList.keys(); 321 while (en.hasMoreElements()) { 322 JCheckBox check = en.nextElement(); 323 printList.get(check).includeInPrint(selectAll.isSelected()); 324 check.setSelected(selectAll.isSelected()); 325 } 326 }); 327 selectAllBox.add(selectAll); 328 p1.add(selectAllBox); 329 330 JButton cancel = new JButton(Bundle.getMessage("ButtonCancel")); 331 JButton ok = new JButton(Bundle.getMessage("ButtonOK")); 332 cancel.addActionListener(evt -> frame.dispose()); 333 ok.addActionListener(evt -> { 334 doPrintPanes(preview); 335 frame.dispose(); 336 }); 337 JPanel buttons = new JPanel(); 338 buttons.add(cancel); 339 buttons.add(ok); 340 p1.add(buttons); 341 342 frame.add(p1); 343 frame.pack(); 344 frame.setVisible(true); 345 } 346 347 /** 348 * Write the page header to graphic output, using HardcopyWriter w. 349 * <p> 350 * Includes the DecoderPro logo image at top right. 351 * 352 * @param w the active HardcopyWriter instance to be used 353 */ 354 public void printInfoSection(HardcopyWriter w) { 355 ImageIcon icon = new ImageIcon(FileUtil.findURL("resources/decoderpro.gif", FileUtil.Location.INSTALLED)); 356 // we use an ImageIcon because it's guaranteed to have been loaded when ctor is complete 357 w.write(icon.getImage(), new JLabel(icon)); 358 w.setFontStyle(Font.BOLD); 359 // add a number of blank lines 360 int height = icon.getImage().getHeight(null); 361 int blanks = (height - w.getLineAscent()) / w.getLineHeight(); 362 363 try { 364 for (int i = 0; i < blanks; i++) { 365 String s = "\n"; 366 w.write(s, 0, s.length()); 367 } 368 } catch (IOException e) { 369 log.warn("error during printing: ", e); 370 } 371 _rosterEntry.printEntry(w); 372 w.setFontStyle(Font.PLAIN); 373 } 374 375 private final static Logger log = LoggerFactory.getLogger(PrintRosterEntry.class); 376 377}