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