001package jmri.jmrit.beantable; 002 003import java.awt.event.ActionEvent; 004import java.util.ArrayList; 005 006import javax.annotation.Nonnull; 007import javax.swing.JButton; 008import javax.swing.JMenu; 009import javax.swing.JMenuBar; 010import javax.swing.JMenuItem; 011import javax.swing.JPanel; 012import javax.swing.JTable; 013import javax.swing.JTextField; 014import javax.swing.MenuElement; 015 016import jmri.Audio; 017import jmri.AudioManager; 018import jmri.InstanceManager; 019import jmri.NamedBean; 020import jmri.jmrit.audio.swing.AudioBufferFrame; 021import jmri.jmrit.audio.swing.AudioListenerFrame; 022import jmri.jmrit.audio.swing.AudioSourceFrame; 023import jmri.util.swing.JmriMouseEvent; 024 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028/** 029 * Swing action to create and register an AudioTable GUI. 030 * 031 * <hr> 032 * This file is part of JMRI. 033 * <p> 034 * JMRI is free software; you can redistribute it and/or modify it under the 035 * terms of version 2 of the GNU General Public License as published by the Free 036 * Software Foundation. See the "COPYING" file for a copy of this license. 037 * <p> 038 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 039 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 040 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 041 * <p> 042 * 043 * @author Bob Jacobsen Copyright (C) 2003 044 * @author Matthew Harris copyright (c) 2009 045 */ 046public class AudioTableAction extends AbstractTableAction<Audio> { 047 048 AudioTableDataModel listeners; 049 AudioTableDataModel buffers; 050 AudioTableDataModel sources; 051 052 AudioSourceFrame sourceFrame; 053 AudioBufferFrame bufferFrame; 054 AudioListenerFrame listenerFrame; 055 056 AudioTableFrame atf; 057 AudioTablePanel atp; 058 059 /** 060 * Create an action with a specific title. 061 * <p> 062 * Note that the argument is the Action title, not the title of the 063 * resulting frame. Perhaps this should be changed? 064 * 065 * @param actionName title of the action 066 */ 067 public AudioTableAction(String actionName) { 068 super(actionName); 069 070 // disable ourself if there is no primary Audio manager available 071 if (!InstanceManager.getOptionalDefault(AudioManager.class).isPresent()) { 072 setEnabled(false); 073 } 074 } 075 076 /** 077 * Default constructor 078 */ 079 public AudioTableAction() { 080 this(Bundle.getMessage("TitleAudioTable")); 081 } 082 083 @Override 084 public void addToFrame(@Nonnull BeanTableFrame<Audio> f) { 085 JButton addBufferButton = new JButton(Bundle.getMessage("ButtonAddAudioBuffer")); 086 atp.addToBottomBox(addBufferButton); 087 addBufferButton.addActionListener(this::addBufferPressed); 088 089 JButton addSourceButton = new JButton(Bundle.getMessage("ButtonAddAudioSource")); 090 atp.addToBottomBox(addSourceButton); 091 addSourceButton.addActionListener(this::addSourcePressed); 092 } 093 094 @Override 095 public void actionPerformed(ActionEvent e) { 096 097 // create the JTable model, with changes for specific NamedBean 098 createModel(); 099 100 // create the frame 101 atf = new AudioTableFrame(atp, helpTarget()) { 102 103 /** 104 * Include "Add Source..." and "Add Buffer..." buttons 105 */ 106 @Override 107 void extras() { 108 addToFrame(this); 109 } 110 }; 111 setTitle(); 112 atf.pack(); 113 atf.setVisible(true); 114 } 115 116 /** 117 * Create the JTable DataModels, along with the changes for the specific 118 * case of Audio objects 119 */ 120 @Override 121 protected void createModel() { 122 // ensure that the AudioFactory has been initialised 123 InstanceManager.getOptionalDefault(jmri.AudioManager.class).ifPresent(cm -> { 124 if (cm.getActiveAudioFactory() == null) { 125 cm.init(); 126 if (cm.getActiveAudioFactory() instanceof jmri.jmrit.audio.NullAudioFactory) { 127 InstanceManager.getDefault(jmri.UserPreferencesManager.class). 128 showWarningMessage("Error", "NullAudioFactory initialised - no sounds will be available", getClassName(), "nullAudio", false, true); 129 } 130 } 131 }); 132 listeners = new AudioListenerTableDataModel(); 133 buffers = new AudioBufferTableDataModel(); 134 sources = new AudioSourceTableDataModel(); 135 atp = new AudioTablePanel(listeners, buffers, sources, helpTarget()); 136 } 137 138 @Override 139 public JPanel getPanel() { 140 createModel(); 141 142 return atp; 143 } 144 145 @Override 146 protected void setTitle() { 147 atf.setTitle(Bundle.getMessage("TitleAudioTable")); 148 } 149 150 @Override 151 protected String helpTarget() { 152 return "package.jmri.jmrit.beantable.AudioTable"; 153 } 154 155 @Override 156 protected void addPressed(ActionEvent e) { 157 log.warn("This should not have happened"); 158 } 159 160 void addSourcePressed(ActionEvent e) { 161 if (sourceFrame == null) { 162 sourceFrame = new AudioSourceFrame(Bundle.getMessage("TitleAddAudioSource"), sources); 163 } 164 sourceFrame.updateBufferList(); 165 sourceFrame.resetFrame(); 166 sourceFrame.setEscapeKeyClosesWindow(true); 167 sourceFrame.pack(); 168 sourceFrame.setVisible(true); 169 } 170 171 void addBufferPressed(ActionEvent e) { 172 if (bufferFrame == null) { 173 bufferFrame = new AudioBufferFrame(Bundle.getMessage("TitleAddAudioBuffer"), buffers); 174 } 175 bufferFrame.resetFrame(); 176 bufferFrame.setEscapeKeyClosesWindow(true); 177 bufferFrame.pack(); 178 bufferFrame.setVisible(true); 179 } 180 181 @Override 182 public void setMenuBar(BeanTableFrame<Audio> f) { 183 JMenuBar menuBar = f.getJMenuBar(); 184 MenuElement[] subElements; 185 JMenu fileMenu = null; 186 for (int i = 0; i < menuBar.getMenuCount(); i++) { 187 if (menuBar.getComponent(i) instanceof JMenu) { 188 if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuFile"))) { 189 fileMenu = menuBar.getMenu(i); 190 } 191 } 192 } 193 if (fileMenu == null) { 194 return; 195 } 196 subElements = fileMenu.getSubElements(); 197 for (MenuElement subElement : subElements) { 198 MenuElement[] popsubElements = subElement.getSubElements(); 199 for (MenuElement popsubElement : popsubElements) { 200 if (popsubElement instanceof JMenuItem) { 201 if (((JMenuItem) popsubElement).getText().equals(Bundle.getMessage("PrintTable"))) { 202 JMenuItem printMenu = (JMenuItem) popsubElement; 203 fileMenu.remove(printMenu); 204 break; 205 } 206 } 207 } 208 } 209 fileMenu.add(atp.getPrintItem()); 210 } 211 212 protected void editAudio(Audio a) { 213 Runnable t; 214 switch (a.getSubType()) { 215 case Audio.LISTENER: 216 if (listenerFrame == null) { 217 listenerFrame = new AudioListenerFrame(Bundle.getMessage("TitleAddAudioListener"), listeners); 218 } 219 listenerFrame.populateFrame(a); 220 t = new Runnable() { 221 @Override 222 public void run() { 223 listenerFrame.pack(); 224 listenerFrame.setVisible(true); 225 } 226 }; 227 javax.swing.SwingUtilities.invokeLater(t); 228 break; 229 case Audio.BUFFER: 230 if (bufferFrame == null) { 231 bufferFrame = new AudioBufferFrame(Bundle.getMessage("TitleAddAudioBuffer"), buffers); 232 } 233 bufferFrame.populateFrame(a); 234 t = new Runnable() { 235 @Override 236 public void run() { 237 bufferFrame.pack(); 238 bufferFrame.setVisible(true); 239 } 240 }; 241 javax.swing.SwingUtilities.invokeLater(t); 242 break; 243 case Audio.SOURCE: 244 if (sourceFrame == null) { 245 sourceFrame = new AudioSourceFrame(Bundle.getMessage("TitleAddAudioBuffer"), sources); 246 } 247 sourceFrame.updateBufferList(); 248 sourceFrame.populateFrame(a); 249 t = new Runnable() { 250 @Override 251 public void run() { 252 sourceFrame.pack(); 253 sourceFrame.setVisible(true); 254 } 255 }; 256 javax.swing.SwingUtilities.invokeLater(t); 257 break; 258 default: 259 throw new IllegalArgumentException(); 260 } 261 } 262 263 private static final Logger log = LoggerFactory.getLogger(AudioTableAction.class); 264 265 /** 266 * Define abstract AudioTableDataModel 267 */ 268 abstract public class AudioTableDataModel extends BeanTableDataModel<Audio> { 269 270 char subType; 271 272 public static final int EDITCOL = NUMCOLUMN; 273 274 @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"}) 275 public AudioTableDataModel(char subType) { 276 super(); 277 this.subType = subType; 278 getManager().addPropertyChangeListener(this); 279 updateNameList(); 280 } 281 282 @Override 283 public AudioManager getManager() { 284 return InstanceManager.getDefault(jmri.AudioManager.class); 285 } 286 287 @Override 288 protected String getMasterClassName() { 289 return getClassName(); 290 } 291 292 @Override 293 public Audio getBySystemName(@Nonnull String name) { 294 return InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(name); 295 } 296 297 @Override 298 public Audio getByUserName(@Nonnull String name) { 299 return InstanceManager.getDefault(jmri.AudioManager.class).getByUserName(name); 300 } 301 302 /** 303 * Update the NamedBean list for the specific sub-type 304 * 305 * @param subType Audio sub-type to update 306 */ 307 protected synchronized void updateSpecificNameList(char subType) { 308 // first, remove listeners from the individual objects 309 if (sysNameList != null) { 310 for (String sysName : sysNameList) { 311 // if object has been deleted, it's not here; ignore it 312 NamedBean b = getBySystemName(sysName); 313 if (b != null) { 314 b.removePropertyChangeListener(this); 315 } 316 } 317 } 318 319 // recreate the list of system names 320 var tempSet = getManager().getNamedBeanSet(); 321 var out = new ArrayList<String>(); 322 tempSet.stream().forEach((audio) -> { 323 if (audio.getSubType() == subType) { 324 out.add(audio.getSystemName()); 325 } 326 }); 327 sysNameList = out; 328 329 // and add them back in 330 sysNameList.stream().forEach((sysName) -> { 331 getBySystemName(sysName).addPropertyChangeListener(this); 332 }); 333 } 334 335 @Override 336 public int getColumnCount() { 337 return EDITCOL + 1; 338 } 339 340 @Override 341 public String getColumnName(int col) { 342 switch (col) { 343 case VALUECOL: 344 return Bundle.getMessage("LightControlDescription"); 345 case EDITCOL: 346 return ""; 347 default: 348 return super.getColumnName(col); 349 } 350 } 351 352 @Override 353 public Class<?> getColumnClass(int col) { 354 switch (col) { 355 case VALUECOL: 356 return String.class; 357 case EDITCOL: 358 return JButton.class; 359 case DELETECOL: 360 return (subType != Audio.LISTENER) ? JButton.class : String.class; 361 default: 362 return super.getColumnClass(col); 363 } 364 } 365 366 @Override 367 public String getValue(String systemName) { 368 Object m = InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(systemName); 369 if (subType == Audio.SOURCE) { 370 return (m != null) ? ((jmri.jmrit.audio.AudioSource) m).getDebugString() : ""; 371 } else { 372 return (m != null) ? m.toString() : ""; 373 } 374 } 375 376 @Override 377 public Object getValueAt(int row, int col) { 378 Audio a; 379 switch (col) { 380 case SYSNAMECOL: // slot number 381 return sysNameList.get(row); 382 case USERNAMECOL: // return user name 383 // sometimes, the TableSorter invokes this on rows that no longer exist, so we check 384 a = getBySystemName(sysNameList.get(row)); 385 return (a != null) ? a.getUserName() : null; 386 case VALUECOL: 387 a = getBySystemName(sysNameList.get(row)); 388 return (a != null) ? getValue(a.getSystemName()) : null; 389 case COMMENTCOL: 390 a = getBySystemName(sysNameList.get(row)); 391 return (a != null) ? a.getComment() : null; 392 case DELETECOL: 393 return (subType != Audio.LISTENER) ? Bundle.getMessage("ButtonDelete") : ""; 394 case EDITCOL: 395 return Bundle.getMessage("ButtonEdit"); 396 default: 397 log.error("internal state inconsistent with table requst for {} {}", row, col); 398 return null; 399 } 400 } 401 402 @Override 403 public void setValueAt(Object value, int row, int col) { 404 Audio a; 405 switch (col) { 406 case EDITCOL: 407 a = getBySystemName(sysNameList.get(row)); 408 editAudio(a); 409 break; 410 default: 411 super.setValueAt(value, row, col); 412 } 413 } 414 415 @Override 416 public int getPreferredWidth(int col) { 417 switch (col) { 418 case VALUECOL: 419 return new JTextField(50).getPreferredSize().width; 420 case EDITCOL: 421 return new JButton(Bundle.getMessage("ButtonEdit")).getPreferredSize().width; 422 default: 423 return super.getPreferredWidth(col); 424 } 425 } 426 427 @Override 428 public boolean isCellEditable(int row, int col) { 429 switch (col) { 430 case DELETECOL: 431 return (subType != Audio.LISTENER); 432 case VALUECOL: 433 return false; 434 case EDITCOL: 435 return true; 436 default: 437 return super.isCellEditable(row, col); 438 } 439 } 440 441 @Override 442 protected void clickOn(Audio t) { 443 // Do nothing 444 } 445 446 @Override 447 protected void configValueColumn(JTable table) { 448 // Do nothing 449 } 450 451 protected void configEditColumn(JTable table) { 452 // have the edit column hold a button 453 setColumnToHoldButton(table, EDITCOL, 454 new JButton(Bundle.getMessage("ButtonEdit"))); 455 } 456 457 @Override 458 protected String getBeanType() { 459 return "Audio"; 460 } 461 } 462 463 /** 464 * Specific AudioTableDataModel for Audio Listener sub-type 465 */ 466 public class AudioListenerTableDataModel extends AudioTableDataModel { 467 468 AudioListenerTableDataModel() { 469 super(Audio.LISTENER); 470 } 471 472 @Override 473 protected synchronized void updateNameList() { 474 updateSpecificNameList(Audio.LISTENER); 475 } 476 477 @Override 478 protected void showPopup(JmriMouseEvent e) { 479 // Do nothing - disable pop-up menu for AudioListener 480 } 481 } 482 483 /** 484 * Specific AudioTableDataModel for Audio Buffer sub-type 485 */ 486 public class AudioBufferTableDataModel extends AudioTableDataModel { 487 488 AudioBufferTableDataModel() { 489 super(Audio.BUFFER); 490 } 491 492 @Override 493 protected synchronized void updateNameList() { 494 updateSpecificNameList(Audio.BUFFER); 495 } 496 } 497 498 /** 499 * Specific AudioTableDataModel for Audio Source sub-type 500 */ 501 public class AudioSourceTableDataModel extends AudioTableDataModel { 502 503 AudioSourceTableDataModel() { 504 super(Audio.SOURCE); 505 } 506 507 @Override 508 protected synchronized void updateNameList() { 509 updateSpecificNameList(Audio.SOURCE); 510 } 511 } 512 513 @Override 514 public void setMessagePreferencesDetails(){ 515 jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "nullAudio", Bundle.getMessage("HideNullAudioWarningMessage")); 516 super.setMessagePreferencesDetails(); 517 } 518 519 @Override 520 public String getClassDescription() { 521 return Bundle.getMessage("TitleAudioTable"); 522 } 523 524 @Override 525 protected String getClassName() { 526 return AudioTableAction.class.getName(); 527 } 528}