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