001package jmri.jmrit.cabsignals; 002 003import java.awt.BorderLayout; 004import java.awt.Component; 005import java.awt.Dimension; 006import java.awt.event.ActionEvent; 007import java.awt.event.ActionListener; 008import java.awt.event.KeyEvent; 009import java.awt.event.KeyListener; 010import java.beans.PropertyChangeEvent; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.List; 014 015import javax.swing.*; 016import javax.swing.event.ChangeEvent; 017import javax.swing.table.TableCellRenderer; 018import javax.swing.table.TableColumn; 019import javax.swing.table.TableRowSorter; 020import javax.swing.text.DefaultFormatter; 021 022import jmri.LocoAddress; 023import jmri.CabSignalListListener; 024import jmri.CabSignalManager; 025import jmri.jmrit.catalog.NamedIcon; 026import jmri.jmrit.DccLocoAddressSelector; 027import jmri.jmrit.roster.swing.GlobalRosterEntryComboBox; 028import jmri.jmrit.roster.swing.RosterEntryComboBox; 029import jmri.util.swing.XTableColumnModel; 030import jmri.util.swing.StayOpenCheckBoxItem; 031import jmri.util.swing.JmriJOptionPane; 032import jmri.util.table.ButtonEditor; 033import jmri.util.table.ButtonRenderer; 034import jmri.util.table.JTableWithColumnToolTips; 035 036/** 037 * Pane for sending Cab Signal data via block lookup 038 * @author Steve Young Copyright (C) 2018 039 * @author Paul Bender Copyright (C) 2019 040 * @see CabSignalTableModel 041 * @since 4.13.4 042 */ 043public class CabSignalPane extends jmri.util.swing.JmriPanel implements CabSignalListListener { 044 045 private CabSignalManager cabSignalManager; 046 047 private JScrollPane slotScroll; 048 private CabSignalTableModel slotModel; 049 private JTable _slotTable; 050 private XTableColumnModel tcm; 051 052 private JMenu cabSigColMenu; 053 private List<JCheckBoxMenuItem> colMenuList; 054 private JToggleButton masterPauseButton; 055 private JLabel textLocoLabel; 056 private DccLocoAddressSelector locoSelector; 057 private RosterEntryComboBox locoRosterBox; 058 private JButton addLocoButton; 059 private JButton resetLocoButton; 060 private int _rotationOffset; 061 private int _defaultRowHeight; 062 063 public CabSignalPane() { 064 super(); 065 cabSignalManager = jmri.InstanceManager.getNullableDefault(CabSignalManager.class); 066 if(cabSignalManager == null){ 067 log.info("creating new DefaultCabSignalManager"); 068 jmri.InstanceManager.store(new jmri.managers.DefaultCabSignalManager(),CabSignalManager.class); 069 cabSignalManager = jmri.InstanceManager.getNullableDefault(CabSignalManager.class); 070 } 071 } 072 073 /** 074 * {@inheritDoc} 075 */ 076 @Override 077 public void initComponents() { 078 super.initComponents(); 079 if (cabSignalManager != null) { 080 cabSignalManager.addCabSignalListListener(this); 081 } 082 slotModel = new CabSignalTableModel(5, 083 CabSignalTableModel.MAX_COLUMN); // row, column 084 085 tcm = new XTableColumnModel(); 086 cabSigColMenu = new JMenu(Bundle.getMessage("SigDataCol")); 087 colMenuList = new ArrayList<>(); 088 textLocoLabel = new JLabel(); 089 locoSelector = new DccLocoAddressSelector(); 090 addLocoButton = new JButton(); 091 resetLocoButton = new JButton(); 092 _defaultRowHeight = 26; 093 init(); 094 } 095 096 public void init() { 097 _slotTable = new JTableWithColumnToolTips(slotModel,CabSignalTableModel.COLUMNTOOLTIPS); 098 099 // Use XTableColumnModel so we can control which columns are visible 100 _slotTable.setColumnModel(tcm); 101 _slotTable.createDefaultColumnsFromModel(); 102 103 for (int i = 0; i < _slotTable.getColumnCount(); i++) { 104 int colnumber=i; 105 String colName = _slotTable.getColumnName(colnumber); 106 StayOpenCheckBoxItem showcol = new StayOpenCheckBoxItem(colName); 107 showcol.setToolTipText(CabSignalTableModel.COLUMNTOOLTIPS[i]); 108 colMenuList.add(showcol); 109 cabSigColMenu.add(showcol); // cabsig columns 110 } 111 112 for (int i = 0; i < CabSignalTableModel.MAX_COLUMN; i++) { 113 int colnumber=i; 114 TableColumn column = tcm.getColumnByModelIndex(colnumber); 115 116 if (Arrays.stream(CabSignalTableModel.STARTUPCOLUMNS).anyMatch(j -> j == colnumber)) { 117 colMenuList.get(colnumber).setSelected(true); 118 tcm.setColumnVisible(column, true); 119 } else { 120 colMenuList.get(colnumber).setSelected(false); 121 tcm.setColumnVisible(column, false); 122 } 123 124 colMenuList.get(colnumber).addActionListener((ActionEvent e) -> { 125 TableColumn column1 = tcm.getColumnByModelIndex(colnumber); 126 boolean visible1 = tcm.isColumnVisible(column1); 127 tcm.setColumnVisible(column1, !visible1); 128 }); 129 } 130 131 _slotTable.setAutoCreateRowSorter(true); 132 133 final TableRowSorter<CabSignalTableModel> sorter = new TableRowSorter<>(slotModel); 134 _slotTable.setRowSorter(sorter); 135 136 _slotTable.setRowHeight(_defaultRowHeight); 137 138 // configure items for GUI 139 slotModel.configureTable(_slotTable); 140 141 tcm.getColumnByModelIndex(CabSignalTableModel.REVERSE_BLOCK_DIR_BUTTON_COLUMN).setCellRenderer( 142 new ButtonRenderer() ); 143 tcm.getColumnByModelIndex(CabSignalTableModel.REVERSE_BLOCK_DIR_BUTTON_COLUMN).setCellEditor( 144 new ButtonEditor( new JButton() ) ); 145 146 tcm.getColumnByModelIndex(CabSignalTableModel.NEXT_ASPECT_ICON).setCellRenderer( 147 tableSignalAspectRenderer() ); 148 149 slotScroll = new JScrollPane(_slotTable); 150 slotScroll.setPreferredSize(new Dimension(400, 200)); 151 152 this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); 153 154 // add event displays 155 JPanel p1 = new JPanel(); 156 p1.setLayout(new BorderLayout()); 157 158 JPanel toppanelcontainer = new JPanel(); 159 // toppanelcontainer.setLayout(new BoxLayout(toppanelcontainer, BoxLayout.X_AXIS)); 160 161 masterPauseButton= new JToggleButton(); 162 masterPauseButton.setSelected(false); // cabdata on 163 refreshMasterPauseButton(); 164 masterPauseButton.setVisible(true); 165 masterPauseButton.addActionListener ((ActionEvent e) -> { 166 refreshMasterPauseButton(); 167 }); 168 169 toppanelcontainer.add(masterPauseButton); 170 171 JPanel locoSelectContainer = new JPanel(); 172 173 textLocoLabel.setText(Bundle.getMessage("LocoLabelText")); 174 textLocoLabel.setVisible(true); 175 176 locoSelector.setToolTipText(Bundle.getMessage("LocoSelectorToolTip")); 177 locoSelector.setVisible(true); 178 textLocoLabel.setLabelFor(locoSelector); 179 180 locoSelectContainer.add(textLocoLabel); 181 locoSelectContainer.add(locoSelector); 182 183 locoSelector.addKeyListener(new KeyListener() { 184 @Override 185 public void keyPressed(KeyEvent e) { 186 // if we start typing, set the selected index of the locoRosterbox to nothing. 187 locoRosterBox.setSelectedIndex(0); 188 } 189 190 @Override 191 public void keyTyped(KeyEvent e) { 192 } 193 194 @Override 195 public void keyReleased(KeyEvent e) { 196 } 197 }); 198 199 locoRosterBox = new GlobalRosterEntryComboBox(); 200 locoRosterBox.setNonSelectedItem(""); 201 locoRosterBox.setSelectedIndex(0); 202 203 locoRosterBox.addPropertyChangeListener("selectedRosterEntries", (PropertyChangeEvent pce) -> { 204 locoSelected(); 205 }); 206 locoRosterBox.setVisible(true); 207 locoSelectContainer.add(locoRosterBox); 208 209 addLocoButton.setText(Bundle.getMessage("ButtonAddText")); 210 addLocoButton.setVisible(true); 211 addLocoButton.setToolTipText(Bundle.getMessage("AddButtonToolTip")); 212 addLocoButton.addActionListener((ActionEvent e) -> { 213 addLocoButtonActionPerformed(e); 214 }); 215 locoSelectContainer.add(addLocoButton); 216 217 resetLocoButton.setText(Bundle.getMessage("ButtonReset")); 218 resetLocoButton.setVisible(true); 219 resetLocoButton.setToolTipText(Bundle.getMessage("ResetButtonToolTip")); 220 resetLocoButton.addActionListener((ActionEvent e) -> { 221 locoSelector.reset(); 222 locoRosterBox.setSelectedIndex(0); 223 }); 224 225 locoSelectContainer.add(resetLocoButton); 226 locoSelectContainer.setBorder(javax.swing.BorderFactory.createEtchedBorder()); 227 228 locoSelectContainer.setVisible(true); 229 toppanelcontainer.add(locoSelectContainer); 230 231 p1.add(toppanelcontainer, BorderLayout.PAGE_START); 232 p1.add(slotScroll, BorderLayout.CENTER); 233 add(p1); 234 235 Dimension p1size = new Dimension(450, 200); 236 p1.setMinimumSize(p1size); 237 238 p1.setVisible(true); 239 log.debug("class name {} ",CabSignalPane.class.getName()); 240 } 241 242 private void refreshMasterPauseButton(){ 243 if (masterPauseButton.isSelected()) { // is paused 244 masterPauseButton.setText(Bundle.getMessage("SigDataResume")); 245 masterPauseButton.setToolTipText(Bundle.getMessage("SigDataResumeTip")); 246 slotModel.setPanelPauseButton( true ); 247 } 248 else { // pause relased, go back to normal 249 masterPauseButton.setText(Bundle.getMessage("SigDataPause")); 250 masterPauseButton.setToolTipText(Bundle.getMessage("SigDataPauseTip")); 251 slotModel.setPanelPauseButton( false ); 252 } 253 } 254 255 /** 256 * {@inheritDoc} 257 */ 258 @Override 259 public String getTitle() { 260 return Bundle.getMessage("CabSignalPaneTitle"); 261 } 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override 267 public List<JMenu> getMenus() { 268 List<JMenu> menuList = new ArrayList<>(); 269 270 menuList.add(cabSigColMenu); 271 272 JMenu displayMenu = new JMenu(Bundle.getMessage("DisplayMenu")); 273 274 JMenu iconMenu = new JMenu(Bundle.getMessage("AspectIconMenu")); 275 ButtonGroup offsetGroup = new ButtonGroup(); 276 277 JRadioButtonMenuItem offset0MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 0)); 278 JRadioButtonMenuItem offset1MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 90)); 279 JRadioButtonMenuItem offset2MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 180)); 280 JRadioButtonMenuItem offset3MenuItem = new JRadioButtonMenuItem(Bundle.getMessage("IconDegrees", 270)); 281 282 offsetGroup.add(offset0MenuItem); 283 offsetGroup.add(offset1MenuItem); 284 offsetGroup.add(offset2MenuItem); 285 offsetGroup.add(offset3MenuItem); 286 287 iconMenu.add(offset0MenuItem); 288 iconMenu.add(offset1MenuItem); 289 iconMenu.add(offset2MenuItem); 290 iconMenu.add(offset3MenuItem); 291 292 displayMenu.add(iconMenu); 293 294 _rotationOffset = 0; // startup 295 offset0MenuItem.setSelected(true); 296 ActionListener iconMenuListener = ae -> { 297 if ( offset0MenuItem.isSelected() ) { 298 _rotationOffset = 0; 299 } 300 else if ( offset1MenuItem.isSelected() ) { 301 _rotationOffset = 1; 302 } 303 else if ( offset2MenuItem.isSelected() ) { 304 _rotationOffset = 2; 305 } 306 else if ( offset3MenuItem.isSelected() ) { 307 _rotationOffset = 3; 308 } 309 notifyCabSignalListChanged(); 310 }; 311 offset0MenuItem.addActionListener(iconMenuListener); 312 offset1MenuItem.addActionListener(iconMenuListener); 313 offset2MenuItem.addActionListener(iconMenuListener); 314 offset3MenuItem.addActionListener(iconMenuListener); 315 316 ActionListener rowHeightMenuListener = ae -> { 317 JSpinner delaySpinner = getNewRowHeightSpinner(); 318 int option = JmriJOptionPane.showOptionDialog(this, 319 delaySpinner, 320 Bundle.getMessage("RowHeightOption"), 321 JmriJOptionPane.OK_CANCEL_OPTION, 322 JmriJOptionPane.QUESTION_MESSAGE, null, null, null); 323 if (option == JmriJOptionPane.OK_OPTION) { 324 _defaultRowHeight = (Integer) delaySpinner.getValue(); 325 } 326 else { 327 _slotTable.setRowHeight(_defaultRowHeight); 328 } 329 }; 330 331 JMenuItem searchForNodesMenuItem = new JMenuItem(Bundle.getMessage("RowHeightOption")); 332 searchForNodesMenuItem.addActionListener(rowHeightMenuListener); 333 displayMenu.add(searchForNodesMenuItem); 334 335 menuList.add(displayMenu); 336 337 return menuList; 338 } 339 340 private JSpinner getNewRowHeightSpinner() { 341 JSpinner rqnnSpinner = new JSpinner(new SpinnerNumberModel(_defaultRowHeight, 10, 150, 1)); 342 JComponent rqcomp = rqnnSpinner.getEditor(); 343 JFormattedTextField rqfield = (JFormattedTextField) rqcomp.getComponent(0); 344 DefaultFormatter rqformatter = (DefaultFormatter) rqfield.getFormatter(); 345 rqformatter.setCommitsOnValidEdit(true); 346 rqnnSpinner.addChangeListener((ChangeEvent e) -> { 347 _slotTable.setRowHeight((Integer) rqnnSpinner.getValue()); 348 }); 349 return rqnnSpinner; 350 } 351 352 public void addLocoButtonActionPerformed(ActionEvent e) { 353 if (locoSelector.getAddress() == null) { 354 return; 355 } 356 LocoAddress locoaddress = locoSelector.getAddress(); 357 // create and inform CabSignal state of master pause / resume 358 cabSignalManager.getCabSignal(locoaddress).setMasterCabSigPauseActive( masterPauseButton.isSelected() ); 359 } 360 361 public void locoSelected() { 362 if (locoRosterBox.getSelectedRosterEntries().length == 1) { 363 locoSelector.setAddress(locoRosterBox.getSelectedRosterEntries()[0].getDccLocoAddress()); 364 } 365 } 366 367 private TableCellRenderer tableSignalAspectRenderer() { 368 369 return new TableCellRenderer() { 370 JLabel f = new JLabel(); 371 /** 372 * {@inheritDoc} 373 */ 374 @Override 375 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, 376 boolean hasFocus, int row, int column) { 377 f.setIcon(null); 378 if ( !value.toString().isEmpty() ) { 379 // value gets passed as a string so image can be rotated here 380 NamedIcon tmpIcon = new NamedIcon(value.toString(), value.toString() ); 381 tmpIcon.setRotation( tmpIcon.getRotation() + _rotationOffset,slotScroll); 382 // double d = mastIcon.reduceTo(28, 28, 0.01d); 383 f.setIcon(tmpIcon); 384 } 385 f.setText(""); 386 f.setHorizontalAlignment(JLabel.CENTER); 387 if (isSelected) { 388 f.setBackground( table.getSelectionBackground() ); 389 } else { 390 f.setBackground(null); 391 } 392 return f; 393 } 394 }; 395 } 396 397 /** 398 * {@inheritDoc} 399 */ 400 @Override 401 public String getHelpTarget() { 402 return "package.jmri.jmrit.cabsignals.CabSignalPane"; 403 } 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override 409 public void dispose() { 410 cabSignalManager.removeCabSignalListListener(this); 411 _slotTable = null; 412 slotModel.dispose(); 413 cabSignalManager = null; 414 super.dispose(); 415 } 416 417 /** 418 * {@inheritDoc} 419 * Cab Signal List Listener interface 420 */ 421 @Override 422 public void notifyCabSignalListChanged(){ 423 slotModel.fireTableDataChanged(); 424 } 425 426 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CabSignalPane.class); 427 428}