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.pack();
163        sourceFrame.setVisible(true);
164    }
165
166    void addBufferPressed(ActionEvent e) {
167        if (bufferFrame == null) {
168            bufferFrame = new AudioBufferFrame(Bundle.getMessage("TitleAddAudioBuffer"), buffers);
169        }
170        bufferFrame.resetFrame();
171        bufferFrame.pack();
172        bufferFrame.setVisible(true);
173    }
174
175    @Override
176    public void setMenuBar(BeanTableFrame<Audio> f) {
177        JMenuBar menuBar = f.getJMenuBar();
178        MenuElement[] subElements;
179        JMenu fileMenu = null;
180        for (int i = 0; i < menuBar.getMenuCount(); i++) {
181            if (menuBar.getComponent(i) instanceof JMenu) {
182                if (((JMenu) menuBar.getComponent(i)).getText().equals(Bundle.getMessage("MenuFile"))) {
183                    fileMenu = menuBar.getMenu(i);
184                }
185            }
186        }
187        if (fileMenu == null) {
188            return;
189        }
190        subElements = fileMenu.getSubElements();
191        for (MenuElement subElement : subElements) {
192            MenuElement[] popsubElements = subElement.getSubElements();
193            for (MenuElement popsubElement : popsubElements) {
194                if (popsubElement instanceof JMenuItem) {
195                    if (((JMenuItem) popsubElement).getText().equals(Bundle.getMessage("PrintTable"))) {
196                        JMenuItem printMenu = (JMenuItem) popsubElement;
197                        fileMenu.remove(printMenu);
198                        break;
199                    }
200                }
201            }
202        }
203        fileMenu.add(atp.getPrintItem());
204    }
205
206    protected void editAudio(Audio a) {
207        Runnable t;
208        switch (a.getSubType()) {
209            case Audio.LISTENER:
210                if (listenerFrame == null) {
211                    listenerFrame = new AudioListenerFrame(Bundle.getMessage("TitleAddAudioListener"), listeners);
212                }
213                listenerFrame.populateFrame(a);
214                t = new Runnable() {
215                    @Override
216                    public void run() {
217                        listenerFrame.pack();
218                        listenerFrame.setVisible(true);
219                    }
220                };
221                javax.swing.SwingUtilities.invokeLater(t);
222                break;
223            case Audio.BUFFER:
224                if (bufferFrame == null) {
225                    bufferFrame = new AudioBufferFrame(Bundle.getMessage("TitleAddAudioBuffer"), buffers);
226                }
227                bufferFrame.populateFrame(a);
228                t = new Runnable() {
229                    @Override
230                    public void run() {
231                        bufferFrame.pack();
232                        bufferFrame.setVisible(true);
233                    }
234                };
235                javax.swing.SwingUtilities.invokeLater(t);
236                break;
237            case Audio.SOURCE:
238                if (sourceFrame == null) {
239                    sourceFrame = new AudioSourceFrame(Bundle.getMessage("TitleAddAudioBuffer"), sources);
240                }
241                sourceFrame.updateBufferList();
242                sourceFrame.populateFrame(a);
243                t = new Runnable() {
244                    @Override
245                    public void run() {
246                        sourceFrame.pack();
247                        sourceFrame.setVisible(true);
248                    }
249                };
250                javax.swing.SwingUtilities.invokeLater(t);
251                break;
252            default:
253                throw new IllegalArgumentException();
254        }
255    }
256
257    private static final Logger log = LoggerFactory.getLogger(AudioTableAction.class);
258
259    /**
260     * Define abstract AudioTableDataModel
261     */
262    abstract public class AudioTableDataModel extends BeanTableDataModel<Audio> {
263
264        char subType;
265
266        public static final int EDITCOL = NUMCOLUMN;
267
268        @SuppressWarnings({"OverridableMethodCallInConstructor", "LeakingThisInConstructor"})
269        public AudioTableDataModel(char subType) {
270            super();
271            this.subType = subType;
272            getManager().addPropertyChangeListener(this);
273            updateNameList();
274        }
275
276        @Override
277        public AudioManager getManager() {
278            return InstanceManager.getDefault(jmri.AudioManager.class);
279        }
280
281        @Override
282        protected String getMasterClassName() {
283            return getClassName();
284        }
285
286        @Override
287        public Audio getBySystemName(String name) {
288            return InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(name);
289        }
290
291        @Override
292        public Audio getByUserName(String name) {
293            return InstanceManager.getDefault(jmri.AudioManager.class).getByUserName(name);
294        }
295
296        /**
297         * Update the NamedBean list for the specific sub-type
298         *
299         * @param subType Audio sub-type to update
300         */
301        @SuppressWarnings("deprecation") // needs careful unwinding for Set operations & generics
302        protected synchronized void updateSpecificNameList(char subType) {
303            // first, remove listeners from the individual objects
304            if (sysNameList != null) {
305                for (String sysName : sysNameList) {
306                    // if object has been deleted, it's not here; ignore it
307                    NamedBean b = getBySystemName(sysName);
308                    if (b != null) {
309                        b.removePropertyChangeListener(this);
310                    }
311                }
312            }
313            sysNameList = getManager().getSystemNameList(subType);
314            // and add them back in
315            sysNameList.stream().forEach((sysName) -> {
316                getBySystemName(sysName).addPropertyChangeListener(this);
317            });
318        }
319
320        @Override
321        public int getColumnCount() {
322            return EDITCOL + 1;
323        }
324
325        @Override
326        public String getColumnName(int col) {
327            switch (col) {
328                case VALUECOL:
329                    return Bundle.getMessage("LightControlDescription");
330                case EDITCOL:
331                    return "";
332                default:
333                    return super.getColumnName(col);
334            }
335        }
336
337        @Override
338        public Class<?> getColumnClass(int col) {
339            switch (col) {
340                case VALUECOL:
341                    return String.class;
342                case EDITCOL:
343                    return JButton.class;
344                case DELETECOL:
345                    return (subType != Audio.LISTENER) ? JButton.class : String.class;
346                default:
347                    return super.getColumnClass(col);
348            }
349        }
350
351        @Override
352        public String getValue(String systemName) {
353            Object m = InstanceManager.getDefault(jmri.AudioManager.class).getBySystemName(systemName);
354            if (subType == Audio.SOURCE) {
355                return (m != null) ? ((jmri.jmrit.audio.AudioSource) m).getDebugString() : "";
356            } else {
357                return (m != null) ? m.toString() : "";
358            }
359        }
360
361        @Override
362        public Object getValueAt(int row, int col) {
363            Audio a;
364            switch (col) {
365                case SYSNAMECOL:  // slot number
366                    return sysNameList.get(row);
367                case USERNAMECOL:  // return user name
368                    // sometimes, the TableSorter invokes this on rows that no longer exist, so we check
369                    a = getBySystemName(sysNameList.get(row));
370                    return (a != null) ? a.getUserName() : null;
371                case VALUECOL:
372                    a = getBySystemName(sysNameList.get(row));
373                    return (a != null) ? getValue(a.getSystemName()) : null;
374                case COMMENTCOL:
375                    a = getBySystemName(sysNameList.get(row));
376                    return (a != null) ? a.getComment() : null;
377                case DELETECOL:
378                    return (subType != Audio.LISTENER) ? Bundle.getMessage("ButtonDelete") : "";
379                case EDITCOL:
380                    return Bundle.getMessage("ButtonEdit");
381                default:
382                    log.error("internal state inconsistent with table requst for {} {}", row, col);
383                    return null;
384            }
385        }
386
387        @Override
388        public void setValueAt(Object value, int row, int col) {
389            Audio a;
390            switch (col) {
391                case EDITCOL:
392                    a = getBySystemName(sysNameList.get(row));
393                    editAudio(a);
394                    break;
395                default:
396                    super.setValueAt(value, row, col);
397            }
398        }
399
400        @Override
401        public int getPreferredWidth(int col) {
402            switch (col) {
403                case VALUECOL:
404                    return new JTextField(50).getPreferredSize().width;
405                case EDITCOL:
406                    return new JButton(Bundle.getMessage("ButtonEdit")).getPreferredSize().width;
407                default:
408                    return super.getPreferredWidth(col);
409            }
410        }
411
412        @Override
413        public boolean isCellEditable(int row, int col) {
414            switch (col) {
415                case DELETECOL:
416                    return (subType != Audio.LISTENER);
417                case VALUECOL:
418                    return false;
419                case EDITCOL:
420                    return true;
421                default:
422                    return super.isCellEditable(row, col);
423            }
424        }
425
426        @Override
427        protected void clickOn(Audio t) {
428            // Do nothing
429        }
430
431        @Override
432        protected void configValueColumn(JTable table) {
433            // Do nothing
434        }
435
436        protected void configEditColumn(JTable table) {
437            // have the edit column hold a button
438            setColumnToHoldButton(table, EDITCOL,
439                    new JButton(Bundle.getMessage("ButtonEdit")));
440        }
441
442        @Override
443        protected String getBeanType() {
444            return "Audio";
445        }
446    }
447
448    /**
449     * Specific AudioTableDataModel for Audio Listener sub-type
450     */
451    public class AudioListenerTableDataModel extends AudioTableDataModel {
452
453        AudioListenerTableDataModel() {
454            super(Audio.LISTENER);
455        }
456
457        @Override
458        protected synchronized void updateNameList() {
459            updateSpecificNameList(Audio.LISTENER);
460        }
461
462        @Override
463        protected void showPopup(MouseEvent e) {
464            // Do nothing - disable pop-up menu for AudioListener
465        }
466    }
467
468    /**
469     * Specific AudioTableDataModel for Audio Buffer sub-type
470     */
471    public class AudioBufferTableDataModel extends AudioTableDataModel {
472
473        AudioBufferTableDataModel() {
474            super(Audio.BUFFER);
475        }
476
477        @Override
478        protected synchronized void updateNameList() {
479            updateSpecificNameList(Audio.BUFFER);
480        }
481    }
482
483    /**
484     * Specific AudioTableDataModel for Audio Source sub-type
485     */
486    public class AudioSourceTableDataModel extends AudioTableDataModel {
487
488        AudioSourceTableDataModel() {
489            super(Audio.SOURCE);
490        }
491
492        @Override
493        protected synchronized void updateNameList() {
494            updateSpecificNameList(Audio.SOURCE);
495        }
496    }
497
498    @Override
499    public void setMessagePreferencesDetails(){
500        jmri.InstanceManager.getDefault(jmri.UserPreferencesManager.class).setPreferenceItemDetails(getClassName(), "nullAudio", Bundle.getMessage("HideNullAudioWarningMessage"));
501        super.setMessagePreferencesDetails();
502    }
503
504    @Override
505    public String getClassDescription() {
506        return Bundle.getMessage("TitleAudioTable");
507    }
508
509    @Override
510    protected String getClassName() {
511        return AudioTableAction.class.getName();
512    }
513}