001package jmri.jmrit.beantable.oblock; 002 003import jmri.*; 004import jmri.jmrit.logix.OBlockManager; 005import jmri.jmrit.logix.Portal; 006import jmri.jmrit.logix.PortalManager; 007import jmri.swing.NamedBeanComboBox; 008import jmri.util.JmriJFrame; 009import jmri.util.swing.JComboBoxUtil; 010 011import org.slf4j.Logger; 012import org.slf4j.LoggerFactory; 013 014import javax.annotation.CheckForNull; 015import javax.annotation.Nonnull; 016import javax.swing.*; 017import java.awt.*; 018import java.awt.event.ActionEvent; 019import java.util.Objects; 020 021/** 022 * Defines a GUI for editing OBlock - Signal objects in the tabbed Table interface. 023 * Adapted from AudioSourceFrame. 024 * Compare to CPE CircuitBuilder Signal Config frame {@link jmri.jmrit.display.controlPanelEditor.EditSignalFrame} 025 * 026 * @author Matthew Harris copyright (c) 2009 027 * @author Egbert Broerse (C) 2020 028 */ 029public class SignalEditFrame extends JmriJFrame { 030 031 JPanel main = new JPanel(); 032 033 SignalTableModel model; 034 NamedBean signal; 035 PortalManager pm; 036 OBlockManager obm; 037 private final SignalEditFrame frame = this; 038 private Portal _portal; 039 SignalTableModel.SignalRow _sr; 040 //private final Object lock = new Object(); 041 042 // UI components for Add/Edit Signal (head or mast) 043 JLabel portalLabel = new JLabel(Bundle.getMessage("AtPortalLabel"), JLabel.TRAILING); 044 045 JLabel signalMastLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameSignalMast"))); 046 JLabel signalHeadLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("BeanNameSignalHead"))); 047 JLabel fromBlockLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("FromBlockName"))); 048 JLabel fromBlock = new JLabel(); 049 JLabel toBlockLabel = new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("ToBlockName"))); 050 JLabel toBlock = new JLabel(); 051 JLabel mastName = new JLabel(); 052 JLabel headName = new JLabel(); 053 String[] p0 = {""}; 054 private final JComboBox<String> portalComboBox = new JComboBox<>(p0); 055 private final NamedBeanComboBox<SignalMast> sigMastComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalMastManager.class), 056 null, NamedBean.DisplayOptions.DISPLAYNAME); 057 private final NamedBeanComboBox<SignalHead> sigHeadComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(SignalHeadManager.class), 058 null, NamedBean.DisplayOptions.DISPLAYNAME); 059// private final NamedBeanComboBox<OBlock> fromBlockComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(OBlockManager.class), 060// null, NamedBean.DisplayOptions.DISPLAYNAME); 061// private final NamedBeanComboBox<OBlock> toBlockComboBox = new NamedBeanComboBox<>(InstanceManager.getDefault(OBlockManager.class), 062// null, NamedBean.DisplayOptions.DISPLAYNAME); 063 private final JButton flipButton = new JButton(Bundle.getMessage("ButtonFlipBlocks")); 064 // the following 3 items copied from beanedit, place in separate static method? 065 JSpinner lengthSpinner = new JSpinner(); // 2 digit decimal format field, initialized later as instance 066 JRadioButton inch = new JRadioButton(Bundle.getMessage("LengthInches")); 067 JRadioButton cm = new JRadioButton(Bundle.getMessage("LengthCentimeters")); 068 JLabel statusBar = new JLabel(Bundle.getMessage("AddXStatusInitial1", 069 (Bundle.getMessage("BeanNameSignalMast") + "/" + Bundle.getMessage("BeanNameSignalHead")), 070 Bundle.getMessage("ButtonOK"))); 071 072 private boolean _newSignal; 073 074// @SuppressWarnings("OverridableMethodCallInConstructor") 075 public SignalEditFrame(@Nonnull String title, 076 @CheckForNull NamedBean signal, 077 @CheckForNull SignalTableModel.SignalRow sr, 078 @CheckForNull SignalTableModel model) { 079 super(title, true, true); 080 this.model = model; 081 this.signal = signal; 082 if (signal == null) { 083 _newSignal = true; 084 } 085 log.debug("SR == {}", (sr == null ? "null" : "not null")); 086 obm = InstanceManager.getDefault(OBlockManager.class); 087 pm = InstanceManager.getDefault(PortalManager.class); 088 for (Portal pi : pm.getPortalSet()) { 089 portalComboBox.addItem(pi.getName()); 090 } 091 layoutFrame(); 092 if (sr != null) { 093 _sr = sr; 094 _portal = sr.getPortal(); 095 populateFrame(_sr); 096 } else { 097 resetFrame(); 098 } 099 addCloseListener(this); 100 } 101 102 public void layoutFrame() { 103 frame.addHelpMenu("package.jmri.jmrit.beantable.OBlockTable", true); 104 frame.getContentPane().setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.PAGE_AXIS)); 105 frame.setSize(300, 200); 106 main.setLayout(new BoxLayout(main, BoxLayout.PAGE_AXIS)); 107 108 JPanel configGrid = new JPanel(); 109 GridLayout layout = new GridLayout(4, 2, 10, 0); // (int rows, int cols, int hgap, int vgap) 110 configGrid.setLayout(layout); 111 112 JPanel p = new JPanel(); 113 p.setLayout(new BoxLayout(p, BoxLayout.PAGE_AXIS)); 114 115 // row 1 116 JPanel p1 = new JPanel(); 117 p1.add(signalMastLabel); 118 p1.add(sigMastComboBox); 119 sigMastComboBox.setAllowNull(true); 120 JComboBoxUtil.setupComboBoxMaxRows(sigMastComboBox); 121 p1.add(mastName); 122 configGrid.add(p1); 123 124 p1 = new JPanel(); 125 p1.add(signalHeadLabel); 126 p1.add(sigHeadComboBox); 127 sigHeadComboBox.setAllowNull(true); 128 JComboBoxUtil.setupComboBoxMaxRows(sigHeadComboBox); 129 p1.add(headName); 130 configGrid.add(p1); 131 sigMastComboBox.addActionListener(e -> { 132 if ((sigMastComboBox.getSelectedIndex() > 0) && (sigHeadComboBox.getItemCount() > 0)) { 133 sigHeadComboBox.setSelectedIndex(0); // either one 134 model.checkDuplicateSignal(sigMastComboBox.getSelectedItem()); 135 } 136 }); 137 sigHeadComboBox.addActionListener(e -> { 138 if ((sigHeadComboBox.getSelectedIndex() > 0) && (sigMastComboBox.getItemCount() > 0)) { 139 sigMastComboBox.setSelectedIndex(0); // either one 140 model.checkDuplicateSignal(sigHeadComboBox.getSelectedItem()); 141 } 142 }); 143 144 // row 2 145 p1 = new JPanel(); 146 p1.add(portalLabel); 147 p1.add(portalComboBox); // combo has a blank first item 148 JComboBoxUtil.setupComboBoxMaxRows(portalComboBox); 149 portalComboBox.addActionListener(e -> { 150 if (portalComboBox.getSelectedIndex() > 0) { 151 fromBlock.setText(pm.getPortal((String) portalComboBox.getSelectedItem()).getFromBlockName()); 152 toBlock.setText(pm.getPortal((String) portalComboBox.getSelectedItem()).getToBlockName()); 153 } 154 }); 155 configGrid.add(p1); 156 flipButton.addActionListener(e -> { 157 String left = fromBlock.getText(); 158 fromBlock.setText(toBlock.getText()); 159 toBlock.setText(left); 160 }); 161 p1 = new JPanel(); 162 p1.add(flipButton); 163 flipButton.setToolTipText(Bundle.getMessage("FlipToolTip")); 164 configGrid.add(p1); 165 166 // row 3 167 p1 = new JPanel(); 168 p1.add(fromBlockLabel); 169 p1.add(fromBlock); 170// fromBlockComboBox.setAllowNull(true); 171// fromBlockComboBox.addActionListener(e -> { 172// if (fromBlockComboBox.getSelectedIndex() == toBlockComboBox.getSelectedIndex()) { 173// toBlockComboBox.setSelectedIndex(0); 174// } 175// }); 176 configGrid.add(p1); 177 178 p1 = new JPanel(); 179 p1.add(toBlockLabel); 180 p1.add(toBlock); 181// toBlockComboBox.setAllowNull(true); 182// toBlockComboBox.addActionListener(e -> { 183// if (fromBlockComboBox.getSelectedIndex() == toBlockComboBox.getSelectedIndex()) { 184// fromBlockComboBox.setSelectedIndex(0); 185// } 186// }); 187 configGrid.add(p1); 188 189 // row 4 190 // copied from beanedit, also in BlockPathEditFrame 191 p1 = new JPanel(); 192 p1.add(new JLabel(Bundle.getMessage("MakeLabel", Bundle.getMessage("Offset")))); 193 lengthSpinner.setModel( 194 new SpinnerNumberModel(Float.valueOf(0f), Float.valueOf(-2000f), Float.valueOf(2000f), Float.valueOf(0.01f))); 195 lengthSpinner.setEditor(new JSpinner.NumberEditor(lengthSpinner, "###0.00")); 196 lengthSpinner.setPreferredSize(new JTextField(8).getPreferredSize()); 197 lengthSpinner.setValue(0f); // reset from possible previous use 198 lengthSpinner.setToolTipText(Bundle.getMessage("OffsetToolTip")); 199 p1.add(lengthSpinner); 200 configGrid.add(p1); 201 202 ButtonGroup bg = new ButtonGroup(); 203 bg.add(inch); 204 bg.add(cm); 205 206 p1 = new JPanel(); 207 p1.add(inch); 208 p1.add(cm); 209 p1.setLayout(new BoxLayout(p1, BoxLayout.PAGE_AXIS)); 210 inch.setSelected(true); 211 inch.addActionListener(e -> { 212 cm.setSelected(!inch.isSelected()); 213 updateLength(); 214 }); 215 cm.addActionListener(e -> { 216 inch.setSelected(!cm.isSelected()); 217 updateLength(); 218 }); 219 configGrid.add(p1); 220 p.add(configGrid); 221 222 p.add(Box.createHorizontalGlue()); 223 224 JPanel p2 = new JPanel(); 225 statusBar.setFont(statusBar.getFont().deriveFont(0.9f * signalMastLabel.getFont().getSize())); // a bit smaller 226 statusBar.setForeground(Color.gray); 227 p2.add(statusBar); 228 p.add(p2); 229 230 p2 = new JPanel(); 231 p2.setLayout(new BoxLayout(p2, BoxLayout.LINE_AXIS)); 232 JButton cancel; 233 p2.add(cancel = new JButton(Bundle.getMessage("ButtonCancel"))); 234 cancel.addActionListener((ActionEvent e) -> closeFrame()); 235 JButton ok; 236 p2.add(ok = new JButton(Bundle.getMessage("ButtonOK"))); 237 ok.addActionListener(this::applyPressed); 238 p.add(p2); 239 240 //main.add(p); 241 frame.getContentPane().add(p); 242 243 frame.setEscapeKeyClosesWindow(true); 244 frame.getRootPane().setDefaultButton(ok); 245 246 //frame.add(scroll); 247 frame.pack(); 248 } 249 250 /** 251 * Reset the Edit Signal frame with default values. 252 */ 253 public void resetFrame() { 254 if (sigMastComboBox.getItemCount() > 0) { 255 sigMastComboBox.setSelectedIndex(0); 256 } 257 if (sigHeadComboBox.getItemCount() > 0) { 258 sigHeadComboBox.setSelectedIndex(0); 259 } 260 if (portalComboBox.getItemCount() > 0) { 261 portalComboBox.setSelectedIndex(0); 262 } 263 lengthSpinner.setValue(0f); 264 // reset statusBar text 265 if ((sigMastComboBox.getItemCount() == 0) && (sigHeadComboBox.getItemCount() == 0)) { 266 status(Bundle.getMessage("NoSignalWarning"), true); 267 } else if (portalComboBox.getItemCount() > 1) { 268 status(Bundle.getMessage("AddXStatusInitial1", 269 (Bundle.getMessage("BeanNameSignalMast")+"/"+Bundle.getMessage("BeanNameSignalHead")), 270 Bundle.getMessage("ButtonOK")), false); // I18N to include original button name in help string 271 } else { 272 status(Bundle.getMessage("NoSignalPortal"), true); 273 } 274 mastName.setVisible(false); 275 headName.setVisible(false); 276 sigMastComboBox.setVisible(true); 277 sigHeadComboBox.setVisible(true); 278 frame.pack(); 279 } 280 281 /** 282 * Populate the Edit Signal frame with current values from a SignalRow in the SignalTable. 283 * 284 * @param sr existing SignalRow to copy the attributes from 285 */ 286 public void populateFrame(SignalTableModel.SignalRow sr) { 287 if (sr == null) { 288 throw new IllegalArgumentException("Null Signal object"); 289 } 290 status(Bundle.getMessage("AddXStatusInitial3", sr.getSignal().getDisplayName(), 291 Bundle.getMessage("ButtonOK")), false); 292 fromBlock.setText(sr.getFromBlock().getDisplayName()); 293 toBlock.setText(sr.getToBlock().getDisplayName()); 294 if (signal instanceof SignalMast) { 295 mastName.setText(sr.getSignal().getDisplayName()); 296 headName.setText("-"); 297 //sigMastComboBox.setSelectedItemByName(sr.getSignal().getDisplayName()); // combo hidden for Edits 298 } else if (signal instanceof SignalHead) { 299 mastName.setText("-"); 300 headName.setText(sr.getSignal().getDisplayName()); 301 //sigHeadComboBox.setSelectedItemByName(sr.getSignal().getDisplayName()); // combo hidden for Edits 302 } 303 portalComboBox.setSelectedItem(_portal.getName()); 304 cm.setSelected(sr._isMetric); // before filling in value in spinner prevent recalc 305 if (sr.isMetric()) { 306 lengthSpinner.setValue(sr.getLength()/10); 307 } else { 308 lengthSpinner.setValue(sr.getLength()/25.4f); 309 } 310 mastName.setVisible(true); 311 headName.setVisible(true); 312 sigMastComboBox.setVisible(false); 313 sigHeadComboBox.setVisible(false); 314 frame.pack(); 315 _newSignal = false; 316 } 317 318 private void applyPressed(ActionEvent e) { 319 if (_newSignal) { // can't change an existing mast, easy to delete and recreate 320 if (sigMastComboBox.getSelectedIndex() > 0) { 321 signal = sigMastComboBox.getSelectedItem(); 322 } else if (sigHeadComboBox.getSelectedIndex() > 0) { 323 signal = sigHeadComboBox.getSelectedItem(); 324 } else { 325 status(Bundle.getMessage("WarnNoSignal"), true); 326 return; 327 } 328 String msg = model.checkDuplicateSignal(signal); 329 if (msg != null) { 330 status(msg, true); 331 return; 332 } 333 } 334 _portal = pm.getPortal((String) portalComboBox.getSelectedItem()); 335 if (_portal == null || portalComboBox.getSelectedIndex() < 1) { 336 status(Bundle.getMessage("WarnNoPortal"), true); 337 return; 338 } 339 if (!_newSignal) { 340 model.deleteSignal(_sr); // delete old in Portal if it was set 341 _sr.setPortal(_portal); 342 } 343 // fetch physical details 344 float length; 345 if (cm.isSelected()) { 346 length = (float) lengthSpinner.getValue()*10.0f; 347 } else { 348 length = (float) lengthSpinner.getValue()*25.4f; 349 } 350 351 if (_portal.setProtectSignal(signal, length, obm.getOBlock(toBlock.getText()))) { 352 if ((fromBlock.getText() == null) && (toBlock.getText() != null)) { // could be read from old panels? 353 _portal.setFromBlock(_portal.getOpposingBlock(obm.getOBlock(Objects.requireNonNull(toBlock.getText()))), true); 354 } 355 } 356 // update Metric choice in ProtectedBlock 357 if (toBlock.getText() != null) { 358 Objects.requireNonNull(obm.getOBlock(toBlock.getText())).setMetricUnits(cm.isSelected()); 359 } 360 // Notify changes 361 model.fireTableDataChanged(); 362 363 closeFrame(); 364 } 365 366 protected void closeFrame(){ 367 // remind to save, if Turnout was created or edited 368 // if (isDirty) { 369 // showReminderMessage(); 370 // isDirty = false; 371 // } 372 // hide frame 373 setVisible(false); 374 375 model.setEditMode(false); 376 log.debug("SignalEditFrame.closeFrame signalEdit=False"); 377 frame.dispose(); 378 } 379 380 // copied from beanedit, also in BlockPathEditFrame 381 private void updateLength() { 382 float len = (float) lengthSpinner.getValue(); 383 if (inch.isSelected()) { 384 lengthSpinner.setValue(len/2.54f); 385 } else { 386 lengthSpinner.setValue(len*2.54f); 387 } 388 } 389 390 void status(String message, boolean warn){ 391 statusBar.setText(message); 392 statusBar.setForeground(warn ? Color.red : Color.gray); 393 } 394 395 // listen for frame closing 396 void addCloseListener(JmriJFrame frame) { 397 frame.addWindowListener(new java.awt.event.WindowAdapter() { 398 @Override 399 public void windowClosing(java.awt.event.WindowEvent e) { 400 model.setEditMode(false); 401 log.debug("SignalEditFrame.closeFrame signalEdit=False"); 402 frame.dispose(); 403 } 404 }); 405 } 406 407 private static final Logger log = LoggerFactory.getLogger(SignalEditFrame.class); 408 409}