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