001package jmri.jmrix.loconet.slotmon; 002 003import java.awt.event.ActionEvent; 004import java.text.DecimalFormat; 005import java.text.NumberFormat; 006import java.util.ArrayList; 007import java.util.List; 008 009import javax.swing.*; 010import javax.swing.table.DefaultTableCellRenderer; 011import javax.swing.table.TableCellEditor; 012import javax.swing.table.TableCellRenderer; 013import javax.swing.table.TableColumnModel; 014import javax.swing.table.TableRowSorter; 015 016import jmri.InstanceManager; 017import jmri.jmrix.loconet.LnConstants; 018import jmri.jmrix.loconet.LocoNetSlot; 019import jmri.jmrix.loconet.SlotListener; 020import jmri.jmrix.loconet.SlotMapEntry.SlotType; 021import jmri.swing.JmriJTablePersistenceManager; 022import jmri.util.swing.WrapLayout; 023import jmri.util.table.*; 024 025/** 026 * Frame providing a command station slot manager. 027 * <p> 028 * Slots 102 through 127 are normally not used for loco control, so are shown 029 * separately. 030 * 031 * @author Bob Jacobsen Copyright (C) 2001 032 */ 033public class SlotMonPane extends jmri.jmrix.loconet.swing.LnPanel implements SlotListener { 034 035 /** 036 * Controls whether not-in-use slots are shown 037 */ 038 protected final JCheckBox showUnusedCheckBox = new JCheckBox(); 039 /** 040 * Controls whether system slots (0, 121-127) are shown 041 */ 042 protected final JCheckBox showSystemCheckBox = new JCheckBox(); 043 044 private JLabel dcsCSLabel = new JLabel(Bundle.getMessage("SlotMonCSLabel")); 045 private JTextField dcsType = new JTextField(); 046 private JLabel dcsSlotsLabel = new JLabel(Bundle.getMessage("SlotMonTotalSlots")); 047 private JTextField dcsSlots = new JTextField(); 048 049 private final JButton estopAllButton = new JButton(Bundle.getMessage("ButtonSlotMonEStopAll")); 050 051 //Added by Jeffrey Machacek 2013 052 private final JButton clearAllButton = new JButton(Bundle.getMessage("ButtonSlotMonClearAll")); 053 private final JButton refreshAllButton = new JButton(Bundle.getMessage("ButtonSlotRefresh")); 054 055 private JPanel topPanel; // the panel across the top that holds buttons 056 057 private SlotMonDataModel slotModel; 058 private JTable slotTable; 059 private JScrollPane slotScroll; 060 private transient TableRowSorter<SlotMonDataModel> sorter; 061 062 public SlotMonPane() { 063 super(); 064 } 065 066 @Override 067 public void initComponents(jmri.jmrix.loconet.LocoNetSystemConnectionMemo memo) { 068 super.initComponents(memo); 069 int columns = 40; 070 if (memo.getSlotManager().getLoconetProtocol() != LnConstants.LOCONETPROTOCOL_TWO) { 071 columns=20; 072 } 073 slotModel = new SlotMonDataModel(memo.getSlotManager().getNumSlots(), columns, memo); 074 slotTable = new JTable(slotModel); 075 slotTable.setName(this.getTitle()); 076 077 sorter = new TableRowSorter<>(slotModel); 078 slotTable.setRowSorter(sorter); 079 slotScroll = new JScrollPane(slotTable); 080 081 // configure items for GUI 082 showUnusedCheckBox.setText(Bundle.getMessage("TextSlotMonShowUnused")); 083 showUnusedCheckBox.setVisible(true); 084 showUnusedCheckBox.setSelected(false); 085 showUnusedCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowUnused")); 086 087 showSystemCheckBox.setText(Bundle.getMessage("TextSlotMonShowSystem")); 088 showSystemCheckBox.setVisible(true); 089 showSystemCheckBox.setSelected(false); 090 showSystemCheckBox.setToolTipText(Bundle.getMessage("TooltipSlotMonShowSystem")); 091 092 // allow reordering of the columns 093 slotTable.getTableHeader().setReorderingAllowed(true); 094 095 // shut off autoResizeMode to get horizontal scroll to work (JavaSwing p 541) 096 slotTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); 097 098 // resize columns as requested 099 for (int i = 0; i < slotTable.getColumnCount(); i++) { 100 int width = slotModel.getPreferredWidth(i); 101 slotTable.getColumnModel().getColumn(i).setPreferredWidth(width); 102 } 103 slotTable.sizeColumnsToFit(-1); 104 105 InstanceManager.getOptionalDefault(JmriJTablePersistenceManager.class).ifPresent((tpm) -> { 106 // unable to persist because Default class provides no mechanism to 107 // ensure window is destroyed when closed or that existing window is 108 // reused when hidden and user reopens it from menu 109 // tpm.persist(slotTable, true); 110 }); 111 112 // install a button renderer & editor in the "DISP" column for freeing a slot 113 setColumnToHoldButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.DISPCOLUMN)); 114 115 // install a button renderer & editor in the "ESTOP" column for stopping a loco 116 setColumnToHoldEStopButton(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.ESTOPCOLUMN)); 117 118 // Install a numeric format for ConsistAddress 119 setColumnForBlankWhenZero(slotTable, slotTable.convertColumnIndexToView(SlotMonDataModel.CONSISTADDRESS)); 120 // add listener object so checkboxes function 121 122 refreshAllButton.addActionListener((ActionEvent e) -> { 123 slotModel.refreshSlots(); 124 }); 125 126 showUnusedCheckBox.addActionListener((ActionEvent e) -> { 127 filter(); 128 }); 129 showSystemCheckBox.addActionListener((ActionEvent e) -> { 130 filter(); 131 }); 132 133 // add listener object so stop all button functions 134 estopAllButton.addActionListener((ActionEvent e) -> { 135 slotModel.estopAll(); 136 }); 137 138 //Jeffrey 6/29/2013 139 clearAllButton.addActionListener((ActionEvent e) -> { 140 slotModel.clearAllSlots(); 141 }); 142 143 // adjust model to default settings 144 filter(); 145 146 // general GUI config 147 setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 148 149 // install items in GUI 150 topPanel = new JPanel(); 151 topPanel.setLayout(new WrapLayout()); 152 153 topPanel.add(dcsCSLabel); 154 dcsType.setEditable(false); 155 topPanel.add(dcsType); 156 topPanel.add(dcsSlotsLabel); 157 dcsSlots.setEditable(false); 158 topPanel.add(dcsSlots); 159 showHideSlot250Data(false); 160 topPanel.add(refreshAllButton); 161 topPanel.add(showUnusedCheckBox); 162 topPanel.add(showSystemCheckBox); 163 topPanel.add(estopAllButton); 164 topPanel.add(clearAllButton); 165 166 add(topPanel); 167 add(slotScroll); 168 169 memo.getSlotManager().addSlotListener(this); 170 171 // set top panel size 172 if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) { 173 topPanel.setMaximumSize(topPanel.getPreferredSize()); 174 } 175 } 176 177 void setColumnToHoldButton(JTable slotTable, int column) { 178 TableColumnModel tcm = slotTable.getColumnModel(); 179 // install the button renderers & editors in this column 180 ButtonRenderer buttonRenderer = new ButtonRenderer(); 181 tcm.getColumn(column).setCellRenderer(buttonRenderer); 182 // ensure the table rows, columns have enough room for buttons 183 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 184 slotTable.setDefaultEditor(JButton.class, buttonEditor); 185 slotTable.setRowHeight(new JButton(" " + slotModel.getValueAt(1, column)).getPreferredSize().height); 186 slotTable.getColumnModel().getColumn(column) 187 .setPreferredWidth(new JButton(" " + slotModel.getValueAt(1, column)).getPreferredSize().width); 188 } 189 190 /* 191 * Helper class to format number and optionally make blank when zero 192 */ 193 private static class NumberFormatRenderer extends DefaultTableCellRenderer 194 { 195 public NumberFormatRenderer(String pattern, boolean suppressZero) { 196 super(); 197 this.pattern = pattern; 198 this.suppressZero = suppressZero; 199 setHorizontalAlignment(JLabel.RIGHT); 200 } 201 @Override 202 public void setValue(Object value) 203 { 204 try 205 { 206 if (value != null && value instanceof Number) { 207 if (suppressZero && ((Number) value).doubleValue() == 0.0 ) { 208 value = ""; 209 } 210 NumberFormat formatter = new DecimalFormat(pattern); 211 value = formatter.format(value); 212 } 213 } 214 catch(IllegalArgumentException e) {} 215 super.setValue(value); 216 } 217 private String pattern; 218 private boolean suppressZero; 219 } 220 221 void setColumnForBlankWhenZero(JTable slotTable, int column) { 222 TableColumnModel tcm = slotTable.getColumnModel(); 223 TableCellRenderer renderer = new NumberFormatRenderer("####",true); 224 tcm.getColumn(column).setCellRenderer(renderer); 225 } 226 227 void setColumnToHoldEStopButton(JTable slotTable, int column) { 228 TableColumnModel tcm = slotTable.getColumnModel(); 229 // install the button renderers & editors in this column 230 ButtonRenderer buttonRenderer = new ButtonRenderer(); 231 tcm.getColumn(column).setCellRenderer(buttonRenderer); 232 // ensure the table rows, columns have enough room for buttons 233 TableCellEditor buttonEditor = new ButtonEditor(new JButton()); 234 slotTable.setDefaultEditor(JButton.class, buttonEditor); 235 slotTable.setRowHeight(new JButton(" " + slotModel.getValueAt(1, column)).getPreferredSize().height); 236 slotTable.getColumnModel().getColumn(column) 237 .setPreferredWidth(new JButton(" " + slotModel.getValueAt(1, column)).getPreferredSize().width); 238 } 239 240 @Override 241 public String getHelpTarget() { 242 return "package.jmri.jmrix.loconet.slotmon.SlotMonFrame"; // NOI18N 243 } 244 245 @Override 246 public String getTitle() { 247 return getTitle(Bundle.getMessage("MenuItemSlotMonitor")); 248 } 249 250 @Override 251 public void dispose() { 252 slotModel.dispose(); 253 slotModel = null; 254 slotTable = null; 255 slotScroll = null; 256 super.dispose(); 257 } 258 259 private void filter() { 260 RowFilter<SlotMonDataModel, Integer> rf = new RowFilter<SlotMonDataModel, Integer>() { 261 @Override 262 public boolean include(RowFilter.Entry<? extends SlotMonDataModel, ? extends Integer> entry) { 263 // default filter is IN-USE and regular systems slot 264 // the default is whatever the person last closed it with 265 jmri.jmrix.loconet.LocoNetSlot slot = entry.getModel().getSlot(entry.getIdentifier()); 266 boolean include = entry.getModel().getSlot(entry.getIdentifier()).slotStatus() != LnConstants.LOCO_FREE 267 && slot.getSlotType() == SlotType.LOCO; 268 if (slot.getSlotType() == SlotType.UNKNOWN) { 269 return false; // dont ever show unknown 270 } 271 if (!include && showUnusedCheckBox.isSelected() && !slot.isSystemSlot()) { 272 include = true; 273 } 274 if (!include && showSystemCheckBox.isSelected() && slot.isSystemSlot()) { 275 include = true; 276 } 277 return include; 278 } 279 }; 280 sorter.setRowFilter(rf); 281 } 282 283 @Override 284 public List<JMenu> getMenus() { 285 List<JMenu> menuList = new ArrayList<>(); 286 menuList.add(getFileMenu()); 287 return menuList; 288 } 289 290 private JMenu getFileMenu(){ 291 JMenu fileMenu = new JMenu(Bundle.getMessage("MenuFile")); // NOI18N 292 fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvAll")), 293 null, slotModel, "Slot_Monitor_All.csv", new int[]{ 294 SlotMonDataModel.ESTOPCOLUMN})); // NOI18N 295 fileMenu.add(new JTableToCsvAction((Bundle.getMessage("ExportCsvView")), 296 slotTable, slotModel, "Slot_Monitor_View.csv", new int[]{ 297 SlotMonDataModel.ESTOPCOLUMN})); // NOI18N 298 return fileMenu; 299 } 300 301 // methods to communicate with SlotManager 302 @Override 303 public synchronized void notifyChangedSlot(LocoNetSlot s) { 304 // update model from this slot 305 if (s.getSlot() == 250) { 306 if (memo.getSlotManager().getSlot250CSSlots() > 0) { 307 showHideSlot250Data(true); 308 dcsSlots.setText(Integer.toString(memo.getSlotManager().getSlot250CSSlots())); 309 dcsType.setText(memo.getSlotManager().getSlot248CommandStationType()); 310 311 // set scroll size 312 if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) { 313 topPanel.setMaximumSize(topPanel.getPreferredSize()); 314 } 315 topPanel.revalidate(); 316 317 } 318 } 319 } 320 321 void showHideSlot250Data(boolean b) { 322 dcsCSLabel.setVisible(b); 323 dcsSlots.setVisible(b); 324 dcsSlotsLabel.setVisible(b); 325 dcsType.setVisible(b); 326 // set scroll size 327 if (topPanel.getMaximumSize().height > 0 && topPanel.getMaximumSize().width > 0) { 328 topPanel.setMaximumSize(topPanel.getPreferredSize()); 329 } 330 topPanel.revalidate(); 331 } 332 333 334}