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 &lt;pane&gt; elements (tabs) from
052     * Programmer (generic) as well as rosterEntry decoder.xml Called from
053     * RosterFrame &gt; 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 &lt;pane&gt;
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}