001package jmri.jmrit.roster.swing.speedprofile; 002 003import java.awt.BorderLayout; 004import java.awt.Component; 005import java.awt.event.ActionEvent; 006import java.awt.event.KeyEvent; 007import java.awt.event.KeyListener; 008import java.util.ArrayList; 009import java.util.Map; 010import java.util.TreeMap; 011 012import javax.swing.Box; 013import javax.swing.BoxLayout; 014import javax.swing.JLabel; 015import javax.swing.JPanel; 016import javax.swing.JRadioButton; 017import javax.swing.JScrollPane; 018import javax.swing.JTable; 019import javax.swing.JTextField; 020import javax.swing.border.EmptyBorder; 021import javax.swing.table.DefaultTableCellRenderer; 022 023import jmri.implementation.SignalSpeedMap; 024import jmri.jmrit.roster.RosterEntry; 025import jmri.jmrit.roster.RosterSpeedProfile; 026import jmri.jmrit.roster.RosterSpeedProfile.SpeedStep; 027import jmri.util.swing.JmriJOptionPane; 028 029/** 030 * Display Speed Profile. 031 * 032 * @author Pete Cressman Copyright (C) 2015 033 */ 034public class SpeedProfileTable extends jmri.util.JmriJFrame { 035 036 java.text.DecimalFormat threeDigit = new java.text.DecimalFormat("0.000"); 037 int interp; 038 float loScale; 039 JLabel description; 040 String rosterId; 041 RosterSpeedProfile speedProfile; 042 Map<Integer, Boolean> anomalies; 043 boolean hasAnomaly; 044 // divided by layout scale, gives a rough conversion for throttle setting to track speed 045 static float SCALE = jmri.jmrit.logix.SpeedUtil.SCALE_FACTOR; 046 static java.awt.Color myRed = new java.awt.Color(255, 120, 120); 047 048 public SpeedProfileTable(RosterEntry re) { 049 this(re.getSpeedProfile(), re.getId()); 050 } 051 052 public SpeedProfileTable(RosterSpeedProfile sp, String id) { 053 super(false, true); 054 speedProfile = sp; 055 rosterId = id; 056 anomalies = jmri.jmrit.logix.MergePrompt.validateSpeedProfile(speedProfile); 057 hasAnomaly = (anomalies !=null && anomalies.size() > 0); 058 setTitle(Bundle.getMessage("SpeedTable", rosterId)); 059 getContentPane().setLayout(new BorderLayout(15,15)); 060 061 interp = jmri.InstanceManager.getDefault(SignalSpeedMap.class).getInterpretation(); 062 loScale = jmri.InstanceManager.getDefault(SignalSpeedMap.class).getLayoutScale(); 063 SpeedTableModel model = new SpeedTableModel(speedProfile); 064 JTable table = new JTable(model); 065 table.addKeyListener(new KeyListener() { 066 @Override 067 public void keyTyped(KeyEvent ke) { 068 char ch = ke.getKeyChar(); 069 if (ch == KeyEvent.VK_DELETE || ch == KeyEvent.VK_X) { 070 deleteRow(table); 071 } 072 } 073 @Override 074 public void keyPressed(KeyEvent e) {} 075 @Override 076 public void keyReleased(KeyEvent e) {} 077 }); 078 079 table.getColumnModel().getColumn(SpeedTableModel.FORWARD_SPEED_COL).setCellRenderer(new ColorCellRenderer()); 080 table.getColumnModel().getColumn(SpeedTableModel.REVERSE_SPEED_COL).setCellRenderer(new ColorCellRenderer()); 081 082 for (int i = 0; i < model.getColumnCount(); i++) { 083 int width = model.getPreferredWidth(i); 084 javax.swing.table.TableColumn column = table.getColumnModel().getColumn(i); 085 column.setPreferredWidth(width); 086 } 087 088 JPanel contentPane = new JPanel(); 089 contentPane.setLayout(new BorderLayout(5, 5)); 090 java.awt.Font font = table.getFont(); 091 092 JPanel panel = new JPanel(); 093 panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS)); 094 JLabel label = new JLabel(Bundle.getMessage("units")); 095 label.setFont(font); 096 javax.swing.ButtonGroup bp = new javax.swing.ButtonGroup(); 097 JRadioButton mm = new JRadioButton(Bundle.getMessage("mmps")); 098 mm.setFont(font); 099 mm.addActionListener((ActionEvent e) -> { 100 update(model, SignalSpeedMap.PERCENT_NORMAL); 101 }); 102 JRadioButton mph = new JRadioButton(Bundle.getMessage("mph")); 103 mph.setFont(font); 104 mph.addActionListener((ActionEvent e) -> { 105 update(model, SignalSpeedMap.SPEED_MPH); 106 }); 107 JRadioButton kph = new JRadioButton(Bundle.getMessage("kph")); 108 kph.setFont(font); 109 kph.addActionListener((ActionEvent e) -> { 110 update(model, SignalSpeedMap.SPEED_KMPH); 111 }); 112 bp.add(mm); 113 bp.add(mph); 114 bp.add(kph); 115 panel.add(Box.createHorizontalGlue()); 116 panel.add(label); 117 panel.add(mm); 118 panel.add(mph); 119 panel.add(kph); 120 panel.add(Box.createHorizontalGlue()); 121 String str; 122 switch(interp) { 123 case SignalSpeedMap.SPEED_MPH: 124 mph.setSelected(true); 125 str = "scale"; // NOI18N 126 break; 127 case SignalSpeedMap.SPEED_KMPH: 128 kph.setSelected(true); 129 str = "scale"; // NOI18N 130 break; 131 default: 132 mm.setSelected(true); 133 str = "track"; // NOI18N 134 } 135 JPanel topPanel = new JPanel(); 136 topPanel.setBorder( new EmptyBorder( 0, 8, 8, 8 ) ); // keep text away from edges of pane 137 topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.PAGE_AXIS)); 138 description = new JLabel(Bundle.getMessage("rosterId", Bundle.getMessage(str), rosterId)); 139 description.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 140 topPanel.add(description); 141 JLabel info = new JLabel(Bundle.getMessage("cellInfo")); 142 info.setFont(font); 143 topPanel.add(info); 144 if (hasAnomaly) { 145 JLabel redInfo = new JLabel(Bundle.getMessage("redInfo_1")); 146 redInfo.setFont(font); 147 redInfo.setForeground(java.awt.Color.RED); 148 redInfo.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 149 topPanel.add(redInfo); 150 redInfo = new JLabel(Bundle.getMessage("redInfo_2")); 151 redInfo.setForeground(java.awt.Color.RED); 152 redInfo.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 153 redInfo.setFont(font); 154 topPanel.add(redInfo); 155 redInfo = new JLabel(Bundle.getMessage("redInfo_3")); 156 redInfo.setForeground(java.awt.Color.RED); 157 redInfo.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); 158 redInfo.setFont(font); 159 topPanel.add(redInfo); 160 } 161 162 contentPane.add(topPanel, BorderLayout.NORTH); 163 contentPane.add(panel, BorderLayout.CENTER); 164 165 JScrollPane pane = new JScrollPane(table); 166 contentPane.add(pane, BorderLayout.SOUTH); 167 getContentPane().add(contentPane); 168 pack(); 169 } 170 171 private void deleteRow(JTable table) { 172 int row = table.getSelectedRow(); 173 if (row >= 0) { 174 SpeedTableModel model = (SpeedTableModel)table.getModel(); 175 Map.Entry<Integer, SpeedStep> entry = model.speedArray.get(row); 176 int step = Math.round((float)(entry.getKey()*126)/1000); 177 if ( JmriJOptionPane.YES_OPTION == JmriJOptionPane.showConfirmDialog(table, 178 Bundle.getMessage("DeleteRow", step), Bundle.getMessage("SpeedTable", rosterId), 179 JmriJOptionPane.YES_NO_OPTION)) { 180 model.speedArray.remove(entry); 181 speedProfile.deleteStep(entry.getKey()); 182 model.fireTableDataChanged(); 183 // re.updateFile(); 184 // Roster.getDefault().writeRoster(); 185 } 186 } 187 } 188 189 private void update(SpeedTableModel model, int which) { 190 interp = which; 191 // can't figure out a way to update column names 192 //model.getColumnName(SpeedTableModel.FORWARD_SPEED_COL); 193 //model.getColumnName(SpeedTableModel.REVERSE_SPEED_COL); 194 String str; 195 switch(interp) { 196 case SignalSpeedMap.SPEED_MPH: 197 case SignalSpeedMap.SPEED_KMPH: 198 str = "scale"; // NOI18N 199 break; 200 default: 201 str = "track"; // NOI18N 202 } 203 description.setText(Bundle.getMessage("rosterId", Bundle.getMessage(str), rosterId)); 204 model.fireTableDataChanged(); 205 } 206 207 public class ColorCellRenderer extends DefaultTableCellRenderer { 208 @Override 209 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) { 210 Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); 211 212 SpeedTableModel model = (SpeedTableModel) table.getModel(); 213 214 if (anomalies == null || anomalies.size() == 0) { 215 c.setBackground(table.getBackground()); 216 return c; 217 } 218 Map.Entry<Integer, SpeedStep> entry = model.getEntry(row); 219 Boolean direction = anomalies.get(entry.getKey()); 220 if (direction == null) { 221 c.setBackground(table.getBackground()); 222 return c; 223 } 224 boolean dir = direction.booleanValue(); 225 if ( dir && col == SpeedTableModel.FORWARD_SPEED_COL) { 226 c.setBackground(myRed); 227 } else if (!dir && col == SpeedTableModel.REVERSE_SPEED_COL){ 228 c.setBackground(myRed); 229 } 230 return c; 231 } 232 } 233 234 class SpeedTableModel extends javax.swing.table.AbstractTableModel { 235 static final int STEP_COL = 0; 236 static final int THROTTLE_COL = 1; 237 static final int FORWARD_SPEED_COL = 2; 238 static final int FORWARD_FACTOR_COL = 3; 239 static final int REVERSE_SPEED_COL = 4; 240 static final int REVERSE_FACTOR_COL = 5; 241 static final int NUMCOLS = 6; 242 243 ArrayList<Map.Entry<Integer, SpeedStep>> speedArray = new ArrayList<>(); 244 245 SpeedTableModel(RosterSpeedProfile sp) { 246 TreeMap<Integer, SpeedStep> speeds = sp.getProfileSpeeds(); 247 Map.Entry<Integer, SpeedStep> entry = speeds.firstEntry(); 248 while (entry!=null) { 249 speedArray.add(entry); 250 entry = speeds.higherEntry(entry.getKey()); 251 } 252 } 253 254 @Override 255 public int getColumnCount() { 256 return NUMCOLS; 257 } 258 259 @Override 260 public int getRowCount() { 261 return speedArray.size(); 262 } 263 @Override 264 public String getColumnName(int col) { 265 String rate = Bundle.getMessage("speed"); 266 /* can't figure out a way to update column names 267 switch(interp) { 268 case SignalSpeedMap.SPEED_MPH: 269 rate = "Mph"; 270 break; 271 case SignalSpeedMap.SPEED_KMPH: 272 rate = "KMph"; 273 break; 274 default: 275 rate = "mm/s"; 276 }*/ 277 switch (col) { 278 case STEP_COL: 279 return Bundle.getMessage("step"); 280 case THROTTLE_COL: 281 return Bundle.getMessage("throttle"); 282 case FORWARD_SPEED_COL: 283 return Bundle.getMessage("forwardSpeed", rate); 284 case REVERSE_SPEED_COL: 285 return Bundle.getMessage("reverseSpeed", rate); 286 case FORWARD_FACTOR_COL: 287 case REVERSE_FACTOR_COL: 288 return Bundle.getMessage("factor"); 289 default: 290 // fall out 291 break; 292 } 293 return ""; 294 } 295 @Override 296 public Class<?> getColumnClass(int col) { 297 return String.class; 298 } 299 300 public int getPreferredWidth(int col) { 301 switch (col) { 302 case STEP_COL: 303 return new JTextField(3).getPreferredSize().width; 304 case THROTTLE_COL: 305 case FORWARD_FACTOR_COL: 306 case REVERSE_FACTOR_COL: 307 return new JTextField(5).getPreferredSize().width; 308 case FORWARD_SPEED_COL: 309 case REVERSE_SPEED_COL: 310 return new JTextField(8).getPreferredSize().width; 311 default: 312 // fall out 313 break; 314 } 315 return new JTextField(8).getPreferredSize().width; 316 } 317 318 @Override 319 public boolean isCellEditable(int row, int col) { 320 if (hasAnomaly && (col == FORWARD_SPEED_COL || col == REVERSE_SPEED_COL)) { 321 return true; 322 } 323 return false; 324 } 325 326 327 @Override 328 public Object getValueAt(int row, int col) { 329 Map.Entry<Integer, SpeedStep> entry = speedArray.get(row); 330 switch (col) { 331 case STEP_COL: 332 return Math.round((float)(entry.getKey()*126)/1000); 333 case THROTTLE_COL: 334 return threeDigit.format((float)(entry.getKey())/1000); 335 case FORWARD_SPEED_COL: 336 float speed = entry.getValue().getForwardSpeed(); 337 switch(interp) { 338 case SignalSpeedMap.SPEED_MPH: 339 speed = speed*loScale*3.6f*0.621371f/1000; 340 break; 341 case SignalSpeedMap.SPEED_KMPH: 342 speed = speed*loScale*3.6f/1000; 343 break; 344 default: 345 } 346 return threeDigit.format(speed); 347 case FORWARD_FACTOR_COL: 348 return threeDigit.format( 349 entry.getValue().getForwardSpeed() * SCALE / (loScale * entry.getKey())); 350 case REVERSE_SPEED_COL: 351 speed = entry.getValue().getReverseSpeed(); 352 switch(interp) { 353 case SignalSpeedMap.SPEED_MPH: 354 speed = speed*loScale*3.6f*0.621371f/1000; 355 break; 356 case SignalSpeedMap.SPEED_KMPH: 357 speed = speed*loScale*3.6f/1000; 358 break; 359 default: 360 } 361 return threeDigit.format(speed); 362 case REVERSE_FACTOR_COL: 363 return threeDigit.format( 364 entry.getValue().getReverseSpeed() * SCALE / (loScale * entry.getKey())); 365 default: 366 // fall out 367 break; 368 } 369 return ""; 370 } 371 372 @Override 373 public void setValueAt(Object value, int row, int col) { 374 if (!hasAnomaly) { 375 return; 376 } 377 Map.Entry<Integer, SpeedStep> entry = speedArray.get(row); 378 try { 379 switch (col) { 380 case FORWARD_SPEED_COL: 381 entry.getValue().setForwardSpeed(Float.parseFloat(((String)value).replace(',', '.'))); 382 return; 383 case REVERSE_SPEED_COL: 384 entry.getValue().setReverseSpeed(Float.parseFloat(((String)value).replace(',', '.'))); 385 return; 386 default: 387 // fall out 388 break; 389 } 390 } catch (NumberFormatException nfe) { // ignore 391 } 392 } 393 394 Map.Entry<Integer, SpeedStep> getEntry(int row) { 395 return speedArray.get(row); 396 } 397 398 } 399}