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