001package jmri.jmrix.maple.assignment;
002
003import java.awt.BorderLayout;
004import java.awt.Container;
005import java.awt.FlowLayout;
006import java.awt.Font;
007import java.awt.event.ActionEvent;
008import java.awt.event.ActionListener;
009import java.io.IOException;
010import javax.swing.BorderFactory;
011import javax.swing.BoxLayout;
012import javax.swing.ButtonGroup;
013import javax.swing.JButton;
014import javax.swing.JComboBox;
015import javax.swing.JLabel;
016import javax.swing.JPanel;
017import javax.swing.JRadioButton;
018import javax.swing.JScrollPane;
019import javax.swing.JTable;
020import javax.swing.border.Border;
021import javax.swing.table.AbstractTableModel;
022import javax.swing.table.TableColumn;
023import javax.swing.table.TableColumnModel;
024import javax.swing.table.TableModel;
025import jmri.jmrix.maple.InputBits;
026import jmri.jmrix.maple.MapleSystemConnectionMemo;
027import jmri.jmrix.maple.OutputBits;
028import jmri.jmrix.maple.SerialAddress;
029import jmri.jmrix.maple.SerialNode;
030import jmri.util.davidflanagan.HardcopyWriter;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * Frame for running assignment list.
036 *
037 * @author Dave Duchamp Copyright (C) 2006
038 */
039public class ListFrame extends jmri.util.JmriJFrame {
040
041    // configured node information
042    protected int numConfigNodes = 0;
043    protected SerialNode[] configNodes = new SerialNode[128];
044    protected int[] configNodeAddresses = new int[128];
045    protected boolean inputSelected = false;  // true if displaying input assignments, false for output
046    protected SerialNode selNode = null;
047    protected String selNodeID = "x"; // text address of selected Node
048    public int selNodeNum = 0;  // Address (ua) of selected Node
049    public int numBits = 48;  // number of bits in assignment table
050    public int numInputBits = 24;  // number of input bits for selected node
051    public int numOutputBits = 48; // number of output bits for selected node
052
053    // node select pane items
054    JLabel nodeLabel = new JLabel(Bundle.getMessage("NodeBoxLabel") + " ");
055    JComboBox<String> nodeSelBox = new JComboBox<String>();
056    ButtonGroup bitTypeGroup = new ButtonGroup();
057    JRadioButton inputBits = new JRadioButton(Bundle.getMessage("ShowInputButton") + "   ", false);
058    JRadioButton outputBits = new JRadioButton(Bundle.getMessage("ShowOutputButton"), true);
059    JLabel nodeInfoText = new JLabel(Bundle.getMessage("NodeInfoText"));
060
061    // assignment pane items
062    protected JPanel assignmentPanel = null;
063    protected Border inputBorder = BorderFactory.createEtchedBorder();
064    protected Border inputBorderTitled = BorderFactory.createTitledBorder(inputBorder,
065            Bundle.getMessage("AssignmentPanelInputName"));
066    protected Border outputBorder = BorderFactory.createEtchedBorder();
067    protected Border outputBorderTitled = BorderFactory.createTitledBorder(outputBorder,
068            Bundle.getMessage("AssignmentPanelOutputName"));
069    protected JTable assignmentTable = null;
070    protected TableModel assignmentListModel = null;
071
072    // button pane items
073    JButton printButton = new JButton(Bundle.getMessage("PrintButtonText"));
074
075    ListFrame curFrame;
076
077    private MapleSystemConnectionMemo _memo = null;
078
079    public ListFrame(MapleSystemConnectionMemo memo) {
080        super();
081        curFrame = this;
082        _memo = memo;
083    }
084
085    /** 
086     * {@inheritDoc}
087     */
088    @Override
089    public void initComponents() {
090
091        // set the frame's initial state
092        setTitle(Bundle.getMessage("WindowTitle"));
093        setSize(500, 400);
094        Container contentPane = getContentPane();
095        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.Y_AXIS));
096
097        // Set up the node selection panel
098        initializeNodes();
099        nodeSelBox.setEditable(false);
100        if (numConfigNodes > 0) {
101            nodeSelBox.addActionListener(new ActionListener() {
102                @Override
103                public void actionPerformed(ActionEvent event) {
104                    displayNodeInfo((String) nodeSelBox.getSelectedItem());
105                }
106            });
107            inputBits.addActionListener(new ActionListener() {
108                @Override
109                public void actionPerformed(ActionEvent event) {
110                    if (inputSelected == false) {
111                        inputSelected = true;
112                        displayNodeInfo(selNodeID);
113                    }
114                }
115            });
116            outputBits.addActionListener(new ActionListener() {
117                @Override
118                public void actionPerformed(ActionEvent event) {
119                    if (inputSelected == true) {
120                        inputSelected = false;
121                        displayNodeInfo(selNodeID);
122                    }
123                }
124            });
125        } else {
126            nodeInfoText.setText(Bundle.getMessage("NoNodesError"));
127        }
128        nodeSelBox.setToolTipText(Bundle.getMessage("NodeBoxTip"));
129        inputBits.setToolTipText(Bundle.getMessage("ShowInputTip"));
130        outputBits.setToolTipText(Bundle.getMessage("ShowOutputTip"));
131
132        JPanel panel1 = new JPanel();
133        panel1.setLayout(new BoxLayout(panel1, BoxLayout.Y_AXIS));
134        JPanel panel11 = new JPanel();
135        panel11.add(nodeLabel);
136        panel11.add(nodeSelBox);
137        bitTypeGroup.add(outputBits);
138        bitTypeGroup.add(inputBits);
139        panel11.add(inputBits);
140        panel11.add(outputBits);
141        JPanel panel12 = new JPanel();
142        panel12.add(nodeInfoText);
143        panel1.add(panel11);
144        panel1.add(panel12);
145        Border panel1Border = BorderFactory.createEtchedBorder();
146        Border panel1Titled = BorderFactory.createTitledBorder(panel1Border,
147                Bundle.getMessage("NodePanelName"));
148        panel1.setBorder(panel1Titled);
149        contentPane.add(panel1);
150
151        if (numConfigNodes > 0) {
152            // Set up the assignment panel
153            assignmentPanel = new JPanel();
154            assignmentPanel.setLayout(new BoxLayout(assignmentPanel, BoxLayout.Y_AXIS));
155            assignmentListModel = new AssignmentTableModel();
156            assignmentTable = new JTable(assignmentListModel);
157            assignmentTable.setRowSelectionAllowed(false);
158            assignmentTable.setPreferredScrollableViewportSize(new java.awt.Dimension(300, 350));
159            TableColumnModel assignmentColumnModel = assignmentTable.getColumnModel();
160            TableColumn bitColumn = assignmentColumnModel.getColumn(AssignmentTableModel.BIT_COLUMN);
161            bitColumn.setMinWidth(20);
162            bitColumn.setMaxWidth(40);
163            bitColumn.setResizable(true);
164            TableColumn addressColumn = assignmentColumnModel.getColumn(AssignmentTableModel.ADDRESS_COLUMN);
165            addressColumn.setMinWidth(40);
166            addressColumn.setMaxWidth(85);
167            addressColumn.setResizable(true);
168            TableColumn sysColumn = assignmentColumnModel.getColumn(AssignmentTableModel.SYSNAME_COLUMN);
169            sysColumn.setMinWidth(75);
170            sysColumn.setMaxWidth(100);
171            sysColumn.setResizable(true);
172            TableColumn userColumn = assignmentColumnModel.getColumn(AssignmentTableModel.USERNAME_COLUMN);
173            userColumn.setMinWidth(90);
174            userColumn.setMaxWidth(450);
175            userColumn.setResizable(true);
176            JScrollPane assignmentScrollPane = new JScrollPane(assignmentTable);
177            assignmentPanel.add(assignmentScrollPane, BorderLayout.CENTER);
178            if (inputSelected) {
179                assignmentPanel.setBorder(inputBorderTitled);
180            } else {
181                assignmentPanel.setBorder(outputBorderTitled);
182            }
183            contentPane.add(assignmentPanel);
184        }
185
186        // Set up Print button
187        JPanel panel3 = new JPanel();
188        panel3.setLayout(new FlowLayout());
189        printButton.setVisible(true);
190        printButton.setToolTipText(Bundle.getMessage("PrintButtonTip"));
191        if (numConfigNodes > 0) {
192            printButton.addActionListener(new java.awt.event.ActionListener() {
193                @Override
194                public void actionPerformed(java.awt.event.ActionEvent e) {
195                    printButtonActionPerformed(e);
196                }
197            });
198        }
199        panel3.add(printButton);
200        contentPane.add(panel3);
201
202        if (numConfigNodes > 0) {
203            // initialize for the first time
204            displayNodeInfo((String) nodeSelBox.getSelectedItem());
205        }
206
207        addHelpMenu("package.jmri.jmrix.maple.assignment.ListFrame", true);
208
209        // pack for display
210        this.pack();
211    }
212
213    /**
214     * Method to initialize configured nodes and set up the node select combo
215     * box
216     */
217    public void initializeNodes() {
218        String str = "";
219        // clear the arrays
220        for (int i = 0; i < 128; i++) {
221            configNodeAddresses[i] = -1;
222            configNodes[i] = null;
223        }
224        // get all configured nodes
225        SerialNode node = (SerialNode) _memo.getTrafficController().getNode(0);
226        int index = 1;
227        while (node != null) {
228            configNodes[numConfigNodes] = node;
229            configNodeAddresses[numConfigNodes] = node.getNodeAddress();
230            str = Integer.toString(configNodeAddresses[numConfigNodes]);
231            nodeSelBox.addItem(str);
232            if (index == 1) {
233                selNode = node;
234                selNodeNum = configNodeAddresses[numConfigNodes];
235                selNodeID = "y";  // to force first time initialization
236            }
237            numConfigNodes++;
238            // go to next node
239            node = (SerialNode) _memo.getTrafficController().getNode(index);
240            index++;
241        }
242    }
243
244    /**
245     * Handle selection of a Node for info display.
246     * @param nodeID node ID string.
247     */
248    public void displayNodeInfo(String nodeID) {
249        if (!nodeID.equals(selNodeID)) {
250            // The selected node is changing - initialize it
251            int nAdd = Integer.parseInt(nodeID);
252            SerialNode s = null;
253            for (int k = 0; k < numConfigNodes; k++) {
254                if (nAdd == configNodeAddresses[k]) {
255                    s = configNodes[k];
256                }
257            }
258            if (s == null) {
259                // serious trouble, log error and ignore
260                log.error("Cannot find Node {} in list of configured Nodes.", nodeID);
261                return;
262            }
263            // have node, initialize for new node
264            selNodeID = nodeID;
265            selNode = s;
266            selNodeNum = nAdd;
267            // prepare the information line
268            numInputBits = InputBits.getNumInputBits();
269            numOutputBits = OutputBits.getNumOutputBits();
270            nodeInfoText.setText(" - " + Bundle.getMessage("BitsInfoText", numInputBits, numOutputBits));
271        }
272        // initialize for input or output assignments
273        if (inputSelected) {
274            numBits = numInputBits;
275            assignmentPanel.setBorder(inputBorderTitled);
276        } else {
277            numBits = numOutputBits;
278            assignmentPanel.setBorder(outputBorderTitled);
279        }
280        ((AssignmentTableModel) assignmentListModel).fireTableDataChanged();
281    }
282
283    /**
284     * Handle print button in List Frame.
285     * @param e unused.
286     */
287    public void printButtonActionPerformed(java.awt.event.ActionEvent e) {
288        int[] colWidth = new int[4];
289        // initialize column widths
290        TableColumnModel assignmentColumnModel = assignmentTable.getColumnModel();
291        colWidth[0] = assignmentColumnModel.getColumn(AssignmentTableModel.BIT_COLUMN).getWidth();
292        colWidth[1] = assignmentColumnModel.getColumn(AssignmentTableModel.ADDRESS_COLUMN).getWidth();
293        colWidth[2] = assignmentColumnModel.getColumn(AssignmentTableModel.SYSNAME_COLUMN).getWidth();
294        colWidth[3] = assignmentColumnModel.getColumn(AssignmentTableModel.USERNAME_COLUMN).getWidth();
295        // set up a page title
296        String head;
297        if (inputSelected) {
298            head = Bundle.getMessage("AssignmentPanelInputName") + " - "
299                    + Bundle.getMessage("NodeBoxLabel") + " " + selNodeID;
300        } else {
301            head = Bundle.getMessage("AssignmentPanelOutputName") + " - "
302                    + Bundle.getMessage("NodeBoxLabel") + " " + selNodeID;
303        }
304        // initialize a printer writer
305        HardcopyWriter writer = null;
306        try {
307            writer = new HardcopyWriter(curFrame, head, 10, .8, .5, .5, .5, false);
308        } catch (HardcopyWriter.PrintCanceledException ex) {
309            //log.debug("Print cancelled");
310            return;
311        }
312        writer.increaseLineSpacing(20);
313        // print the assignments
314        ((AssignmentTableModel) assignmentListModel).printTable(writer, colWidth);
315    }
316
317    /**
318     * Set up table for displaying bit assignments.
319     */
320    public class AssignmentTableModel extends AbstractTableModel {
321
322        private String free = Bundle.getMessage("AssignmentFree");
323        private int curRow = -1;
324        private String curRowSysName = "";
325
326        /** 
327         * {@inheritDoc}
328         */
329        @Override
330        public String getColumnName(int c) {
331            return assignmentTableColumnNames[c];
332        }
333
334        /** 
335         * {@inheritDoc}
336         */
337        @Override
338        public Class<?> getColumnClass(int c) {
339            return String.class;
340        }
341
342        /** 
343         * {@inheritDoc}
344         */
345        @Override
346        public boolean isCellEditable(int r, int c) {
347            return false;
348        }
349
350        /** 
351         * {@inheritDoc}
352         */
353        @Override
354        public int getColumnCount() {
355            return 4;
356        }
357
358        /** 
359         * {@inheritDoc}
360         */
361        @Override
362        public int getRowCount() {
363            return numBits;
364        }
365
366        /** 
367         * {@inheritDoc}
368         */
369        @Override
370        public Object getValueAt(int r, int c) {
371            if (c == 0) {
372                return Integer.toString(r + 1);
373            } else if (c == 1) {
374                if (inputSelected) {
375                    return Integer.toString(r + 1);
376                } else {
377                    return Integer.toString(r + 1001);
378                }
379            } else if (c == 2) {
380                String sName = null;
381                if (curRow != r) {
382                    if (inputSelected) {
383                        sName = SerialAddress.isInputBitFree((r + 1), _memo.getSystemPrefix());
384                    } else {
385                        sName = SerialAddress.isOutputBitFree((r + 1), _memo.getSystemPrefix());
386                    }
387                    curRow = r;
388                    curRowSysName = sName;
389                } else {
390                    sName = curRowSysName;
391                }
392                if (sName == null) {
393                    return (free);
394                } else {
395                    return sName;
396                }
397            } else if (c == 3) {
398                String sName = null;
399                if (curRow != r) {
400                    if (inputSelected) {
401                        sName = SerialAddress.isInputBitFree((r + 1), _memo.getSystemPrefix());
402                    } else {
403                        sName = SerialAddress.isOutputBitFree((r + 1), _memo.getSystemPrefix());
404                    }
405                    curRow = r;
406                    curRowSysName = sName;
407                } else {
408                    sName = curRowSysName;
409                }
410                if (sName == null) {
411                    return ("");
412                } else {
413                    return (SerialAddress.getUserNameFromSystemName(sName, _memo.getSystemPrefix()));
414                }
415            }
416            return "";
417        }
418
419        @Override
420        public void setValueAt(Object type, int r, int c) {
421            // nothing is stored here
422        }
423
424        public static final int BIT_COLUMN = 0;
425        public static final int ADDRESS_COLUMN = 1;
426        public static final int SYSNAME_COLUMN = 2;
427        public static final int USERNAME_COLUMN = 3;
428
429        /**
430         * Print or print preview the assignment table.
431         * <p>
432         * Printed in proportionately sized columns across the page with 
433         * headings and vertical lines between each column.
434         * Data is word wrapped within a column.
435         * Can only handle 4 columns of data as strings.
436         * <p>
437         * Adapted from routines in BeanTableDataModel.java by
438         * Bob Jacobsen and Dennis Miller
439         * @param w the HardcopyWriter instance.
440         * @param colWidth column width array.
441         */
442        public void printTable(HardcopyWriter w, int colWidth[]) {
443            // determine the column sizes - proportionately sized, with space between for lines
444            int[] columnSize = new int[4];
445            int charPerLine = w.getCharactersPerLine();
446            int tableLineWidth = 0;  // table line width in characters
447            int totalColWidth = 0;
448            for (int j = 0; j < 4; j++) {
449                totalColWidth += colWidth[j];
450            }
451            float ratio = ((float) charPerLine) / ((float) totalColWidth);
452            for (int j = 0; j < 4; j++) {
453                columnSize[j] = ((int) (colWidth[j] * ratio)) - 1;
454                tableLineWidth += (columnSize[j] + 1);
455            }
456
457            // Draw horizontal dividing line
458            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(),
459                    tableLineWidth);
460
461            // print the column header labels
462            String[] columnStrings = new String[4];
463            // Put each column header in the array
464            for (int i = 0; i < 4; i++) {
465                columnStrings[i] = this.getColumnName(i);
466            }
467            w.setFontStyle(Font.BOLD);
468            printColumns(w, columnStrings, columnSize);
469            w.setFontStyle(0);
470            // draw horizontal line
471            w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(),
472                    tableLineWidth);
473
474            // now print each row of data
475            String[] spaces = new String[4];
476            // create base strings the width of each of the columns
477            for (int k = 0; k < 4; k++) {
478                spaces[k] = "";
479                for (int i = 0; i < columnSize[k]; i++) {
480                    spaces[k] = spaces[k] + " ";
481                }
482            }
483            for (int i = 0; i < this.getRowCount(); i++) {
484                for (int j = 0; j < 4; j++) {
485                    //check for special, null contents
486                    if (this.getValueAt(i, j) == null) {
487                        columnStrings[j] = spaces[j];
488                    } else {
489                        columnStrings[j] = (String) this.getValueAt(i, j);
490                    }
491                }
492                printColumns(w, columnStrings, columnSize);
493                // draw horizontal line
494                w.write(w.getCurrentLineNumber(), 0, w.getCurrentLineNumber(),
495                        tableLineWidth);
496            }
497            w.close();
498        }
499
500        protected void printColumns(HardcopyWriter w, String columnStrings[], int columnSize[]) {
501            String columnString = "";
502            StringBuilder lineString = new StringBuilder("");
503            StringBuilder[] spaces = new StringBuilder[4];
504            // create base strings the width of each of the columns
505            for (int k = 0; k < 4; k++) {
506                spaces[k] = new StringBuilder("");
507                for (int i = 0; i < columnSize[k]; i++) {
508                    spaces[k].append(" ");
509                }
510            }
511            // loop through each column
512            boolean complete = false;
513            while (!complete) {
514                complete = true;
515                for (int i = 0; i < 4; i++) {
516                    // if the column string is too wide cut it at word boundary (valid delimiters are space, - and _)
517                    // use the initial part of the text,pad it with spaces and place the remainder back in the array
518                    // for further processing on next line
519                    // if column string isn't too wide, pad it to column width with spaces if needed
520                    if (columnStrings[i].length() > columnSize[i]) {
521                        // this column string will not fit on one line
522                        boolean noWord = true;
523                        for (int k = columnSize[i]; k >= 1; k--) {
524                            if (columnStrings[i].substring(k - 1, k).equals(" ")
525                                    || columnStrings[i].substring(k - 1, k).equals("-")
526                                    || columnStrings[i].substring(k - 1, k).equals("_")) {
527                                columnString = columnStrings[i].substring(0, k)
528                                        + spaces[i].substring(columnStrings[i].substring(0, k).length());
529                                columnStrings[i] = columnStrings[i].substring(k);
530                                noWord = false;
531                                complete = false;
532                                break;
533                            }
534                        }
535                        if (noWord) {
536                            columnString = columnStrings[i].substring(0, columnSize[i]);
537                            columnStrings[i] = columnStrings[i].substring(columnSize[i]);
538                            complete = false;
539                        }
540                    } else {
541                        // this column string will fit on one line
542                        columnString = columnStrings[i] + spaces[i].substring(columnStrings[i].length());
543                        columnStrings[i] = "";
544                    }
545                    lineString.append(columnString).append(" ");
546                }
547                try {
548                    w.write(lineString.toString());
549                    //write vertical dividing lines
550                    int iLine = w.getCurrentLineNumber();
551                    for (int i = 0, k = 0; i < w.getCharactersPerLine(); k++) {
552                        w.write(iLine, i, iLine + 1, i);
553                        if (k < 4) {
554                            i = i + columnSize[k] + 1;
555                        } else {
556                            i = w.getCharactersPerLine();
557                        }
558                    }
559                    w.write("\n"); // NOI18N
560                    lineString = new StringBuilder("");
561                } catch (IOException e) {
562                    log.warn("error during printing:", e);
563                }
564            }
565        }
566    }
567
568    private final String[] assignmentTableColumnNames = {Bundle.getMessage("HeadingBit"),
569            Bundle.getMessage("HeadingAddress"),
570            Bundle.getMessage("HeadingSystemName"),
571            Bundle.getMessage("HeadingUserName")};
572
573    private final static Logger log = LoggerFactory.getLogger(ListFrame.class);
574
575}