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}