001package jmri.jmrit.operations.routes; 002 003import java.awt.BorderLayout; 004import java.awt.FlowLayout; 005import java.awt.event.ActionEvent; 006import java.awt.event.ActionListener; 007import java.beans.PropertyChangeEvent; 008import java.beans.PropertyChangeListener; 009import java.util.ArrayList; 010import java.util.List; 011 012import javax.swing.*; 013import javax.swing.colorchooser.AbstractColorChooserPanel; 014import javax.swing.table.TableCellEditor; 015 016import jmri.jmrit.operations.setup.Control; 017import jmri.jmrit.operations.setup.Setup; 018import jmri.util.swing.*; 019import jmri.util.table.ButtonEditor; 020import jmri.util.table.ButtonRenderer; 021 022/** 023 * Table Model for edit of route locations used by operations 024 * 025 * @author Daniel Boudreau Copyright (C) 2008, 2013 026 */ 027public class RouteEditTableModel extends javax.swing.table.AbstractTableModel implements PropertyChangeListener { 028 029 // Defines the columns 030 private static final int ID_COLUMN = 0; 031 private static final int NAME_COLUMN = ID_COLUMN + 1; 032 private static final int TRAIN_DIRECTION_COLUMN = NAME_COLUMN + 1; 033 private static final int MAXMOVES_COLUMN = TRAIN_DIRECTION_COLUMN + 1; 034 private static final int RANDOM_CONTROL_COLUMN = MAXMOVES_COLUMN + 1; 035 private static final int PICKUP_COLUMN = RANDOM_CONTROL_COLUMN + 1; 036 private static final int DROP_COLUMN = PICKUP_COLUMN + 1; 037 private static final int TRAVEL_COLUMN = DROP_COLUMN + 1; 038 private static final int TIME_COLUMN = TRAVEL_COLUMN + 1; 039 private static final int MAXLENGTH_COLUMN = TIME_COLUMN + 1; 040 private static final int GRADE = MAXLENGTH_COLUMN + 1; 041 private static final int TRAINICONX = GRADE + 1; 042 private static final int TRAINICONY = TRAINICONX + 1; 043 private static final int COMMENT_COLUMN = TRAINICONY + 1; 044 private static final int UP_COLUMN = COMMENT_COLUMN + 1; 045 private static final int DOWN_COLUMN = UP_COLUMN + 1; 046 private static final int DELETE_COLUMN = DOWN_COLUMN + 1; 047 048 private static final int HIGHEST_COLUMN = DELETE_COLUMN + 1; 049 050 private JTable _table; 051 private Route _route; 052 private RouteEditFrame _frame; 053 List<RouteLocation> _routeList = new ArrayList<>(); 054 055 public RouteEditTableModel() { 056 super(); 057 } 058 059 public void setWait(boolean showWait) { 060 XTableColumnModel tcm = (XTableColumnModel) _table.getColumnModel(); 061 tcm.setColumnVisible(tcm.getColumnByModelIndex(TRAVEL_COLUMN), showWait); 062 tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), !showWait); 063 } 064 065 private void updateList() { 066 if (_route == null) { 067 return; 068 } 069 // first, remove listeners from the individual objects 070 removePropertyChangeRouteLocations(); 071 _routeList = _route.getLocationsBySequenceList(); 072 // and add them back in 073 for (RouteLocation rl : _routeList) { 074 rl.addPropertyChangeListener(this); 075 } 076 } 077 078 protected void initTable(RouteEditFrame frame, JTable table, Route route) { 079 _frame = frame; 080 _table = table; 081 _route = route; 082 if (_route != null) { 083 _route.addPropertyChangeListener(this); 084 } 085 Setup.getDefault().addPropertyChangeListener(this); 086 initTable(table); 087 } 088 089 private void initTable(JTable table) { 090 // Use XTableColumnModel so we can control which columns are visible 091 XTableColumnModel tcm = new XTableColumnModel(); 092 _table.setColumnModel(tcm); 093 _table.createDefaultColumnsFromModel(); 094 // Install the button handlers 095 ButtonRenderer buttonRenderer = new ButtonRenderer(); 096 TableCellEditor buttonEditor = new ButtonEditor(new javax.swing.JButton()); 097 tcm.getColumn(COMMENT_COLUMN).setCellRenderer(buttonRenderer); 098 tcm.getColumn(COMMENT_COLUMN).setCellEditor(buttonEditor); 099 tcm.getColumn(UP_COLUMN).setCellRenderer(buttonRenderer); 100 tcm.getColumn(UP_COLUMN).setCellEditor(buttonEditor); 101 tcm.getColumn(DOWN_COLUMN).setCellRenderer(buttonRenderer); 102 tcm.getColumn(DOWN_COLUMN).setCellEditor(buttonEditor); 103 tcm.getColumn(DELETE_COLUMN).setCellRenderer(buttonRenderer); 104 tcm.getColumn(DELETE_COLUMN).setCellEditor(buttonEditor); 105 table.setDefaultRenderer(JComboBox.class, new jmri.jmrit.symbolicprog.ValueRenderer()); 106 table.setDefaultEditor(JComboBox.class, new jmri.jmrit.symbolicprog.ValueEditor()); 107 108 // set column preferred widths 109 table.getColumnModel().getColumn(ID_COLUMN).setPreferredWidth(40); 110 table.getColumnModel().getColumn(NAME_COLUMN).setPreferredWidth(150); 111 table.getColumnModel().getColumn(TRAIN_DIRECTION_COLUMN).setPreferredWidth(95); 112 table.getColumnModel().getColumn(MAXMOVES_COLUMN).setPreferredWidth(50); 113 table.getColumnModel().getColumn(RANDOM_CONTROL_COLUMN).setPreferredWidth(65); 114 table.getColumnModel().getColumn(PICKUP_COLUMN).setPreferredWidth(65); 115 table.getColumnModel().getColumn(DROP_COLUMN).setPreferredWidth(65); 116 table.getColumnModel().getColumn(TRAVEL_COLUMN).setPreferredWidth(65); 117 table.getColumnModel().getColumn(TIME_COLUMN).setPreferredWidth(65); 118 table.getColumnModel().getColumn(MAXLENGTH_COLUMN).setPreferredWidth(75); 119 table.getColumnModel().getColumn(GRADE).setPreferredWidth(50); 120 table.getColumnModel().getColumn(TRAINICONX).setPreferredWidth(35); 121 table.getColumnModel().getColumn(TRAINICONY).setPreferredWidth(35); 122 table.getColumnModel().getColumn(COMMENT_COLUMN).setPreferredWidth(70); 123 table.getColumnModel().getColumn(UP_COLUMN).setPreferredWidth(60); 124 table.getColumnModel().getColumn(DOWN_COLUMN).setPreferredWidth(70); 125 table.getColumnModel().getColumn(DELETE_COLUMN).setPreferredWidth(80); 126 127 _frame.loadTableDetails(table); 128 // does not use a table sorter 129 table.setRowSorter(null); 130 131 // turn off columns 132 tcm.setColumnVisible(tcm.getColumnByModelIndex(TIME_COLUMN), false); 133 134 updateList(); 135 } 136 137 @Override 138 public int getRowCount() { 139 return _routeList.size(); 140 } 141 142 @Override 143 public int getColumnCount() { 144 return HIGHEST_COLUMN; 145 } 146 147 @Override 148 public String getColumnName(int col) { 149 switch (col) { 150 case ID_COLUMN: 151 return Bundle.getMessage("Id"); 152 case NAME_COLUMN: 153 return Bundle.getMessage("Location"); 154 case TRAIN_DIRECTION_COLUMN: 155 return Bundle.getMessage("TrainDirection"); 156 case MAXMOVES_COLUMN: 157 return Bundle.getMessage("MaxMoves"); 158 case RANDOM_CONTROL_COLUMN: 159 return Bundle.getMessage("Random"); 160 case PICKUP_COLUMN: 161 return Bundle.getMessage("Pickups"); 162 case DROP_COLUMN: 163 return Bundle.getMessage("Drops"); 164 case TRAVEL_COLUMN: 165 return Bundle.getMessage("Travel"); 166 case TIME_COLUMN: 167 return Bundle.getMessage("Time"); 168 case MAXLENGTH_COLUMN: 169 return Bundle.getMessage("MaxLength"); 170 case GRADE: 171 return Bundle.getMessage("Grade"); 172 case TRAINICONX: 173 return Bundle.getMessage("X"); 174 case TRAINICONY: 175 return Bundle.getMessage("Y"); 176 case COMMENT_COLUMN: 177 return Bundle.getMessage("Comment"); 178 case UP_COLUMN: 179 return Bundle.getMessage("Up"); 180 case DOWN_COLUMN: 181 return Bundle.getMessage("Down"); 182 case DELETE_COLUMN: 183 return Bundle.getMessage("ButtonDelete"); // titles above all columns 184 default: 185 return "unknown"; // NOI18N 186 } 187 } 188 189 @Override 190 public Class<?> getColumnClass(int col) { 191 switch (col) { 192 case ID_COLUMN: 193 case NAME_COLUMN: 194 return String.class; 195 case TRAVEL_COLUMN: 196 case MAXLENGTH_COLUMN: 197 case MAXMOVES_COLUMN: 198 case TRAINICONX: 199 case TRAINICONY: 200 return Integer.class; 201 case GRADE: 202 return Double.class; 203 case TRAIN_DIRECTION_COLUMN: 204 case RANDOM_CONTROL_COLUMN: 205 case PICKUP_COLUMN: 206 case DROP_COLUMN: 207 case TIME_COLUMN: 208 return JComboBox.class; 209 case COMMENT_COLUMN: 210 case UP_COLUMN: 211 case DOWN_COLUMN: 212 case DELETE_COLUMN: 213 return JButton.class; 214 default: 215 return null; 216 } 217 } 218 219 @Override 220 public boolean isCellEditable(int row, int col) { 221 switch (col) { 222 case DELETE_COLUMN: 223 case TRAIN_DIRECTION_COLUMN: 224 case MAXMOVES_COLUMN: 225 case RANDOM_CONTROL_COLUMN: 226 case PICKUP_COLUMN: 227 case DROP_COLUMN: 228 case TRAVEL_COLUMN: 229 case TIME_COLUMN: 230 case MAXLENGTH_COLUMN: 231 case GRADE: 232 case TRAINICONX: 233 case TRAINICONY: 234 case COMMENT_COLUMN: 235 case UP_COLUMN: 236 case DOWN_COLUMN: 237 return true; 238 default: 239 return false; 240 } 241 } 242 243 @Override 244 public Object getValueAt(int row, int col) { 245 if (row >= getRowCount()) { 246 return "ERROR unknown " + row; // NOI18N 247 } 248 RouteLocation rl = _routeList.get(row); 249 if (rl == null) { 250 return "ERROR unknown route location " + row; // NOI18N 251 } 252 switch (col) { 253 case ID_COLUMN: 254 return rl.getId(); 255 case NAME_COLUMN: 256 return rl.getName(); 257 case TRAIN_DIRECTION_COLUMN: { 258 JComboBox<String> cb = Setup.getTrainDirectionComboBox(); 259 cb.setSelectedItem(rl.getTrainDirectionString()); 260 return cb; 261 } 262 case MAXMOVES_COLUMN: 263 return rl.getMaxCarMoves(); 264 case RANDOM_CONTROL_COLUMN: { 265 JComboBox<String> cb = getRandomControlComboBox(); 266 cb.setSelectedItem(rl.getRandomControl()); 267 return cb; 268 } 269 case PICKUP_COLUMN: { 270 JComboBox<String> cb = getYesNoComboBox(); 271 cb.setSelectedItem(rl.isPickUpAllowed() ? Bundle.getMessage("yes") : Bundle.getMessage("no")); 272 return cb; 273 } 274 case DROP_COLUMN: { 275 JComboBox<String> cb = getYesNoComboBox(); 276 cb.setSelectedItem(rl.isDropAllowed() ? Bundle.getMessage("yes") : Bundle.getMessage("no")); 277 return cb; 278 } 279 case TRAVEL_COLUMN: { 280 return rl.getWait() + Setup.getTravelTime(); 281 } 282 case TIME_COLUMN: { 283 JComboBox<String> cb = getTimeComboBox(); 284 cb.setSelectedItem(rl.getDepartureTime()); 285 return cb; 286 } 287 case MAXLENGTH_COLUMN: 288 return rl.getMaxTrainLength(); 289 case GRADE: 290 return rl.getGrade(); 291 case TRAINICONX: 292 return rl.getTrainIconX(); 293 case TRAINICONY: 294 return rl.getTrainIconY(); 295 case COMMENT_COLUMN: { 296 if (rl.getComment().equals(RouteLocation.NONE)) { 297 return Bundle.getMessage("Add"); 298 } else { 299 return Bundle.getMessage("ButtonEdit"); 300 } 301 } 302 case UP_COLUMN: 303 return Bundle.getMessage("Up"); 304 case DOWN_COLUMN: 305 return Bundle.getMessage("Down"); 306 case DELETE_COLUMN: 307 return Bundle.getMessage("ButtonDelete"); 308 default: 309 return "unknown " + col; // NOI18N 310 } 311 } 312 313 @Override 314 public void setValueAt(Object value, int row, int col) { 315 if (value == null) { 316 log.debug("Warning route table row {} still in edit", row); 317 return; 318 } 319 RouteLocation rl = _routeList.get(row); 320 if (rl == null) { 321 log.error("ERROR unknown route location for row: {}", row); // NOI18N 322 } 323 switch (col) { 324 case COMMENT_COLUMN: 325 setComment(rl); 326 break; 327 case UP_COLUMN: 328 moveUpRouteLocation(rl); 329 break; 330 case DOWN_COLUMN: 331 moveDownRouteLocation(rl); 332 break; 333 case DELETE_COLUMN: 334 deleteRouteLocation(rl); 335 break; 336 case TRAIN_DIRECTION_COLUMN: 337 setTrainDirection(value, rl); 338 break; 339 case MAXMOVES_COLUMN: 340 setMaxTrainMoves(value, rl); 341 break; 342 case RANDOM_CONTROL_COLUMN: 343 setRandomControlValue(value, rl); 344 break; 345 case PICKUP_COLUMN: 346 setPickup(value, rl); 347 break; 348 case DROP_COLUMN: 349 setDrop(value, rl); 350 break; 351 case TRAVEL_COLUMN: 352 setTravel(value, rl); 353 break; 354 case TIME_COLUMN: 355 setDepartureTime(value, rl); 356 break; 357 case MAXLENGTH_COLUMN: 358 setMaxTrainLength(value, rl); 359 break; 360 case GRADE: 361 setGrade(value, rl); 362 break; 363 case TRAINICONX: 364 setTrainIconX(value, rl); 365 break; 366 case TRAINICONY: 367 setTrainIconY(value, rl); 368 break; 369 default: 370 break; 371 } 372 } 373 374 private void moveUpRouteLocation(RouteLocation rl) { 375 log.debug("move location up"); 376 _route.moveLocationUp(rl); 377 } 378 379 private void moveDownRouteLocation(RouteLocation rl) { 380 log.debug("move location down"); 381 _route.moveLocationDown(rl); 382 } 383 384 private void deleteRouteLocation(RouteLocation rl) { 385 log.debug("Delete location"); 386 _route.deleteLocation(rl); 387 } 388 389 private int _trainDirection = Setup.getDirectionInt(Setup.getTrainDirectionList().get(0)); 390 391 public int getLastTrainDirection() { 392 return _trainDirection; 393 } 394 395 private void setTrainDirection(Object value, RouteLocation rl) { 396 _trainDirection = Setup.getDirectionInt((String) ((JComboBox<?>) value).getSelectedItem()); 397 rl.setTrainDirection(_trainDirection); 398 // update train icon 399 rl.setTrainIconCoordinates(); 400 } 401 402 private int _maxTrainMoves = Setup.getCarMoves(); 403 404 public int getLastMaxTrainMoves() { 405 return _maxTrainMoves; 406 } 407 408 private void setMaxTrainMoves(Object value, RouteLocation rl) { 409 int moves = (int) value; 410 if (moves <= 500) { 411 rl.setMaxCarMoves(moves); 412 _maxTrainMoves = moves; 413 } else { 414 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("MaximumLocationMoves"), Bundle 415 .getMessage("CanNotChangeMoves"), JmriJOptionPane.ERROR_MESSAGE); 416 } 417 } 418 419 private void setRandomControlValue(Object value, RouteLocation rl) { 420 rl.setRandomControl((String) ((JComboBox<?>) value).getSelectedItem()); 421 } 422 423 private void setDrop(Object value, RouteLocation rl) { 424 rl.setDropAllowed( 425 ((String) ((JComboBox<?>) value).getSelectedItem()).equals(Bundle.getMessage("yes"))); 426 } 427 428 private void setPickup(Object value, RouteLocation rl) { 429 rl.setPickUpAllowed( 430 ((String) ((JComboBox<?>) value).getSelectedItem()).equals(Bundle.getMessage("yes"))); 431 } 432 433 private int _maxTrainLength = Setup.getMaxTrainLength(); 434 435 public int getLastMaxTrainLength() { 436 return _maxTrainLength; 437 } 438 439 private void setTravel(Object value, RouteLocation rl) { 440 int wait = (int) value; 441 rl.setWait(wait - Setup.getTravelTime()); 442 } 443 444 private void setDepartureTime(Object value, RouteLocation rl) { 445 rl.setDepartureTime(((String) ((JComboBox<?>) value).getSelectedItem())); 446 } 447 448 private void setMaxTrainLength(Object value, RouteLocation rl) { 449 int length = (int) value; 450 if (length < 0) { 451 log.error("Maximum departure length must be a postive number"); 452 return; 453 } 454 if (length > Setup.getMaxTrainLength()) { 455 log.error("Maximum departure length can not exceed maximum train length"); 456 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("DepartureLengthNotExceed", 457 length, Setup.getMaxTrainLength()), Bundle.getMessage("CanNotChangeMaxLength"), 458 JmriJOptionPane.ERROR_MESSAGE); 459 return; 460 } 461 if (rl != _route.getTerminatesRouteLocation() && 462 (length < 500 && Setup.getLengthUnit().equals(Setup.FEET) || 463 length < 160 && Setup.getLengthUnit().equals(Setup.METER))) { 464 // warn that train length might be too short 465 if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("LimitTrainLength", 466 length, Setup.getLengthUnit().toLowerCase(), rl.getName()), 467 Bundle.getMessage("WarningTooShort"), 468 JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) { 469 return; 470 } 471 } 472 rl.setMaxTrainLength(length); 473 _maxTrainLength = length; 474 } 475 476 private void setGrade(Object value, RouteLocation rl) { 477 double grade = (Double) value; 478 if (grade <= 6 && grade >= -6) { 479 rl.setGrade(grade); 480 } else { 481 log.error("Maximum grade is 6 percent"); 482 JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("MaxGrade"), 483 Bundle.getMessage("CanNotChangeGrade"), 484 JmriJOptionPane.ERROR_MESSAGE); 485 } 486 } 487 488 private void setTrainIconX(Object value, RouteLocation rl) { 489 int x = (int) value; 490 rl.setTrainIconX(x); 491 } 492 493 private void setTrainIconY(Object value, RouteLocation rl) { 494 int y = (int) value; 495 rl.setTrainIconY(y); 496 } 497 498 private void setComment(RouteLocation rl) { 499 // Create comment panel 500 final JDialog dialog = new JDialog(); 501 dialog.setLayout(new BorderLayout()); 502 dialog.setTitle(Bundle.getMessage("Comment") + " " + rl.getName()); 503 final JTextArea commentTextArea = new JTextArea(5, 100); 504 JScrollPane commentScroller = new JScrollPane(commentTextArea); 505 dialog.add(commentScroller, BorderLayout.CENTER); 506 commentTextArea.setText(rl.getComment()); 507 508 JPanel buttonPane = new JPanel(); 509 buttonPane.setLayout(new FlowLayout(FlowLayout.CENTER)); 510 dialog.add(buttonPane, BorderLayout.SOUTH); 511 512 // text color chooser 513 JPanel pTextColor = new JPanel(); 514 pTextColor.setBorder(BorderFactory.createTitledBorder(Bundle.getMessage("TextColor"))); 515 JColorChooser commentColorChooser = new JColorChooser(rl.getCommentColor()); 516 AbstractColorChooserPanel commentColorPanels[] = {new SplitButtonColorChooserPanel()}; 517 commentColorChooser.setChooserPanels(commentColorPanels); 518 commentColorChooser.setPreviewPanel(new JPanel()); 519 pTextColor.add(commentColorChooser); 520 buttonPane.add(pTextColor); 521 522 JButton okayButton = new JButton(Bundle.getMessage("ButtonOK")); 523 okayButton.addActionListener(new ActionListener() { 524 @Override 525 public void actionPerformed(ActionEvent arg0) { 526 rl.setComment(commentTextArea.getText()); 527 rl.setCommentColor(commentColorChooser.getColor()); 528 dialog.dispose(); 529 return; 530 } 531 }); 532 buttonPane.add(okayButton); 533 534 JButton cancelButton = new JButton(Bundle.getMessage("ButtonCancel")); 535 cancelButton.addActionListener(new ActionListener() { 536 @Override 537 public void actionPerformed(ActionEvent arg0) { 538 dialog.dispose(); 539 return; 540 } 541 }); 542 buttonPane.add(cancelButton); 543 544 dialog.setModal(true); 545 dialog.pack(); 546 dialog.setVisible(true); 547 } 548 549 private JComboBox<String> getYesNoComboBox() { 550 JComboBox<String> cb = new JComboBox<>(); 551 cb.addItem(Bundle.getMessage("yes")); 552 cb.addItem(Bundle.getMessage("no")); 553 return cb; 554 } 555 556 private JComboBox<String> getRandomControlComboBox() { 557 JComboBox<String> cb = new JComboBox<>(); 558 cb.addItem(RouteLocation.DISABLED); 559 // 10 to 100 by 10 560 for (int i = 10; i < 101; i = i + 10) { 561 cb.addItem(Integer.toString(i)); 562 } 563 return cb; 564 } 565 566 protected JComboBox<String> getTimeComboBox() { 567 JComboBox<String> timeBox = new JComboBox<>(); 568 String hour; 569 String minute; 570 timeBox.addItem(""); 571 for (int i = 0; i < 24; i++) { 572 if (i < 10) { 573 hour = "0" + Integer.toString(i); 574 } else { 575 hour = Integer.toString(i); 576 } 577 for (int j = 0; j < 60; j += 1) { 578 if (j < 10) { 579 minute = "0" + Integer.toString(j); 580 } else { 581 minute = Integer.toString(j); 582 } 583 584 timeBox.addItem(hour + ":" + minute); 585 } 586 } 587 return timeBox; 588 } 589 590 // this table listens for changes to a route and it's locations 591 @Override 592 public void propertyChange(PropertyChangeEvent e) { 593 if (Control.SHOW_PROPERTY) { 594 log.debug("Property change: ({}) old: ({}) new: ({})", e.getPropertyName(), e.getOldValue(), e 595 .getNewValue()); 596 } 597 if (e.getPropertyName().equals(Route.LISTCHANGE_CHANGED_PROPERTY)) { 598 updateList(); 599 fireTableDataChanged(); 600 } 601 if (e.getPropertyName().equals(Setup.TRAIN_DIRECTION_PROPERTY_CHANGE) || 602 e.getPropertyName().equals(Setup.TRAVEL_TIME_PROPERTY_CHANGE)) { 603 fireTableDataChanged(); 604 } 605 if (e.getSource().getClass().equals(RouteLocation.class)) { 606 RouteLocation rl = (RouteLocation) e.getSource(); 607 int row = _routeList.indexOf(rl); 608 if (Control.SHOW_PROPERTY) { 609 log.debug("Update route table row: {} id: {}", row, rl.getId()); 610 } 611 if (row >= 0) { 612 fireTableRowsUpdated(row, row); 613 } 614 } 615 } 616 617 private void removePropertyChangeRouteLocations() { 618 for (RouteLocation rl : _routeList) { 619 rl.removePropertyChangeListener(this); 620 } 621 } 622 623 public void dispose() { 624 removePropertyChangeRouteLocations(); 625 if (_route != null) { 626 _route.removePropertyChangeListener(this); 627 } 628 Setup.getDefault().removePropertyChangeListener(this); 629 _routeList.clear(); 630 fireTableDataChanged(); 631 } 632 633 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(RouteEditTableModel.class); 634}