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