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