001package jmri.jmrit.logixng.tools.swing; 002 003import java.awt.Component; 004import java.awt.event.ActionEvent; 005import java.awt.event.ActionListener; 006import java.beans.PropertyVetoException; 007import java.util.*; 008import java.util.function.Predicate; 009import java.util.stream.Stream; 010 011import javax.swing.*; 012import javax.swing.table.*; 013 014import jmri.*; 015import jmri.jmrit.logixng.*; 016import jmri.util.swing.JComboBoxUtil; 017import jmri.util.swing.JmriJOptionPane; 018 019/** 020 * Table model for inline LogixNGs. 021 * 022 * @author Daniel Bergqvist Copyright (C) 2022 023 */ 024public class InlineLogixNGsTableModel extends AbstractTableModel { 025 026 public static final int COLUMN_SYSTEM_NAME = 0; 027 public static final int COLUMN_USER_NAME = COLUMN_SYSTEM_NAME + 1; 028 public static final int COLUMN_PANEL_NAME = COLUMN_USER_NAME + 1; 029 public static final int COLUMN_POSITIONABLE_NAME = COLUMN_PANEL_NAME + 1; 030 public static final int COLUMN_NAMED_BEAN = COLUMN_POSITIONABLE_NAME + 1; 031 public static final int COLUMN_POS_X = COLUMN_NAMED_BEAN + 1; 032 public static final int COLUMN_POS_Y = COLUMN_POS_X + 1; 033 public static final int COLUMN_MENU = COLUMN_POS_Y + 1; 034 public static final int NUM_COLUMNS = COLUMN_MENU + 1; 035 036 private final List<LogixNG> _logixNGs = new ArrayList<>(); 037 private boolean _inEditLogixNGMode = false; 038 private LogixNGEditor _logixNGEditor; 039 private Predicate<LogixNG> _filter; 040 041 042 public void init() { 043 updateList(); 044 InstanceManager.getDefault(LogixNG_Manager.class) 045 .addPropertyChangeListener("length", (evt) -> { updateList(); }); 046 } 047 048 public List<LogixNG> getLogixNGList() { 049 return InstanceManager.getDefault(LogixNG_Manager.class) 050 .getNamedBeanSet().stream().filter((LogixNG t) -> t.isInline()) 051 .collect(java.util.stream.Collectors.toList()); 052 } 053 054 private void updateList() { 055 Stream<LogixNG> stream = InstanceManager.getDefault(LogixNG_Manager.class) 056 .getNamedBeanSet().stream().filter((LogixNG t) -> t.isInline()); 057 _logixNGs.clear(); 058 if (_filter != null) stream = stream.filter(_filter); 059 _logixNGs.addAll(stream.collect(java.util.stream.Collectors.toList())); 060 fireTableDataChanged(); 061 } 062 063 /** 064 * Set the filter to select which beans to include in the table. 065 * @param filter the filter 066 */ 067 public void setFilter(Predicate<LogixNG> filter) { 068 this._filter = filter; 069 updateList(); 070 } 071 072 /** 073 * Get the filter to select which beans to include in the table. 074 * @return the filter 075 */ 076 public Predicate<LogixNG> getFilter() { 077 return _filter; 078 } 079 080 /** {@inheritDoc} */ 081 @Override 082 public int getRowCount() { 083 return _logixNGs.size(); 084 } 085 086 /** {@inheritDoc} */ 087 @Override 088 public int getColumnCount() { 089 return NUM_COLUMNS; 090 } 091 092 /** {@inheritDoc} */ 093 @Override 094 public String getColumnName(int col) { 095 switch (col) { 096 case COLUMN_SYSTEM_NAME: 097 return Bundle.getMessage("ColumnSystemName"); 098 case COLUMN_USER_NAME: 099 return Bundle.getMessage("ColumnUserName"); 100 case COLUMN_PANEL_NAME: 101 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnPanelName"); 102 case COLUMN_POSITIONABLE_NAME: 103 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnPositionableName"); 104 case COLUMN_NAMED_BEAN: 105 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnNamedBean"); 106 case COLUMN_POS_X: 107 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnPosX"); 108 case COLUMN_POS_Y: 109 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnPosY"); 110 case COLUMN_MENU: 111 return Bundle.getMessage("InlineLogixNGsTableModel_ColumnMenu"); 112 default: 113 throw new IllegalArgumentException("Invalid column"); 114 } 115 } 116 117 /** {@inheritDoc} */ 118 @Override 119 public Class<?> getColumnClass(int col) { 120 switch (col) { 121 case COLUMN_SYSTEM_NAME: 122 case COLUMN_USER_NAME: 123 case COLUMN_PANEL_NAME: 124 case COLUMN_POSITIONABLE_NAME: 125 case COLUMN_NAMED_BEAN: 126 return String.class; 127 case COLUMN_POS_X: 128 case COLUMN_POS_Y: 129 return Integer.class; 130 case COLUMN_MENU: 131 return Menu.class; 132 default: 133 throw new IllegalArgumentException("Invalid column"); 134 } 135 } 136 137 /** {@inheritDoc} */ 138 @Override 139 public boolean isCellEditable(int row, int col) { 140 return col == COLUMN_USER_NAME || col == COLUMN_MENU; 141 } 142 143 /** {@inheritDoc} */ 144 @Override 145 public void setValueAt(Object value, int rowIndex, int columnIndex) { 146 if (columnIndex == COLUMN_USER_NAME) { 147 if (value.equals("")) value = null; 148 149 LogixNG logixNG = _logixNGs.get(rowIndex); 150 if (value == null && logixNG.getUserName() == null) return; 151 if (value != null && value.equals(logixNG.getUserName())) return; 152 153 LogixNG_Manager logixNG_Manager = InstanceManager.getDefault(LogixNG_Manager.class); 154 LogixNG otherLogixNG = value != null 155 ? logixNG_Manager.getByUserName((String) value) : null; 156 if (otherLogixNG != null) { 157 log.error("User name is not unique {}", value); 158 String msg = Bundle.getMessage("WarningUserName", "" + value); 159 JmriJOptionPane.showMessageDialog(null, msg, 160 Bundle.getMessage("WarningTitle"), 161 JmriJOptionPane.ERROR_MESSAGE); 162 return; 163 } 164 165 NamedBeanHandleManager nbMan = InstanceManager.getDefault(NamedBeanHandleManager.class); 166 167 logixNG.setUserName((String) value); 168 if (nbMan.inUse(logixNG.getSystemName(), logixNG)) { 169 String msg = Bundle.getMessage("UpdateToUserName", logixNG.getBeanType(), value, logixNG.getSystemName()); 170 int optionPane = JmriJOptionPane.showConfirmDialog(null, 171 msg, Bundle.getMessage("UpdateToUserNameTitle"), 172 JmriJOptionPane.YES_NO_OPTION); 173 if (optionPane == JmriJOptionPane.YES_OPTION) { 174 //This will update the bean reference from the systemName to the userName 175 try { 176 nbMan.updateBeanFromSystemToUser(logixNG); 177 } catch (JmriException ex) { 178 //We should never get an exception here as we already check that the username is not valid 179 log.error("Impossible exception setting user name", ex); 180 } 181 } 182 } 183 } 184 } 185 186 /** {@inheritDoc} */ 187 @Override 188 public Object getValueAt(int rowIndex, int columnIndex) { 189 if (rowIndex >= _logixNGs.size()) throw new IllegalArgumentException( 190 String.format("Invalid row index: %s. Num rows: %s", rowIndex, _logixNGs.size())); 191 192 LogixNG logixNG = _logixNGs.get(rowIndex); 193 194 switch (columnIndex) { 195 case COLUMN_SYSTEM_NAME: 196 return logixNG.getSystemName(); 197 case COLUMN_USER_NAME: 198 return logixNG.getUserName(); 199 case COLUMN_PANEL_NAME: 200 return getEditorName(logixNG.getInlineLogixNG()); 201 case COLUMN_POSITIONABLE_NAME: 202 return getPositionableName(logixNG.getInlineLogixNG()); 203 case COLUMN_NAMED_BEAN: 204 String typeName = getTypeName(logixNG.getInlineLogixNG()); 205 return typeName != null ? typeName : ""; 206 case COLUMN_POS_X: 207 return getX(logixNG.getInlineLogixNG()); 208 case COLUMN_POS_Y: 209 return getY(logixNG.getInlineLogixNG()); 210 case COLUMN_MENU: 211 return Menu.Edit; 212 default: 213 throw new IllegalArgumentException("Invalid column"); 214 } 215 } 216 217 public static String getEditorName(InlineLogixNG inlineLogixNG) { 218 return inlineLogixNG != null 219 ? inlineLogixNG.getEditorName() 220 : Bundle.getMessage("InlineLogixNGsTableModel_Error"); 221 } 222 223 public static String getTypeName(InlineLogixNG inlineLogixNG) { 224 return inlineLogixNG != null 225 ? inlineLogixNG.getTypeName() 226 : Bundle.getMessage("InlineLogixNGsTableModel_Error"); 227 } 228 229 public static String getPositionableName(InlineLogixNG inlineLogixNG) { 230 return inlineLogixNG != null 231 ? inlineLogixNG.getNameString() 232 : Bundle.getMessage("InlineLogixNGsTableModel_Error"); 233 } 234 235 public static int getX(InlineLogixNG inlineLogixNG) { 236 return inlineLogixNG != null ? inlineLogixNG.getX() : 0; 237 } 238 239 public static int getY(InlineLogixNG inlineLogixNG) { 240 return inlineLogixNG != null ? inlineLogixNG.getY() : 0; 241 } 242 243 public void setColumnForMenu(JTable table) { 244 JComboBox<Menu> comboBox = new JComboBox<>(); 245 table.setRowHeight(comboBox.getPreferredSize().height); 246 table.getColumnModel().getColumn(COLUMN_MENU) 247 .setPreferredWidth((comboBox.getPreferredSize().width) + 4); 248 } 249 250 251 public static enum Menu { 252 Edit(Bundle.getMessage("InlineLogixNGsTableModel_TableMenuEdit")), 253 Delete(Bundle.getMessage("InlineLogixNGsTableModel_TableMenuDelete")); 254 255 private final String _descr; 256 257 private Menu(String descr) { 258 _descr = descr; 259 } 260 261 @Override 262 public String toString() { 263 return _descr; 264 } 265 } 266 267 268 public static class MenuCellRenderer extends DefaultTableCellRenderer { 269 270 @Override 271 public Component getTableCellRendererComponent(JTable table, Object value, 272 boolean isSelected, boolean hasFocus, int row, int column) { 273 274 if (value == null) value = Menu.Edit; 275 276 if (! (value instanceof Menu)) { 277 throw new IllegalArgumentException("value is not an Menu: " + value.getClass().getName()); 278 } 279 setText(((Menu) value).toString()); 280 return this; 281 } 282 } 283 284 285 public static class MenuCellEditor extends AbstractCellEditor 286 implements TableCellEditor, ActionListener { 287 288 JTable _table; 289 InlineLogixNGsTableModel _tableModel; 290 291 public MenuCellEditor(JTable table, InlineLogixNGsTableModel tableModel) { 292 _table = table; 293 _tableModel = tableModel; 294 } 295 296 @Override 297 public Object getCellEditorValue() { 298 return Menu.Edit; 299 } 300 301 @Override 302 public Component getTableCellEditorComponent(JTable table, Object value, 303 boolean isSelected, int row, int column) { 304 305 if (value == null) value = Menu.Edit; 306 307 if (! (value instanceof Menu)) { 308 throw new IllegalArgumentException("value is not an Menu: " + value.getClass().getName()); 309 } 310 311 JComboBox<Menu> menuComboBox = new JComboBox<>(); 312 313 for (Menu menu : Menu.values()) { 314 menuComboBox.addItem(menu); 315 } 316 JComboBoxUtil.setupComboBoxMaxRows(menuComboBox); 317 318 menuComboBox.setSelectedItem(value); 319 menuComboBox.addActionListener(this); 320 321 return menuComboBox; 322 } 323 324 @Override 325 @SuppressWarnings("unchecked") // Not possible to check that event.getSource() is instanceof JComboBox<Menu> 326 public void actionPerformed(ActionEvent event) { 327 if (! (event.getSource() instanceof JComboBox)) { 328 throw new IllegalArgumentException("value is not an InitialValueType: " + event.getSource().getClass().getName()); 329 } 330 JComboBox<Menu> menuComboBox = (JComboBox<Menu>) event.getSource(); 331 332 int row = _table.getRowSorter().convertRowIndexToModel(_table.getSelectedRow()); 333 Menu menu = menuComboBox.getItemAt(menuComboBox.getSelectedIndex()); 334 335 // Cancel editing before doing the change to the table. 336 _table.editingCanceled(null); 337 338 switch (menu) { 339 case Edit: 340 edit(row); 341 break; 342 case Delete: 343 delete(row); 344 break; 345 default: 346 // Do nothing 347 } 348 } 349 350 /** 351 * Check if edit of a conditional is in progress. 352 * 353 * @return true if this is the case, after showing dialog to user 354 */ 355 private boolean checkEditConditionalNG() { 356 if (_tableModel._inEditLogixNGMode) { 357 // Already editing a LogixNG, ask for completion of that edit 358 JmriJOptionPane.showMessageDialog(null, 359 Bundle.getMessage("Error_InlineLogixNGInEditMode"), // NOI18N 360 Bundle.getMessage("ErrorTitle"), // NOI18N 361 JmriJOptionPane.ERROR_MESSAGE); 362 _tableModel._logixNGEditor.bringToFront(); 363 return true; 364 } 365 return false; 366 } 367 368 private void edit(int row) { 369 if (checkEditConditionalNG()) return; 370 371 LogixNG logixNG = _tableModel._logixNGs.get(row); 372 LogixNGEditor logixNGEditor = 373 new LogixNGEditor(null, logixNG.getSystemName()); 374 logixNGEditor.addEditorEventListener((HashMap<String, String> data) -> { 375 data.forEach((key, value) -> { 376 if (key.equals("Finish")) { // NOI18N 377 _tableModel._inEditLogixNGMode = false; 378 } else if (key.equals("Delete")) { // NOI18N 379 _tableModel._inEditLogixNGMode = false; 380 delete(row); 381 } else if (key.equals("chgUname")) { // NOI18N 382 logixNG.setUserName(value); 383 _tableModel.fireTableDataChanged(); 384 } 385 }); 386 if (logixNG.getNumConditionalNGs() == 0) { 387 deleteBean(logixNG); 388 } 389 }); 390 logixNGEditor.bringToFront(); 391 _tableModel._inEditLogixNGMode = true; 392 _tableModel._logixNGEditor = logixNGEditor; 393 } 394 395 private void delete(int row) { 396 LogixNG logixNG = _tableModel._logixNGs.get(row); 397 398 if (_tableModel._inEditLogixNGMode) { 399 // Already editing a bean, ask for completion of that edit 400 JmriJOptionPane.showMessageDialog(null, 401 Bundle.getMessage("Error_InlineLogixNGInEditMode", 402 logixNG.getSystemName()), 403 Bundle.getMessage("ErrorTitle"), 404 JmriJOptionPane.ERROR_MESSAGE); 405 if (_tableModel._logixNGEditor != null) { 406 _tableModel._logixNGEditor.bringToFront(); 407 } 408 return; 409 } 410 411 DeleteBean<LogixNG> deleteBean = new DeleteBean<>( 412 InstanceManager.getDefault(LogixNG_Manager.class)); 413 414 boolean hasChildren = logixNG.getNumConditionalNGs() > 0; 415 416 deleteBean.delete(logixNG, hasChildren, (t)->{deleteBean(t);}, 417 (t,list)->{logixNG.getListenerRefsIncludingChildren(list);}, 418 jmri.jmrit.logixng.LogixNG_UserPreferences.class.getName()); 419 } 420 421 private void deleteBean(LogixNG logixNG) { 422 logixNG.setEnabled(false); 423 try { 424 InstanceManager.getDefault(LogixNG_Manager.class).deleteBean(logixNG, "DoDelete"); 425 if (logixNG.getInlineLogixNG() != null) { 426 logixNG.getInlineLogixNG().setLogixNG(null); 427 } 428 } catch (PropertyVetoException e) { 429 //At this stage the DoDelete shouldn't fail, as we have already done a can delete, which would trigger a veto 430 log.error("{} : Could not Delete.", e.getMessage()); 431 } 432 } 433 } 434 435 436 private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(InlineLogixNGsTableModel.class); 437}