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