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